mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-25 08:09:02 +01:00
2117 lines
83 KiB
PHP
2117 lines
83 KiB
PHP
<?php
|
|
/**************************************************************************\
|
|
* phpGroupWare API - POP3 *
|
|
* This file written by Mark Peters <skeeter@phpgroupware.org> *
|
|
* Handles specific operations in dealing with POP3 *
|
|
* Copyright (C) 2001 Mark Peters and Angelo "Angles" Puglisi *
|
|
* -------------------------------------------------------------------------*
|
|
* This library is part of the phpGroupWare API *
|
|
* http://www.phpgroupware.org/api *
|
|
* ------------------------------------------------------------------------ *
|
|
* This library is free software; you can redistribute it and/or modify it *
|
|
* under the terms of the GNU Lesser General Public License as published by *
|
|
* the Free Software Foundation; either version 2.1 of the License, *
|
|
* or any later version. *
|
|
* This library is distributed in the hope that it will be useful, but *
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
* See the GNU Lesser General Public License for more details. *
|
|
* You should have received a copy of the GNU Lesser General Public License *
|
|
* along with this library; if not, write to the Free Software Foundation, *
|
|
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
|
|
\**************************************************************************/
|
|
|
|
/* $Id$ */
|
|
|
|
/*!
|
|
@class msg (sockets)
|
|
@abstract part of mail Data Communications class
|
|
@discussion mail Extends msg_base which Extends phpgw api class network
|
|
This is a top level class msg is designed specifically POP3
|
|
@syntax CreateObject('email.mail');
|
|
@author Angles, Skeeter, Itzchak Rehberg, Joseph Engo
|
|
@copyright LGPL
|
|
@package email (to be moved to phpgwapi when mature)
|
|
@access public
|
|
*/
|
|
class msg extends msg_base
|
|
{
|
|
/**************************************************************************\
|
|
* Functions that DO NOTHING in POP3
|
|
\**************************************************************************/
|
|
function createmailbox($stream,$mailbox)
|
|
{
|
|
return true;
|
|
}
|
|
function deletemailbox($stream,$mailbox)
|
|
{
|
|
return true;
|
|
}
|
|
function expunge($stream)
|
|
{
|
|
return true;
|
|
}
|
|
function listmailbox($stream,$ref,$pattern)
|
|
{
|
|
return False;
|
|
}
|
|
function mailcopy($stream,$msg_list,$mailbox,$flags)
|
|
{
|
|
return False;
|
|
}
|
|
function move($stream,$msg_list,$mailbox)
|
|
{
|
|
return False;
|
|
}
|
|
function reopen($stream,$mailbox,$flags = "")
|
|
{
|
|
return False;
|
|
}
|
|
function append($stream, $folder = "Sent", $header, $body, $flags = "")
|
|
{
|
|
return False;
|
|
}
|
|
/**************************************************************************\
|
|
* Functions Not Yet Implemented in POP3
|
|
\**************************************************************************/
|
|
function fetch_overview($stream,$sequence,$flags)
|
|
{
|
|
return False;
|
|
}
|
|
function noop_ping_test($stream)
|
|
{
|
|
return False;
|
|
}
|
|
function server_last_error()
|
|
{
|
|
return '';
|
|
}
|
|
|
|
/**************************************************************************\
|
|
* OPEN and CLOSE Server Connection
|
|
\**************************************************************************/
|
|
/*!
|
|
@function open
|
|
@abstract implements php function IMAP_OPEN
|
|
@param $fq_folder : string : {SERVER_NAME:PORT/OPTIONS}FOLDERNAME
|
|
@param $user : string : account name to log into on the server
|
|
@param $pass : string : password for this account on the mail server
|
|
@param $flags : NOT YET IMPLEMENTED
|
|
@discussion implements the functionality of php function IMAP_OPEN
|
|
note that php's IMAP_OPEN applies to IMAP, POP3 and NNTP servers
|
|
@syntax ?
|
|
@author Angles, skeeter
|
|
@access public
|
|
*/
|
|
function open ($fq_folder, $user, $pass, $flags='')
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Entering open<br>'; }
|
|
|
|
// fq_folder is a "fully qualified folder", seperate the parts:
|
|
$svr_data = array();
|
|
$svr_data = $this->distill_fq_folder($fq_folder);
|
|
$folder = $svr_data['folder'];
|
|
$server = $svr_data['server'];
|
|
$port = $svr_data['port'];
|
|
if ($this->debug >= 1) { echo 'pop3: open: svr_data:<br>'.serialize($svr_data).'<br>'; }
|
|
|
|
//$port = 110;
|
|
if (!$this->open_port($server,$port,15))
|
|
{
|
|
echo '<p><center><b>' . lang('There was an error trying to connect to your POP3 server.<br>Please contact your admin to check the servername, username or password.').'</b></center>';
|
|
$GLOBALS['phpgw_info']['flags']['nodisplay'] = True;
|
|
exit;
|
|
}
|
|
$this->read_port();
|
|
if(!$this->msg2socket('USER '.$user,"^\+ok",&$response) || !$this->msg2socket('PASS '.$pass,"^\+ok",&$response))
|
|
{
|
|
$this->error();
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving open with Error<br>'; }
|
|
return False;
|
|
}
|
|
else
|
|
{
|
|
//echo "Successful POP3 Login.<br>\n";
|
|
if ($this->debug >= 1) { echo 'pop3: open: Successful POP3 Login<br>'; }
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving open<br>'; }
|
|
return $this->socket;
|
|
}
|
|
}
|
|
|
|
function close($flags='')
|
|
{
|
|
if (!$this->msg2socket('QUIT',"^\+ok",&$response))
|
|
{
|
|
$this->error();
|
|
if ($this->debug >= 1) { echo 'pop3: close: Error<br>'; }
|
|
return False;
|
|
}
|
|
else
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: close: Successful POP3 Logout<br>'; }
|
|
return True;
|
|
}
|
|
}
|
|
|
|
/**************************************************************************\
|
|
* Mailbox Status and Information
|
|
\**************************************************************************/
|
|
|
|
function mailboxmsginfo($stream_notused='')
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Entering mailboxmsginfo<br>'; }
|
|
// caching this with POP3 is OK but will cause HAVOC with IMAP or NNTP
|
|
// do we have a cached header_array ?
|
|
//if ($this->mailbox_msg_info != '')
|
|
//{
|
|
// if ($this->debug >= 1) { echo 'pop3: Leaving mailboxmsginfo returning cached data<br>'; }
|
|
// return $this->mailbox_msg_info;
|
|
//}
|
|
// NO cached data, so go get it
|
|
// initialize the structure
|
|
$info = new mailbox_msg_info;
|
|
$info->Date = '';
|
|
$info->Driver ='';
|
|
$info->Mailbox = '';
|
|
$info->Nmsgs = '';
|
|
$info->Recent = '';
|
|
$info->Unread = '';
|
|
$info->Size = '';
|
|
// POP3 will only give 2 items:
|
|
// 1) number of messages
|
|
// 2) total size of mailbox
|
|
// imap_mailboxmsginfo is the only function to return both of these
|
|
if (!$this->msg2socket('STAT',"^\+ok",&$response))
|
|
{
|
|
$this->error();
|
|
return False;
|
|
}
|
|
$num_msg = explode(' ',$response);
|
|
// fill the only 2 data items we have
|
|
$info->Nmsgs = trim($num_msg[1]);
|
|
$info->Size = trim($num_msg[2]);
|
|
if ($info->Nmsgs)
|
|
{
|
|
if ($this->debug >= 2)
|
|
{
|
|
echo 'pop3: mailboxmsginfo: info->Nmsgs: '.$info->Nmsgs.'<br>';
|
|
echo 'pop3: mailboxmsginfo: info->Size: '.$info->Size.'<br>';
|
|
}
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving mailboxmsginfo<br>'; }
|
|
// save this data for future use
|
|
//$this->mailbox_msg_info = $info;
|
|
return $info;
|
|
}
|
|
else
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: mailboxmsginfo: returining False<br>'; }
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving mailboxmsginfo<br>'; }
|
|
return False;
|
|
}
|
|
}
|
|
|
|
function status($stream_notused='', $fq_folder='',$options=SA_ALL)
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Entering status<br>'; }
|
|
// POP3 has only INBOX so ignore $fq_folder
|
|
// assume option is SA_ALL for POP3 because POP3 returns so little info anyway
|
|
// initialize structure
|
|
$info = new mailbox_status;
|
|
$info->messages = '';
|
|
$info->recent = '';
|
|
$info->unseen = '';
|
|
$info->uidnext = '';
|
|
$info->uidvalidity = '';
|
|
// POP3 only knows:
|
|
// 1) many messages are in the box, which is:
|
|
// a) returned by imap_ mailboxmsginfo as ->Nmsgs (in IMAP this is thefolder opened)
|
|
// b) returned by imap_status (THIS) as ->messages (in IMAP used for folders other than the opened one)
|
|
// 2) total size of the box, which is:
|
|
// returned by imap_ mailboxmsginfo as ->Size
|
|
// Most Efficient Method:
|
|
// call mailboxmsginfo and fill THIS structurte from that
|
|
$mailbox_msg_info = $this->mailboxmsginfo($stream_notused);
|
|
// all POP3 can return from imap_status is messages
|
|
$info->messages = $mailbox_msg_info->Nmsgs;
|
|
if ($this->debug >= 1) { echo 'pop3: status: info->messages: '.$info->messages.'<br>'; }
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving status<br>'; }
|
|
return $info;
|
|
}
|
|
|
|
// returns number of messages in the mailbox
|
|
function num_msg($stream_notused='')
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Entering num_msg<br>'; }
|
|
// Most Efficient Method:
|
|
// call mailboxmsginfo and fill THIS size data from that
|
|
$mailbox_msg_info = $this->mailboxmsginfo($stream_notused);
|
|
$return_num_msg = $mailbox_msg_info->Nmsgs;
|
|
if ($this->debug >= 1) { echo 'pop3: num_msg: '.$return_num_msg.'<br>'; }
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving num_msg<br>'; }
|
|
return $return_num_msg;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
* Message Sorting
|
|
\**************************************************************************/
|
|
/*!
|
|
@function sort
|
|
@abstract implements IMAP_SORT
|
|
@param $stream_notused : socket class handles stream reference internally
|
|
@param $criteria : integer : HOW to sort the messages, we prefer SORTARRIVAL, or "1" as default
|
|
SORTDATE: 0: This is the Date that the senders email client stamps the message with
|
|
SORTARRIVAL: 1: This is the date the email arrives at your email server (MTA)
|
|
SORTFROM: 2
|
|
SORTSUBJECT: 3
|
|
SORTSIZE: 6
|
|
@param $reverse : boolean : the ordering if the messages , low to high, or high to low
|
|
FALSE: 0: lowest to highest (default for php's builtin imap)
|
|
TRUE: 1: highest to lowest, a.k.a. "Reverse Sorting"
|
|
@param $options : not implemented
|
|
@result returns an array of integers which are messages numbers for the
|
|
messages sorted as requested.
|
|
@discussion: using SORTDATE can cause some messages to be displayed in the wrong
|
|
cronologicall order, because the sender's MUA can be innaccurate in date stamping
|
|
@author Angles, Skeeter, Itzchak Rehberg, Joseph Engo
|
|
@access public
|
|
*/
|
|
function sort($stream_notused='',$criteria=SORTARRIVAL,$reverse=False,$options='')
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Entering sort<br>'; }
|
|
|
|
// nr_of_msgs on pop server
|
|
$msg_num = $this->num_msg($stream_notused);
|
|
|
|
// no msgs - no sort.
|
|
if (!$msg_num)
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving sort with Error<br>'; }
|
|
return false;
|
|
}
|
|
if ($this->debug >= 1) { echo 'pop3: sort: Number of Msgs:'.$msg_num.'<br>'; }
|
|
switch($criteria)
|
|
{
|
|
case SORTDATE:
|
|
if ($this->debug >= 1) { echo 'pop3: sort: case SORTDATE<br>'; }
|
|
$old_list = $this->fetch_header_element(1,$msg_num,'Date');
|
|
$field_list = $this->convert_date_array($old_list);
|
|
if ($this->debug >= 2) { echo 'pop3: sort: field_list: '.serialize($field_list).'<br><br>'; }
|
|
break;
|
|
case SORTARRIVAL:
|
|
if ($this->debug >= 1) { echo 'pop3: sort: case SORTARRIVAL<br>'; }
|
|
// TEST
|
|
if (!$this->msg2socket('LIST',"^\+ok",&$response))
|
|
{
|
|
$this->error();
|
|
}
|
|
$response = $this->read_port_glob('.');
|
|
// expected array should NOT start at element 0, instead start it at element 1
|
|
$field_list = $this->glob_to_array($response, False, ' ',True,1);
|
|
if ($this->debug >= 2) { echo 'pop3: sort: field_list: '.serialize($field_list).'<br><br><br>'; }
|
|
break;
|
|
case SORTFROM:
|
|
if ($this->debug >= 1) { echo 'pop3: sort: case SORTFROM<br>'; }
|
|
$field_list = $this->fetch_header_element(1,$msg_num,'From');
|
|
break;
|
|
case SORTSUBJECT:
|
|
if ($this->debug >= 1) { echo 'pop3: sort: case SORTSUBJECT<br>'; }
|
|
$field_list = $this->fetch_header_element(1,$msg_num,'Subject');
|
|
break;
|
|
case SORTTO:
|
|
if ($this->debug >= 1) { echo 'pop3: sort: case SORTTO<br>'; }
|
|
$field_list = $this->fetch_header_element(1,$msg_num,'To');
|
|
break;
|
|
case SORTCC:
|
|
if ($this->debug >= 1) { echo 'pop3: sort: case SORTCC<br>'; }
|
|
$field_list = $this->fetch_header_element(1,$msg_num,'cc');
|
|
break;
|
|
case SORTSIZE:
|
|
if ($this->debug >= 1) { echo 'pop3: sort: case SORTSIZE<br>'; }
|
|
$field_list = $this->fetch_header_element(1,$msg_num,'Size');
|
|
break;
|
|
}
|
|
@reset($field_list);
|
|
if($criteria == SORTSUBJECT)
|
|
{
|
|
if(!$reverse)
|
|
{
|
|
uasort($field_list,array($this,"ssort_ascending"));
|
|
}
|
|
else
|
|
{
|
|
uasort($field_list,array($this,"ssort_decending"));
|
|
}
|
|
}
|
|
elseif(!$reverse)
|
|
{
|
|
asort($field_list);
|
|
}
|
|
else
|
|
{
|
|
arsort($field_list);
|
|
}
|
|
$return_array = Array();
|
|
@reset($field_list);
|
|
$i = 1;
|
|
while(list($key,$value) = each($field_list))
|
|
{
|
|
$return_array[] = $key;
|
|
//echo '('.$i.') Field: <b>'.$value."</b>\t\tMsg Num: <b>".$key."</b><br>\n";
|
|
$i++;
|
|
}
|
|
@reset($return_array);
|
|
if ($this->debug >= 2) { echo 'pop3: sort: return_array: '.serialize($return_array).'<br><br>'; }
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving sort<br>'; }
|
|
return $return_array;
|
|
}
|
|
|
|
function fetch_header_element($start,$stop,$element)
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Entering fetch_header_element<br>'; }
|
|
for($i=$start;$i<=$stop;$i++)
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: fetch_header_element: issue "TOP '.$i.' 0"<br>'; }
|
|
if(!$this->write_port('TOP '.$i.' 0'))
|
|
{
|
|
$this->error();
|
|
}
|
|
$this->read_and_load('.');
|
|
if($this->header[$element])
|
|
{
|
|
$field_element[$i] = $this->phpGW_quoted_printable_decode2($this->header[$element]);
|
|
//echo $field_element[$i].' = '.$this->phpGW_quoted_printable_decode2($this->header[$element])."<br>\n";
|
|
if ($this->debug >= 1) { echo 'pop3: fetch_header_element: field_element['.$i.']: '.$field_element[$i].'<br>'; }
|
|
}
|
|
else
|
|
{
|
|
$field_element[$i] = $this->phpGW_quoted_printable_decode2($this->header[strtoupper($element)]);
|
|
//echo $field_element[$i].' = '.$this->phpGW_quoted_printable_decode2($this->header[strtoupper($element)])."<br>\n";
|
|
if ($this->debug >= 1) { echo 'pop3: fetch_header_element: field_element['.$i.']: '.$field_element[$i].'<br>'; }
|
|
}
|
|
|
|
}
|
|
if ($this->debug >= 1) { echo 'pop3: fetch_header_element: field_element: '.serialize($field_element).'<br><br><br>'; }
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving fetch_header_element<br>'; }
|
|
return $field_element;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Message Structural Information
|
|
*
|
|
\**************************************************************************/
|
|
/*!
|
|
@function fetchstructure
|
|
@abstract implements IMAP_FETCHSTRUCTURE
|
|
@param $stream_notused : socket class handles stream reference internally
|
|
@param $msg_num : integer
|
|
@param $flags : integer - FT_UID (not implimented)
|
|
@result returns an instance of Class "msg_structure" is sucessful, False if error
|
|
@discussion basiclly a replacement for PHP's c-client logic which is missing if IMAP is not builtin
|
|
@author Angles, (some sub-parts by Skeeter, Itzchak Rehberg, Joseph Engo)
|
|
@access public
|
|
*/
|
|
function fetchstructure($stream_notused,$msg_num,$flags="")
|
|
{
|
|
// outer control structure for the multi-pass functions
|
|
if ($this->debug >= 1) { echo 'pop3: Entering fetchstructure<br>'; }
|
|
|
|
// do we have a cached fetchstructure ?
|
|
if (($this->msg_structure != '')
|
|
&& ((int)$this->msg_structure_msgnum == (int)($msg_num)))
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: fetchstructure: using cached msg_structure data<br>'; }
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving fetchstructure<br>'; }
|
|
return $this->msg_structure;
|
|
}
|
|
// NO cached fetchstructure data - so make it
|
|
// this will fill $this->msg_structure *TopLevel* only
|
|
if ($this->fill_toplevel_fetchstructure($stream_notused,$msg_num,$flags) == False)
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving fetchstructure with Error from Toplevel<br>'; }
|
|
return False;
|
|
}
|
|
// by now we have these created and stored (cached)
|
|
// $this->header_array
|
|
// $this->header_array_msgnum
|
|
// $this->body_array
|
|
// $this->body_array_msgnum
|
|
// $this->msg_structure (PARTIAL - INCOMPLETE, completed below)
|
|
// $this->msg_structure_msgnum
|
|
|
|
/*
|
|
// --- Create Sub-Parts FetchStructure Data (if necessary) ---
|
|
// NOTE: param to create_embeded_fetchstructure is a REFERENCE
|
|
$this->create_embeded_fetchstructure(&$this->msg_structure);
|
|
// TEST: attempt 3rd level MIME discovery
|
|
$level_3_loops = count($this->msg_structure->parts);
|
|
for ($i=0; $i < $level_3_loops ;$i++)
|
|
{
|
|
if (count($this->msg_structure->parts[$i]) > 0)
|
|
{
|
|
// grap 3rd level embedded data (if any)
|
|
if ($this->debug >= 2) { echo 'pop3: fetchstructure: attempting ['.$i.'] 3rd level parts embedded discovery<br>'; }
|
|
// --- Create 3rd Level Sub-Parts FetchStructure Data (if necessary) ---
|
|
// NOTE: param to create_embeded_fetchstructure is a REFERENCE
|
|
$this->create_embeded_fetchstructure(&$this->msg_structure->parts[$i]);
|
|
}
|
|
else
|
|
{
|
|
if ($this->debug >= 2) { echo 'pop3: fetchstructure: this ['.$i.'] 3rd level part is empty<br>'; }
|
|
}
|
|
}
|
|
*/
|
|
|
|
// --- Create Sub-Parts FetchStructure Data (if necessary) ---
|
|
// first call to $this->create_embeded_fetchstructure fills $this->msg_structure->parts IF there are any subparts
|
|
// that is the 1st level of subparts if they exist, then we know we need to discover those subparts
|
|
// if we have an "old school" very simple email, there will be NO 1st level of subparts
|
|
// in that case the only body that exists is considered part #1
|
|
// NOTE: param to create_embeded_fetchstructure is a REFERENCE
|
|
$this->create_embeded_fetchstructure(&$this->msg_structure);
|
|
|
|
// if there are subparts, we need to discover the details of those parts now
|
|
// FOUR PASS ANALYSIS
|
|
if (isset($this->msg_structure->parts))
|
|
{
|
|
for ($lev_1=0; $lev_1 < count($this->msg_structure->parts) ;$lev_1++)
|
|
{
|
|
// grap 1st level embedded data (if any)
|
|
if ($this->debug >= 2) { echo '<br>***<br>* * * * * * * * *<br>pop3: fetchstructure: attempting this->msg_structure->parts['.$lev_1.'] of ['.(string)(count($this->msg_structure->parts)-1).'] embedded parts discovery * * * * *<br>'; }
|
|
// Create Sub-Parts FetchStructure Data (if necessary) ---
|
|
// NOTE: param to create_embeded_fetchstructure is a REFERENCE
|
|
$this->create_embeded_fetchstructure(&$this->msg_structure->parts[$lev_1]);
|
|
|
|
// go deeper
|
|
if (isset($this->msg_structure->parts[$lev_1]->parts))
|
|
{
|
|
for ($lev_2=0; $lev_2 < count($this->msg_structure->parts[$lev_1]->parts) ;$lev_2++)
|
|
{
|
|
// grap 2nd level embedded data (if any)
|
|
if ($this->debug >= 2) { echo '<br>***<br>* * * * * * * * *<br>pop3: fetchstructure: attempting this->msg_structure->parts['.$lev_1.']->parts['.$lev_2.'] of ['.(string)(count($this->msg_structure->parts[$lev_1]->parts)-1).'] embedded parts discovery * * * * *<br>'; }
|
|
// Create Sub-Parts FetchStructure Data (if necessary) ---
|
|
// NOTE: param to create_embeded_fetchstructure is a REFERENCE
|
|
$this->create_embeded_fetchstructure(&$this->msg_structure->parts[$lev_1]->parts[$lev_2]);
|
|
|
|
// go deeper
|
|
if (isset($this->msg_structure->parts[$lev_1]->parts[$lev_2]->parts))
|
|
{
|
|
for ($lev_3=0; $lev_3 < count($this->msg_structure->parts[$lev_1]->parts[$lev_2]->parts) ;$lev_3++)
|
|
{
|
|
// grap 3rd level embedded data (if any)
|
|
if ($this->debug >= 2) { echo '<br>***<br>* * * * * * * * *<br>pop3: fetchstructure: attempting this->msg_structure->parts['.$lev_1.']->parts['.$lev_2.']->parts['.$lev_3.'] of ['.(string)(count($this->msg_structure->parts[$lev_1]->parts[$lev_2]->parts)-1).'] embedded parts discovery * * * * *<br>'; }
|
|
// Create 3rd Level Sub-Parts FetchStructure Data (if necessary) ---
|
|
// NOTE: param to create_embeded_fetchstructure is a REFERENCE
|
|
$this->create_embeded_fetchstructure(&$this->msg_structure->parts[$lev_1]->parts[$lev_2]->parts[$lev_3]);
|
|
|
|
// go deeper
|
|
if (isset($this->msg_structure->parts[$lev_1]->parts[$lev_2]->parts[$lev_3]->parts))
|
|
{
|
|
for ($lev_4=0; $lev_4 < count($this->msg_structure->parts[$lev_1]->parts[$lev_2]->parts[$lev_3]->parts) ;$lev_4++)
|
|
{
|
|
// grap 3rd level embedded data (if any)
|
|
if ($this->debug >= 2) { echo '<br>***<br>* * * * * * * * *<br>pop3: fetchstructure: attempting this->msg_structure->parts['.$lev_1.']->parts['.$lev_2.']->parts['.$lev_3.']->parts['.$lev_4.'] of ['.(string)(count($this->msg_structure->parts[$lev_1]->parts[$lev_2]->parts[$lev_3]->parts)-1).'] embedded parts discovery * * * * *<br>'; }
|
|
// Create Sub-Parts FetchStructure Data (if necessary) ---
|
|
// NOTE: param to create_embeded_fetchstructure is a REFERENCE
|
|
$this->create_embeded_fetchstructure(&$this->msg_structure->parts[$lev_1]->parts[$lev_2]->parts[$lev_3]->parts[$lev_4]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($this->debug >= 2) { echo '<br>***<br>pop3: fetchstructure: Traversal SKIP FOUTRH PASS level parts NOT SET<br>'; }
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($this->debug >= 2) { echo '<br>***<br>pop3: fetchstructure: Traversal SKIP THIRD PASS level parts NOT SET<br>'; }
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($this->debug >= 2) { echo '<br>***<br>pop3: fetchstructure: Traversal SKIP SECOND PASS level parts NOT SET<br>'; }
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ($this->debug >= 2) { echo 'pop3: fetchstructure: Traversal SKIP FIRST PARTS level parts NOT SET<br>'; }
|
|
}
|
|
|
|
if ($this->debug >= 2) { echo '<br>***<br>pop3: fetchstructure: * * * * * * Traversal OVER * * * * * * * * * * <br>'; }
|
|
|
|
if ($this->debug >= 2)
|
|
{
|
|
echo '<br>dumping fetchstructure FINAL data: <br>';
|
|
var_dump($this->msg_structure);
|
|
echo '<br><br><br>';
|
|
}
|
|
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving fetchstructure<br>'; }
|
|
return $this->msg_structure;
|
|
}
|
|
|
|
/*!
|
|
@function fill_toplevel_fetchstructure
|
|
@abstract HELPER function for fetchstructure / IMAP_FETCHSTRUCTURE
|
|
@param $stream_notused : socket class handles stream reference internally
|
|
@param $msg_num : integer
|
|
@param $flags : integer - FT_UID (not implimented)
|
|
@result returns an instance of Class "msg_structure" is sucessful, False if error
|
|
@discussion basiclly a replacement for PHP's c-client logic which is missing if IMAP is not builtin
|
|
@author Angles, (some sub-parts by Skeeter, Itzchak Rehberg, Joseph Engo)
|
|
@access private
|
|
*/
|
|
function fill_toplevel_fetchstructure($stream_notused,$msg_num,$flags="")
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Entering fill_toplevel_fetchstructure<br>'; }
|
|
|
|
// --- Header Array ---
|
|
$header_array = $this->get_header_array($stream_notused,$msg_num,$flags);
|
|
// --- Body Array ---
|
|
// do we have a cached body_array ?
|
|
if ((count($this->body_array) > 0)
|
|
&& ((int)$this->body_array_msgnum == (int)($msg_num)))
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: fill_toplevel_fetchstructure: using cached body_array data<br>'; }
|
|
$body_array = $this->body_array;
|
|
}
|
|
else
|
|
{
|
|
// NO cached data, get it
|
|
// calling get_body automatically fills $this->body_array
|
|
$this->get_body($stream_notused,$msg_num,$flags='',False);
|
|
$body_array = $this->body_array;
|
|
|
|
if ($this->debug >= 2)
|
|
{
|
|
echo 'pop3: fill_toplevel_fetchstructure: this->body_array DUMP<pre>';
|
|
for ($i=0; $i < count($this->body_array) ;$i++)
|
|
{
|
|
echo '+['.$i.'] '.htmlspecialchars($this->body_array[$i])."\r\n";
|
|
}
|
|
echo '</pre><br><br>';
|
|
}
|
|
}
|
|
if ($this->debug >= 2)
|
|
{
|
|
echo 'pop3: fill_toplevel_fetchstructure header_array iteration:<br>';
|
|
for($i=0;$i < count($header_array);$i++)
|
|
{
|
|
echo '+'.htmlspecialchars($header_array[$i]).'<br>';
|
|
}
|
|
}
|
|
if (!$header_array)
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving fill_toplevel_fetchstructure with error<br>'; }
|
|
return False;
|
|
}
|
|
|
|
// --- Create Class Base Fetchstructure Object ---
|
|
$this->msg_structure_msgnum = (int)$msg_num;
|
|
$this->msg_structure = nil;
|
|
$this->msg_structure = new msg_structure;
|
|
$this->msg_structure->custom['top_level'] = True;
|
|
$this->msg_structure->custom['parent_cookie'] = ''; // no parent at top level
|
|
$this->msg_structure->custom['detect_state'] = 'out'; // not doing multi part detection on this yet
|
|
// --- Fill Top Level Fetchstructure ---
|
|
// NOTE: first param to sub_get_structure is a REFERENCE
|
|
$this->sub_get_structure(&$this->msg_structure,$header_array);
|
|
|
|
// --- Fill Any Missing Necessary Data ---
|
|
// --Bytes-- top level msg Size (bytes) is obtainable from the server
|
|
if (!$this->msg2socket('LIST '.$msg_num,"^\+ok",&$response))
|
|
{
|
|
$this->error();
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving fill_toplevel_fetchstructure with error<br>'; }
|
|
return False;
|
|
}
|
|
$list_response = explode(' ',$response);
|
|
$this->msg_structure->bytes = (int)trim($list_response[2]);
|
|
// --Lines-- php's fetchstructure seems to always include number of lines in it's msg_structure data
|
|
// whether or not that data is present in the headers
|
|
// top level # of lines is the # of lines in the entire body, we do not care about subparts here
|
|
if ((!isset($this->msg_structure->lines))
|
|
|| ((string)$this->msg_structure->lines == ''))
|
|
{
|
|
// earlier in this function we filled $this->body_array
|
|
// the count of that array is the number of lines in the messages full body
|
|
$this->msg_structure->lines = count($this->body_array);
|
|
}
|
|
// make sure some necessary information is present, use RFC defaults if necessary
|
|
//if ((!isset($this->msg_structure->type))
|
|
//|| ((string)$this->msg_structure->type == ''))
|
|
//{
|
|
// // default type - RFC says is Text (unless you are dealing with an attachment)
|
|
// $this->msg_structure->type = $this->default_type(True);
|
|
//}
|
|
//if ((!isset($this->msg_structure->ifsubtype))
|
|
//|| ($this->msg_structure->ifsubtype != True))
|
|
//{
|
|
// // if no type we should NOT have a subtype, or else something is wrong
|
|
// $this->msg_structure->subtype = $this->default_subtype($this->msg_structure->type);
|
|
// $this->msg_structure->ifsubtype = True;
|
|
//}
|
|
if ((!isset($this->msg_structure->encoding))
|
|
|| ((string)$this->msg_structure->encoding == ''))
|
|
{
|
|
$this->msg_structure->encoding = $this->default_encoding();
|
|
}
|
|
// unset any elements that have not been filled
|
|
// NOTE: param to unset_unfilled_fetchstructure is a REFERENCE
|
|
$this->unset_unfilled_fetchstructure(&$this->msg_structure);
|
|
if ($this->debug >= 2)
|
|
{
|
|
echo '<br>dumping fill_toplevel_fetchstructure TOP-LEVEL data: <br>';
|
|
var_dump($this->msg_structure);
|
|
echo '<br><br><br>';
|
|
}
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving fill_toplevel_fetchstructure<br>'; }
|
|
return True;
|
|
}
|
|
|
|
/*!
|
|
@function create_embeded_fetchstructure
|
|
@abstract HELPER function for fetchstructure / IMAP_FETCHSTRUCTURE
|
|
@param $info : **REFERENCE** to a class "msg_structure" object
|
|
@result NONE : this function DIRECTLY manipulates the referenced object
|
|
@discussion as implemented, reference is to some part of class var $this->msg_structure
|
|
@author Angles
|
|
@access private
|
|
*/
|
|
function create_embeded_fetchstructure($info)
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Entering create_embeded_fetchstructure<br>'; }
|
|
// --- Do We Have SubParts To Discover ---
|
|
|
|
// Test 1: Detect Boundary Paramaters
|
|
// initialize boundary holder
|
|
$info->custom['my_cookie'] = '';
|
|
if ($info->ifparameters)
|
|
{
|
|
// if we have a boundary paramater, then we have a multi-part message
|
|
for ($x=0; $x < count($info->parameters) ;$x++)
|
|
{
|
|
$these_params = $info->parameters[$x];
|
|
if (strtolower($these_params->attribute) == 'boundary')
|
|
{
|
|
// store it in custom["my_cookie"] for easy access
|
|
$info->custom['my_cookie'] = $these_params->value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// --- Handle Multi-Part MIME ---
|
|
if (($info->custom['my_cookie'] != '')
|
|
&& (count($info->parts) == 0))
|
|
{
|
|
// Boundry Based Multi-Part MIME In Need Of Discovered
|
|
if ($this->debug >= 1) { echo 'pop3: create_embeded_fetchstructure: Discovery Needed for boundary param: '.$info->custom['my_cookie'].'<br>'; }
|
|
if ($this->debug >= 2) { echo 'pop3: create_embeded_fetchstructure: begin "mime loop", iterate thru body_array<br>'; }
|
|
// look for any parts using this boundary/cookie
|
|
for ($x=0; $x < count($this->body_array) ;$x++)
|
|
{
|
|
// search line by line thru the body
|
|
$body_line = $this->body_array[$x];
|
|
if ($this->debug >= 2) { echo 'pop3: create_embeded_fetchstructure: mime loop ['.$x.']: '.htmlspecialchars($body_line).'<br>'; }
|
|
if ((strstr($body_line,'--'.$info->custom['my_cookie']))
|
|
&& (strpos($body_line,'--'.$info->custom['my_cookie']) == 0)
|
|
// but NOT the final boundary
|
|
&& (!strstr($body_line,'--'.$info->custom['my_cookie'].'--')))
|
|
{
|
|
// we found a body part
|
|
|
|
// BEGINNING of a new part is ALSO the ENDING of a prevoius part
|
|
// if we were in the state of "IN" on that prevoius part (if any previous part exists)
|
|
$cur_part_idx = count($info->parts) - 1;
|
|
if ((isset($info->parts[$cur_part_idx]))
|
|
&& ($info->parts[$cur_part_idx]->custom['detect_state'] == 'in'))
|
|
{
|
|
// we were already "in" so we found ENDING data
|
|
// for the previous part, (as well as BEGINING data for the next part)
|
|
// --Bytes-- we have a running total of byte size, but in testing against UWash, I was over by 2 bytes, so fix that
|
|
$info->parts[$cur_part_idx]->bytes = $info->parts[$cur_part_idx]->bytes - 2;
|
|
$info->parts[$cur_part_idx]->custom['part_end'] = $x-1;
|
|
// --Lines-- we know beginning line and ending line, so calculate # lines for this part
|
|
$info->parts[$cur_part_idx]->lines = (int)$info->parts[$cur_part_idx]->custom['part_end'] - (int)$info->parts[$cur_part_idx]->custom['part_start'];
|
|
if ($this->debug >= 2) { echo 'pop3: create_embeded_fetchstructure: mime loop: current part end at ['.(string)($x-1).'] byte cumula: ['.$info->parts[$cur_part_idx]->bytes.'] lines: ['.$info->parts[$cur_part_idx]->lines.']<br>'; }
|
|
// this individual part has completed discovery, it os now "OUT"
|
|
$info->parts[$cur_part_idx]->custom['detect_state'] = 'out';
|
|
// we are DONE with this part for now
|
|
// unset any unfilled elements
|
|
// NOTE: param to unset_unfilled_fetchstructure is a REFERENCE
|
|
$this->unset_unfilled_fetchstructure(&$info->parts[$cur_part_idx]);
|
|
}
|
|
// so now deal with this NEW part we just discovered
|
|
if ($this->debug >= 2) { echo 'pop3: create_embeded_fetchstructure: mime loop: begin part discovery<br>'; }
|
|
// Create New Sub Part Object
|
|
$new_part_idx = count($info->parts);
|
|
$info->parts[$new_part_idx] = new msg_structure;
|
|
$info->parts[$new_part_idx]->bytes = 0;
|
|
$info->parts[$new_part_idx]->custom['top_level'] = False;
|
|
$info->parts[$new_part_idx]->custom['parent_cookie'] = $info->custom['my_cookie'];
|
|
// state info: we are now "IN" doing multi part detection on this part
|
|
$info->parts[$new_part_idx]->custom['detect_state'] = 'in';
|
|
// get this part's headers
|
|
// start 1 line after the cookie, and end with the first blank line
|
|
// part header starts next line after the boundary/cookie
|
|
$info->parts[$new_part_idx]->custom['header_start'] = $x+1;
|
|
$part_header_blob = '';
|
|
for ($y=$x+1; $y < count($this->body_array) ;$y++)
|
|
{
|
|
if ($this->body_array[$y] != '')
|
|
{
|
|
// grap this part header line
|
|
$part_header_blob .= $this->body_array[$y]."\r\n";
|
|
if ($this->debug >= 2) { echo 'pop3: create_embeded_fetchstructure: mime loop: part part_header_blob line['.$y.']: '.$this->body_array[$y].'<br>'; }
|
|
}
|
|
else
|
|
{
|
|
// reached end of this part's headers
|
|
// headers actually ended 1 line above this blank line
|
|
$info->parts[$new_part_idx]->custom['header_end'] = (int)($y-1);
|
|
// break out of this sub loop
|
|
break;
|
|
}
|
|
}
|
|
// get rid of that last CRLF
|
|
$part_header_blob = trim($part_header_blob);
|
|
// RFC2822 "unfold" the grabbed header
|
|
// unfold any unfolded headers - using CR_LF_TAB as rfc822 "whitespace"
|
|
$part_header_blob = str_replace("\r\n\t"," ",$part_header_blob);
|
|
// unfold any unfolded headers - using CR_LF_SPACE as rfc822 "whitespace"
|
|
$part_header_blob = str_replace("\r\n "," ",$part_header_blob);
|
|
// make the header blob into an array of strings, one array element per header line, throw away blank lines
|
|
$part_header_array = Array();
|
|
$part_header_array = $this->glob_to_array($part_header_blob, False, '', True);
|
|
if ($this->debug >= 2) { echo 'pop3: create_embeded_fetchstructure: mime loop: part_header_array:'.serialize($part_header_array).'<br>'; }
|
|
// since we just passed the headers, and this is NOT a final boundary
|
|
// this MUST be a start point for the next part
|
|
$info->parts[$new_part_idx]->custom['part_start'] = (int)($y+1);
|
|
// fill the conventional info on this fetchstructure sub-part
|
|
// NOTE: first param to sub_get_structure is a REFERENCE
|
|
$this->sub_get_structure(&$info->parts[$new_part_idx],$part_header_array);
|
|
// ADVANCE INDEX $x TO AFTER WHAT WE'VE ALREADY LOOKED AT
|
|
if ($this->debug >= 2) { echo 'pop3: create_embeded_fetchstructure: mime loop: advance x from ['.$x.'] to ['.$y.']<br>'; }
|
|
$x = $y;
|
|
}
|
|
elseif ((strstr($body_line,'--'.$info->custom['my_cookie'].'--'))
|
|
&& (strpos($body_line,'--'.$info->custom['my_cookie'].'--') == 0))
|
|
{
|
|
// we found the CLOSING BOUNDARY
|
|
$cur_part_idx = count($info->parts) - 1;
|
|
$info->parts[$cur_part_idx]->custom['part_end'] = $x-1;
|
|
// --Bytes-- we have a running total of byte size, but in testing against UWash, I was over by 2 bytes, so fix that
|
|
$info->parts[$cur_part_idx]->bytes = $info->parts[$cur_part_idx]->bytes - 2;
|
|
// --Lines-- we know beginning line and ending line, so calculate # lines for this part
|
|
$info->parts[$cur_part_idx]->lines = $info->parts[$cur_part_idx]->custom['part_end'] - $info->parts[$cur_part_idx]->custom['part_start'];
|
|
$info->parts[$cur_part_idx]->custom['detect_state'] = 'out';
|
|
// we are DONE with this part for now
|
|
if ($this->debug >= 2) { echo 'pop3: create_embeded_fetchstructure: mime loop: final boundary at ['.(string)($x-1).'] byte cumula: ['.$info->parts[$cur_part_idx]->bytes.'] lines: ['.$info->parts[$cur_part_idx]->lines.']<br>'; }
|
|
// unset any unfilled elements
|
|
// NOTE: param to unset_unfilled_fetchstructure is a REFERENCE
|
|
$this->unset_unfilled_fetchstructure(&$info->parts[$cur_part_idx]);
|
|
}
|
|
else
|
|
{
|
|
// running byte size of this part (if any)
|
|
$cur_part_idx = count($info->parts) - 1;
|
|
if ((isset($info->parts[$cur_part_idx]))
|
|
&& ($info->parts[$cur_part_idx]->custom['detect_state'] == 'in'))
|
|
{
|
|
// previous count
|
|
$prev_bytes = $info->parts[$cur_part_idx]->bytes;
|
|
// add new count, +2 for the \r\n that will end the line when we feed it to the client
|
|
$add_bytes = strlen($body_line) + 2;
|
|
$info->parts[$cur_part_idx]->bytes = $prev_bytes + $add_bytes;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// do we have an encapsulated (non-boundry based) Embedded Part
|
|
elseif ( (isset($info->type))
|
|
&& ($info->type == TYPEMESSAGE)
|
|
&& (isset($info->subtype))
|
|
&& (strtolower($info->subtype) == 'rfc822')
|
|
&& (count($info->parts) == 0))
|
|
{
|
|
// Encapsulated "message/rfc822" MIME Part In Need Of Discovered
|
|
if ($this->debug >= 1) { echo 'pop3: create_embeded_fetchstructure: Discovery Needed for Encapsulated "message/rfc822" MIME Part<br>'; }
|
|
$range_start = $info->custom['part_start'];
|
|
$range_end = $info->custom['part_end'];
|
|
// is this range data valid
|
|
if ( (!isset($info->custom['part_start']))
|
|
|| (!isset($info->custom['part_end']))
|
|
|| ($info->custom['part_end'] <= $info->custom['part_start']))
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving create_embeded_fetchstructure with Error in "message/rfc2822" range<br>'; }
|
|
return False;
|
|
}
|
|
|
|
// note that below we will iterate thru this range
|
|
if ($this->debug >= 2) { echo 'pop3: create_embeded_fetchstructure: "mime loop", will iterate thru parents body_array range ['.$range_start.'] to ['.$range_end.']<br>'; }
|
|
|
|
// encapsulated is not that tricky, we must so this
|
|
// 1) Create New Sub Part Object
|
|
$enc_part_idx = count($info->parts);
|
|
$info->parts[$enc_part_idx] = new msg_structure;
|
|
$info->parts[$enc_part_idx]->bytes = 0;
|
|
$info->parts[$enc_part_idx]->custom['top_level'] = False;
|
|
// ??? encapsulated part's parent does not have a boundary ???
|
|
$info->parts[$enc_part_idx]->custom['parent_cookie'] = '';
|
|
|
|
// 2) Get This Part's Headers
|
|
// encapsulated headers begin immediately in the encapsulated part
|
|
$info->parts[$enc_part_idx]->custom['header_start'] = $range_start;
|
|
// encapsulated headers end with the 1st blank line
|
|
$part_header_blob = '';
|
|
for ($y=$range_start; $y < $range_end+1 ;$y++)
|
|
{
|
|
if ($this->body_array[$y] != '')
|
|
{
|
|
// grap this part header line
|
|
$part_header_blob .= $this->body_array[$y]."\r\n";
|
|
if ($this->debug >= 2) { echo 'pop3: enc mime loop: part part_header_blob line['.$y.']: '.htmlspecialchars($this->body_array[$y]).'<br>'; }
|
|
}
|
|
else
|
|
{
|
|
// reached end of this part's headers
|
|
// headers actually ended 1 line above this blank line
|
|
$info->parts[$enc_part_idx]->custom['header_end'] = (int)($y-1);
|
|
// break out of this sub loop
|
|
break;
|
|
}
|
|
}
|
|
// get rid of that last CRLF
|
|
$part_header_blob = trim($part_header_blob);
|
|
// RFC2822 "unfold" the grabbed header
|
|
// unfold any unfolded headers - using CR_LF_TAB as rfc822 "whitespace"
|
|
$part_header_blob = str_replace("\r\n\t"," ",$part_header_blob);
|
|
// unfold any unfolded headers - using CR_LF_SPACE as rfc822 "whitespace"
|
|
$part_header_blob = str_replace("\r\n "," ",$part_header_blob);
|
|
// make the header blob into an array of strings, one array element per header line, throw away blank lines
|
|
$part_header_array = Array();
|
|
$part_header_array = $this->glob_to_array($part_header_blob, False, '', True);
|
|
if ($this->debug >= 2) { echo 'pop3: create_embeded_fetchstructure: enc mime loop: part_header_array:'.serialize($part_header_array).'<br>'; }
|
|
|
|
// 2) Feed these Headers thru "sub_get_structure"
|
|
// fill the conventional info on this fetchstructure sub-part
|
|
// NOTE: first param to sub_get_structure is a REFERENCE
|
|
$this->sub_get_structure(&$info->parts[$enc_part_idx],$part_header_array);
|
|
|
|
// == CONTROVESTIAL DEFAULT UWASH VALUE ASSIGNMENTS ==
|
|
// close study of UWash IMAP indicates the an immediate child message part of a RFC822 package will:
|
|
// (A) SUBTYPE
|
|
// will get a default value of "plain" from UWash imap WHEN NO TYPE was specified for this part
|
|
// I assume if a type was specified then UWash would not do this
|
|
// in fact UWash *may* fill a default subtype if a type IS specified (it's in the UWash code)
|
|
// so I will imitate UWash IMAP and assign a subtype of "plain" when NO type is specified
|
|
if ((!isset($info->parts[$enc_part_idx]->subtype))
|
|
|| ((string)$info->parts[$enc_part_idx]->subtype == ''))
|
|
{
|
|
if ($this->debug >= 2) { echo 'pop3: create_embeded_fetchstructure: enc mime loop: CONTROVERSIAL uwash imitation: adding subtype "plain" to immediate RFC822 child part, none was specified<br>'; }
|
|
$info->parts[$enc_part_idx]->ifsubtype = True;
|
|
$info->parts[$enc_part_idx]->subtype = 'plain';
|
|
}
|
|
// (B) PARAM "charset=US-ASCII"
|
|
// gets added if no charset is specified for this immediate RFC822 child
|
|
// I know it hurts, but I'm just copying UWash !!!
|
|
$found_charset = False;
|
|
for ($ux=0; $ux < count($info->parts[$enc_part_idx]->parameters) ;$ux++)
|
|
{
|
|
if (stristr($info->parts[$enc_part_idx]->parameters[$new_idx]->attribute,'charset'))
|
|
{
|
|
$found_charset = True;
|
|
break;
|
|
}
|
|
}
|
|
// do that crappy adding of charset param if necessary
|
|
if ($found_charset == False)
|
|
{
|
|
if ($this->debug >= 2) { echo 'pop3: create_embeded_fetchstructure: enc mime loop: CONTROVERSIAL uwash imitation: adding param "charset=US-ASCII" to immediate RFC822 child part, none was specified<br>'; }
|
|
$new_idx = count($info->parts[$enc_part_idx]->parameters);
|
|
$info->parts[$enc_part_idx]->parameters[$new_idx] = new msg_params('charset','US-ASCII');
|
|
$info->parts[$enc_part_idx]->ifparameters = true;
|
|
}
|
|
// ends CONTROVESTIAL uwash inmitation code
|
|
|
|
// 3) fill Part Start and Part End
|
|
// encapsulated body STARTS at the first line after the blank line header sep above
|
|
$info->parts[$enc_part_idx]->custom['part_start'] = (int)($y+1);
|
|
// encapsulated body ENDS at the end of the partnts range
|
|
$info->parts[$enc_part_idx]->custom['part_end'] = $range_end;
|
|
|
|
// 4) calculate byte size and # of lines of the content within this parts start and end
|
|
$my_start = $info->parts[$enc_part_idx]->custom['part_start'];
|
|
$my_end = $info->parts[$enc_part_idx]->custom['part_end'];
|
|
if ($this->debug >= 2) { echo 'pop3: create_embeded_fetchstructure: enc mime loop: this body range ['.$my_start.'] to ['.$my_end.']<br>'; }
|
|
for ($x=$my_start; $x < $my_end+1 ;$x++)
|
|
{
|
|
// running byte size of this part
|
|
$body_line = $this->body_array[$x];
|
|
if ($this->debug >= 2) { echo 'pop3: encap mime size loop ['.$x.']: '.htmlspecialchars($body_line).'<br>'; }
|
|
// prevoius count
|
|
$prev_bytes = $info->parts[$enc_part_idx]->bytes;
|
|
// add new count, +2 for the \r\n that will end the line when we feed it to the client
|
|
$add_bytes = strlen($body_line) + 2;
|
|
$info->parts[$enc_part_idx]->bytes = $prev_bytes + $add_bytes;
|
|
}
|
|
// --Bytes-- we made a running total of byte size, but in testing against UWash, I was over by 2 bytes, so fix that
|
|
$info->parts[$enc_part_idx]->bytes = $info->parts[$enc_part_idx]->bytes - 2;
|
|
// --Lines-- we know beginning line and ending line, so calculate # lines for this part
|
|
$info->parts[$enc_part_idx]->lines = $my_end - $my_start;
|
|
// we're done with the loop so the bytes have been calculated in that loop
|
|
if ($this->debug >= 2) { echo 'pop3: create_embeded_fetchstructure: this part range byte size ['.$info->parts[$enc_part_idx]->bytes.'] lines: ['.$info->parts[$enc_part_idx]->lines.']<br>'; }
|
|
}
|
|
// no embedded parts, why not?
|
|
elseif ( (isset($info->type))
|
|
&& ($info->type == TYPEMESSAGE)
|
|
&& (isset($info->subtype))
|
|
&& (strtolower($info->subtype) == 'rfc822')
|
|
&& (count($info->parts) == 0))
|
|
{
|
|
// do NOTHING - this level has ALREADY been filled
|
|
if ($this->debug >= 2) { echo 'pop3: create_embeded_fetchstructure: feed info encapsulated "message/rfc822" ALREADY filled<br>'; }
|
|
return False;
|
|
}
|
|
elseif ($info->custom['my_cookie'] == '')
|
|
{
|
|
// do NOTHING - this is NOT multipart
|
|
if ($this->debug >= 2) { echo 'pop3: create_embeded_fetchstructure: feed info not multipart<br>'; }
|
|
if ($this->debug >= 2)
|
|
{
|
|
echo 'pop3: create_embeded_fetchstructure: feed info not multipart DUMP EXAMINE:<br>';
|
|
var_dump($info);
|
|
echo '<br><br>';
|
|
}
|
|
return False;
|
|
}
|
|
elseif (($info->custom['my_cookie'] != '')
|
|
&& (count($info->parts) > 0))
|
|
{
|
|
// do NOTHING - this level has ALREADY been filled
|
|
if ($this->debug >= 2) { echo 'pop3: create_embeded_fetchstructure: feed info multipart ALREADY filled<br>'; }
|
|
return False;
|
|
}
|
|
else
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: create_embeded_fetchstructure: * * no mans land * *<br>'; }
|
|
}
|
|
//if ($this->debug >= 2)
|
|
//{
|
|
// echo '<br>dumping create_embeded_fetchstructure return info: <br>';
|
|
// var_dump($info);
|
|
// echo '<br><br>';
|
|
//}
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving create_embeded_fetchstructure<br>'; }
|
|
return True;
|
|
}
|
|
|
|
/*!
|
|
@function sub_get_structure
|
|
@abstract HELPER function for fetchstructure / IMAP_FETCHSTRUCTURE
|
|
@param $info : **REFERENCE** to a class "msg_structure" object
|
|
@param $header_array : array of headers to process
|
|
@result NONE : this function DIRECTLY manipulates the referenced object
|
|
@discussion as implemented, reference is to some part of class var $this->msg_structure
|
|
@author Angles, Itzchak Rehberg, Joseph Engo
|
|
@access private
|
|
*/
|
|
function sub_get_structure($info,$header_array)
|
|
{
|
|
// set debug flag
|
|
if ($this->debug >= 2)
|
|
{
|
|
$debug_mime = True;
|
|
}
|
|
else
|
|
{
|
|
$debug_mime = False;
|
|
}
|
|
|
|
if ($this->debug >= 1) { echo 'pop3: Entering sub_get_structure<br>'; }
|
|
/*
|
|
// initialize the structure
|
|
$info->custom['top_level'] = $extra_args['top_level'];
|
|
$info->custom['detect_state'] = 'in'; // = 'out';
|
|
$info->custom['parent_cookie'] = '';
|
|
$info->custom['my_cookie'] = ''; // for recursive sub-parts
|
|
$info->custom['my_header_array'] = '';
|
|
$info->custom['header_start'] = ''; // this parts MIME headers start index in body array
|
|
$info->custom['header_end'] = ''; // this parts MIME headers ending index in body array
|
|
$info->custom['part_start'] = $extra_args['part_start'];
|
|
$info->custom['part_end'] = ''; // unknown ending point at this stage, we just got past it's headers
|
|
*/
|
|
// FILL THE DATA
|
|
for ($i=0; $i < count($header_array) ;$i++)
|
|
{
|
|
$pos = strpos($header_array[$i],' ');
|
|
//if ($debug_mime) { echo 'header_array['.$i.']: '.$header_array[$i].'<br>'; }
|
|
if (is_int($pos) && ($pos==0))
|
|
{
|
|
continue;
|
|
}
|
|
$keyword = strtolower(substr($header_array[$i],0,$pos));
|
|
$content = trim(substr($header_array[$i],$pos+1));
|
|
if ($debug_mime)
|
|
{
|
|
//echo 'pos: '.$pos.'<br>';
|
|
echo 'pop3: sub_get_structure: keyword: ['.htmlspecialchars($keyword).']'
|
|
.' content: ['.htmlspecialchars($content).']<br>';
|
|
}
|
|
switch ($keyword)
|
|
{
|
|
case 'content-type:' :
|
|
// this will fill type and (hopefully) subtype
|
|
// NOTE: first param to parse_type_subtype is a REFERENCE
|
|
$this->parse_type_subtype(&$info,$content);
|
|
// ALSO, typically Paramaters are on this line as well
|
|
$pos_param = strpos($content,';');
|
|
if ($pos_param > 0)
|
|
{
|
|
if ($this->debug >= 2) { echo 'pop3: sub_get_structure: apparent params exist in content ['.$content.']<br>'; }
|
|
// feed the whole param line into this function
|
|
$content = substr($content,$pos_param+1);
|
|
if ($this->debug >= 2) { echo 'pop3: sub_get_structure: calling parse_msg_params, feeding content ['.$content.']<br>'; }
|
|
// False = this is NOT a disposition param, this is the more common regular param
|
|
// NOTE: first param to parse_msg_params is a REFERENCE
|
|
$this->parse_msg_params(&$info,$content,False);
|
|
}
|
|
break;
|
|
case 'content-transfer-encoding:' :
|
|
$info->encoding = $this->encoding_str_to_int($content);
|
|
break;
|
|
case 'content-description:' :
|
|
$info->description = $content;
|
|
//$i = $this->more_info($msg_part,$i,&$info,"description");
|
|
$info->ifdescription = true;
|
|
break;
|
|
case 'content-disposition:' :
|
|
// disposition MAY have Paramaters on this line as well
|
|
$pos_param = strpos($content,';');
|
|
if ($pos_param > 0)
|
|
{
|
|
$content = substr($content,0,$pos_param);
|
|
}
|
|
$info->disposition = $content;
|
|
$info->ifdisposition = True;
|
|
// parse paramaters if any
|
|
if ($pos_param > 0)
|
|
{
|
|
// feed the whole param line into this function
|
|
$content = substr($content,$pos_param+1);
|
|
// NOTE: first param to parse_msg_params is a REFERENCE
|
|
$this->parse_msg_params(&$info,$content,False);
|
|
}
|
|
break;
|
|
case 'content-identifier:' :
|
|
case 'content-id:' :
|
|
case 'message-id:' :
|
|
if ((strstr($content, '<'))
|
|
&& (strstr($content, '>')))
|
|
{
|
|
$content = str_replace('<','',$content);
|
|
$content = str_replace('>','',$content);
|
|
}
|
|
//$i = $this->more_info($msg_part,$i,&$info,"id");
|
|
$info->id = $content;
|
|
$info->ifid = true;
|
|
break;
|
|
case 'content-length:' :
|
|
$info->bytes = (int)$content;
|
|
break;
|
|
case 'content-disposition:' :
|
|
$info->disposition = $content;
|
|
//$i = $this->more_info($msg_part,$i,&$info,"disposition");
|
|
$info->ifdisposition = true;
|
|
break;
|
|
case 'lines:' :
|
|
$info->lines = (int)$content;
|
|
break;
|
|
/*
|
|
case 'mime-version:' :
|
|
$new_idx = count($info->parameters);
|
|
$info->parameters[$new_idx] = new msg_params("Mime-Version",$content);
|
|
$info->ifparameters = true;
|
|
break;
|
|
*/
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($this->debug >= 2)
|
|
{
|
|
echo 'pop3: sub_get_structure: info->encoding ['.(string)$info->encoding.'] (if empty here it will get a default value later)<br>';
|
|
}
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving sub_get_structure<br>'; }
|
|
return $info;
|
|
}
|
|
|
|
/*!
|
|
@function unset_unfilled_fetchstructure
|
|
@abstract HELPER function for fetchstructure / IMAP_FETCHSTRUCTURE
|
|
@param $info : **REFERENCE** to a class "msg_structure" object
|
|
@result NONE : this function DIRECTLY manipulates the referenced object
|
|
@discussion as implemented, reference is to some part of class var $this->msg_structure
|
|
unsets any unfilled elements of the referenced part in the fetchstructure object
|
|
to mimic PHP's return structure
|
|
@author Angles
|
|
@access private
|
|
*/
|
|
function unset_unfilled_fetchstructure($info)
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Entering unset_unfilled_fetchstructure<br>'; }
|
|
// unset any unfilled elements, ALWAYS leave parts and custom
|
|
if ((string)$info->type == '')
|
|
{
|
|
$info->type = NIL;
|
|
unset($info->type);
|
|
}
|
|
if ((string)$info->encoding == '')
|
|
{
|
|
$info->encoding = NIL;
|
|
unset($info->encoding);
|
|
}
|
|
//$info->ifsubtype = False;
|
|
if ((string)$info->subtype == '')
|
|
{
|
|
$info->subtype = NIL;
|
|
unset($info->subtype);
|
|
}
|
|
//$info->ifdescription = False;
|
|
if ((string)$info->description == '')
|
|
{
|
|
$info->description = NIL;
|
|
unset($info->description);
|
|
}
|
|
//$info->ifid = False;
|
|
if ((string)$info->id == '')
|
|
{
|
|
$info->id = NIL;
|
|
unset($info->id);
|
|
}
|
|
if ((string)$info->lines == '')
|
|
{
|
|
$info->lines = NIL;
|
|
unset($info->lines);
|
|
}
|
|
if ((string)$info->bytes == '')
|
|
{
|
|
$info->bytes = NIL;
|
|
unset($info->bytes);
|
|
}
|
|
//$info->ifdisposition = False;
|
|
if ((string)$info->disposition == '')
|
|
{
|
|
$info->disposition = NIL;
|
|
unset($info->disposition);
|
|
}
|
|
//$info->ifdparameters = False;
|
|
if (count($info->dparameters) == 0)
|
|
{
|
|
$info->dparameters = NIL;
|
|
unset($info->dparameters);
|
|
}
|
|
//$info->ifparameters = False;
|
|
if (count($info->parameters) == 0)
|
|
{
|
|
$info->parameters = NIL;
|
|
unset($info->parameters);
|
|
}
|
|
//$info->custom = array();
|
|
//$info->parts = array();
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving unset_unfilled_fetchstructure<br>'; }
|
|
}
|
|
|
|
/*!
|
|
@function parse_type_subtype
|
|
@abstract HELPER function for sub_get_structure / IMAP_FETCHSTRUCTURE
|
|
@param $info : **REFERENCE** to a class "msg_structure" object
|
|
@param $content : the text associated with the "content-type:" header
|
|
@result NONE : this function DIRECTLY manipulates the referenced object
|
|
@discussion as implemented, reference is to some part of class var $this->msg_structure
|
|
parses "content-type:" header into fetchstructure data ->type and ->subtype
|
|
@author Angles, Itzchak Rehberg, Joseph Engo
|
|
@access private
|
|
*/
|
|
function parse_type_subtype($info,$content)
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Entering parse_type_subtype<br>'; }
|
|
// used by pop_fetchstructure only
|
|
// get rid of any other params that might be here
|
|
$pos = strpos($content,';');
|
|
if ($pos > 0)
|
|
{
|
|
$content = substr($content,0,$pos);
|
|
}
|
|
// split type from subtype
|
|
$pos = strpos($content,'/');
|
|
if ($pos > 0)
|
|
{
|
|
$prim_type = strtolower(substr($content,0,$pos));
|
|
$info->subtype = strtolower(substr($content,$pos+1));
|
|
$info->ifsubtype = True;
|
|
}
|
|
else
|
|
{
|
|
$prim_type = strtolower($content);
|
|
}
|
|
if ($this->debug >= 2) { echo 'pop3: parse_type_subtype: prim_type: '.$prim_type.'<br>'; }
|
|
$info->type = $this->type_str_to_int($prim_type);
|
|
if ($info->ifsubtype == False)
|
|
{
|
|
// use RFC default for subtype
|
|
$info->subtype = $this->default_subtype($info->type);
|
|
$info->ifsubtype = True;
|
|
}
|
|
if ($this->debug >= 2)
|
|
{
|
|
echo 'pop3: parse_type_subtype: info->type ['.$info->type.'] aka "'.$this->type_int_to_str($info->type).'"<br>';
|
|
echo 'pop3: parse_type_subtype: info->ifsubtype ['.$info->ifsubtype.']<br>';
|
|
echo 'pop3: parse_type_subtype: info->subtype "'.$info->subtype.'"<br>';
|
|
}
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving parse_type_subtype<br>'; }
|
|
}
|
|
|
|
/*!
|
|
@function parse_msg_params
|
|
@abstract HELPER function for sub_get_structure / IMAP_FETCHSTRUCTURE
|
|
@param $info : **REFERENCE** to a class "msg_structure" object
|
|
@param $content : string from the "content-type:" or "content-disposition:" header
|
|
@param $is_disposition_param : boolean : true if parsing "content-disposition:" header string
|
|
tells this function to fill info->dparameters instead of the more common info->parameters
|
|
@result NONE : this function DIRECTLY manipulates the referenced object
|
|
@discussion as implemented, reference is to some part of class var $this->msg_structure
|
|
parses "content-type:" header string into fetchstructure data info->parameters
|
|
or "content-disposition:" header string into fetchstructure data info->dparameters
|
|
@author Angles, Itzchak Rehberg, Joseph Engo
|
|
@access private
|
|
*/
|
|
function parse_msg_params($info,$content,$is_disposition_param=False)
|
|
{
|
|
if ($this->debug >= 2) {
|
|
//echo 'pop3: *in parse_msg_params<br>';
|
|
echo 'pop3: *in parse_msg_params: content ['.$content.']<br>';
|
|
echo 'pop3: *in parse_msg_params: is_disposition_param ['.(string)$is_disposition_param.']<br>';
|
|
}
|
|
// bogus data detection
|
|
if (trim($content) == '')
|
|
{
|
|
// we need to exit this function, we were fed bogus (empty) $content
|
|
// this function does not actually return anything
|
|
// instead it directly manipulates the referenced $info param
|
|
// thus we can call "return" to exit the function with no effect on data flow
|
|
return;
|
|
}
|
|
// seperate param strings into an string list array
|
|
$param_list = Array();
|
|
if (strstr($content, ';'))
|
|
{
|
|
$param_list = explode(';',$content);
|
|
}
|
|
else
|
|
{
|
|
$param_list[0] = $content;
|
|
}
|
|
// process each param string
|
|
for ($x=0; $x < count($param_list) ;$x++)
|
|
{
|
|
$pos_token = strpos($param_list[$x],"=");
|
|
if ($pos_token == 0)
|
|
{
|
|
// error - not a regular param=value pair
|
|
$param_attrib = trim($param_list[$x]);
|
|
$param_value = 'UNKNOWN_PARAM_VALUE';
|
|
}
|
|
else
|
|
{
|
|
$param_attrib = trim(substr($param_list[$x],0,$pos_token));
|
|
$param_value = trim(substr($param_list[$x],$pos_token+1));
|
|
$param_value = str_replace("\"","",$param_value);
|
|
}
|
|
// are these typical message paramaters or the more rare "disposition" params
|
|
if ($is_disposition_param == False)
|
|
{
|
|
// typical msg params
|
|
$new_idx = count($info->parameters);
|
|
$info->parameters[$new_idx] = new msg_params($param_attrib,$param_value);
|
|
$info->ifparameters = true;
|
|
}
|
|
else
|
|
{
|
|
// content-disposition paramaters are pretty rare
|
|
$new_idx = count($info->dparameters);
|
|
$info->dparameters[$new_idx] = new msg_params($param_attrib,$param_value);
|
|
$info->ifparameters = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
function type_str_to_int($type_str)
|
|
{
|
|
// fallback value
|
|
$type_int = TYPEOTHER;
|
|
switch ($type_str)
|
|
{
|
|
case 'text' : $type_int = TYPETEXT; break;
|
|
case 'multipart' : $type_int = TYPEMULTIPART; break;
|
|
case 'message' : $type_int = TYPEMESSAGE; break;
|
|
case 'application' : $type_int = TYPEAPPLICATION; break;
|
|
case 'audio' : $type_int = TYPEAUDIO; break;
|
|
case 'image' : $type_int = TYPEIMAGE; break;
|
|
case 'video' : $type_int = TYPEVIDEO; break;
|
|
// this causes errors under php 4.0.6, but used to work before that, I think
|
|
//default : $type_int = TYPEOTHER; break;
|
|
}
|
|
return $type_int;
|
|
}
|
|
|
|
function default_type($probably_text=True)
|
|
{
|
|
if ($probably_text)
|
|
{
|
|
return TYPETEXT;
|
|
}
|
|
else
|
|
{
|
|
return TYPEAPPLICATION;
|
|
}
|
|
}
|
|
|
|
function default_subtype($type_int=TYPEAPPLICATION)
|
|
{
|
|
// APPLICATION/OCTET-STREAM is the default when NO info is available
|
|
switch ($type_int)
|
|
{
|
|
case TYPETEXT : return 'plain'; break;
|
|
case TYPEMULTIPART : return 'mixed'; break;
|
|
case TYPEMESSAGE : return 'rfc822'; break;
|
|
case TYPEAPPLICATION : return 'octet-stream'; break;
|
|
case TYPEAUDIO : return 'basic'; break;
|
|
default : return 'unknown'; break;
|
|
}
|
|
}
|
|
|
|
function default_encoding()
|
|
{
|
|
return ENC7BIT;
|
|
}
|
|
|
|
// MAY BE OBSOLETED
|
|
function more_info($header,$i,$info,$infokey)
|
|
{
|
|
// used by pop_fetchstructure only
|
|
do
|
|
{
|
|
$pos = strpos($header[$i+1],' ');
|
|
if (is_int($pos) && !$pos)
|
|
{
|
|
$i++;
|
|
$info->$infokey .= ltrim($header[$i]);
|
|
}
|
|
}
|
|
while (is_int($pos) && !$pos);
|
|
return $i;
|
|
}
|
|
|
|
function encoding_str_to_int($encoding_str)
|
|
{
|
|
switch (strtolower($encoding_str))
|
|
{
|
|
case '7bit' : $encoding_int = ENC7BIT; break;
|
|
case '8bit' : $encoding_int = ENC8BIT; break;
|
|
case 'binary' : $encoding_int = ENCBINARY; break;
|
|
case 'base64' : $encoding_int = ENCBASE64; break;
|
|
case 'quoted-printable': $encoding_int = ENCQUOTEDPRINTABLE; break;
|
|
case 'other' : $encoding_int = ENCOTHER; break;
|
|
case 'uu' : $encoding_int = ENCUU; break;
|
|
default : $encoding_int = ENCOTHER; break;
|
|
}
|
|
return $encoding_int;
|
|
}
|
|
|
|
function size_msg($stream_notused,$msg_num)
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Entering size_msg<br>'; }
|
|
if (!$this->msg2socket('LIST '.$msg_num,"^\+ok",&$response))
|
|
{
|
|
$this->error();
|
|
return False;
|
|
}
|
|
$list_response = explode(' ',$response);
|
|
$return_size = trim($list_response[2]);
|
|
$return_size = (int)$return_size * 1;
|
|
if ($this->debug >= 1) { echo 'pop3: size_msg: '.$return_size.'<br>'; }
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving size_msg<br>'; }
|
|
return $return_size;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
* Message Envelope (Header Info) Data
|
|
\**************************************************************************/
|
|
/*!
|
|
@function header
|
|
@abstract implements IMAP_HEADER (alias to IMAP_HEADERINFO)
|
|
@abstract implements IMAP_HEADERINFO
|
|
@param $stream_notused : socket class handles stream reference internally
|
|
@param $msg_num : intefer
|
|
@param $fromlength ?
|
|
@param $tolength ?
|
|
@param $defaulthost ?
|
|
@result returns an instance of Class "hdr_info_envelope", or returns False on error
|
|
@discussion ?
|
|
@author Angles, Skeeter, Itzchak Rehberg, Joseph Engo
|
|
@access public
|
|
*/
|
|
function header($stream_notused,$msg_num,$fromlength='',$tolength='',$defaulthost='')
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Entering header<br>'; }
|
|
$info = new hdr_info_envelope;
|
|
$info->Size = $this->size_msg($stream_notused,$msg_num);
|
|
$info->size = $info->Size;
|
|
$header_array = $this->get_header_array($stream_notused,$msg_num);
|
|
if (!$header_array)
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving header with error<br>'; }
|
|
return False;
|
|
}
|
|
for ($i=0; $i < count($header_array); $i++)
|
|
{
|
|
// POP3 ONLY !!! - POP3 considers ALL messages as "unseen" and/or "recent"
|
|
// because POP3 does not retain such info as seen or unseen
|
|
// I *may* comment that out because I find this annoying
|
|
//$info->Unseen = 'U';
|
|
$pos = strpos($header_array[$i],' ');
|
|
if (is_int($pos) && !$pos)
|
|
{
|
|
continue;
|
|
}
|
|
$keyword = strtolower(substr($header_array[$i],0,$pos));
|
|
$content = trim(substr($header_array[$i],$pos+1));
|
|
switch ($keyword)
|
|
{
|
|
case 'date:':
|
|
$info->date = $content;
|
|
$info->udate = $this->make_udate($content);
|
|
break;
|
|
case 'subject':
|
|
case 'subject:':
|
|
$pos = strpos($header_array[$i+1],' ');
|
|
if (is_int($pos) && !$pos)
|
|
{
|
|
$i++; $content .= chop($header_array[$i]);
|
|
}
|
|
$info->subject = htmlspecialchars($content);
|
|
$info->Subject = htmlspecialchars($content);
|
|
break;
|
|
case 'in-reply-to:':
|
|
$info->in_reply_to = htmlspecialchars($content);
|
|
break;
|
|
case 'message-id':
|
|
case 'message-id:':
|
|
$info->message_id = htmlspecialchars($content);
|
|
break;
|
|
case 'newsgroups:':
|
|
$info->newsgroups = htmlspecialchars($content);
|
|
break;
|
|
case 'followup-to:':
|
|
$info->follow_up_to = htmlspecialchars($content);
|
|
break;
|
|
case 'references:':
|
|
$info->references = htmlspecialchars($content);
|
|
break;
|
|
case 'to':
|
|
case 'to:':
|
|
// following two lines need to be put into a loop!
|
|
// NOTE: 3rd and 4th params to get_addr_details are REFERENCES
|
|
$info->to = $this->get_addr_details('to',$content,&$header_array,&$i);
|
|
break;
|
|
case 'from':
|
|
case 'from:':
|
|
// NOTE: 3rd and 4th params to get_addr_details are REFERENCES
|
|
$info->from = $this->get_addr_details('from',$content,&$header_array,&$i);
|
|
break;
|
|
case 'cc':
|
|
case 'cc:':
|
|
// NOTE: 3rd and 4th params to get_addr_details are REFERENCES
|
|
$info->cc = $this->get_addr_details('cc',$content,&$header_array,&$i);
|
|
break;
|
|
case 'bcc':
|
|
case 'bcc:':
|
|
// NOTE: 3rd and 4th params to get_addr_details are REFERENCES
|
|
$info->bcc = $this->get_addr_details('bcc',$content,&$header_array,&$i);
|
|
break;
|
|
case 'reply-to':
|
|
case 'reply-to:':
|
|
// NOTE: 3rd and 4th params to get_addr_details are REFERENCES
|
|
$info->reply_to = $this->get_addr_details('reply_to',$content,&$header_array,&$i);
|
|
break;
|
|
case 'sender':
|
|
case 'sender:':
|
|
// NOTE: 3rd and 4th params to get_addr_details are REFERENCES
|
|
$info->sender = $this->get_addr_details('sender',$content,&$header_array,&$i);
|
|
break;
|
|
case 'return-path':
|
|
case 'return-path:':
|
|
// NOTE: 3rd and 4th params to get_addr_details are REFERENCES
|
|
$info->return_path = $this->get_addr_details('return_path',$content,&$header_array,&$i);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if ($this->debug >= 1)
|
|
{
|
|
echo 'pop3: Leaving header<br>';
|
|
}
|
|
return $info;
|
|
}
|
|
|
|
/*!
|
|
@function get_addr_details
|
|
@abstract HELPER function to header / IMAP_HEADER
|
|
@param ?
|
|
@param ?
|
|
@param ?
|
|
@param ?
|
|
@result ?
|
|
@discussion ?
|
|
@author Itzchak Rehberg, Joseph Engo
|
|
@access private
|
|
*/
|
|
function get_addr_details($people,$address,$header,$count)
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Entering get_addr_details<br>'; }
|
|
if (!trim($address))
|
|
{
|
|
return False;
|
|
}
|
|
// check wether this header info is split to multiple lines
|
|
$done = false;
|
|
do
|
|
{
|
|
$pos = strpos($header[$count+1],' ');
|
|
if (is_int($pos) && !$pos)
|
|
{
|
|
$count++;
|
|
$address .= chop($header[$count]);
|
|
}
|
|
else
|
|
{
|
|
$done = true;
|
|
}
|
|
}
|
|
while (!$done);
|
|
$temp = $people . 'address';
|
|
|
|
if ($people == 'return_path')
|
|
{
|
|
$this->$people = htmlspecialchars($address);
|
|
}
|
|
else
|
|
{
|
|
$this->$temp = htmlspecialchars($address);
|
|
}
|
|
|
|
for ($i=0,$pos=1;$pos;$i++)
|
|
{
|
|
//$addr_details = new msg_aka;
|
|
$addr_details = new address;
|
|
$pos = strpos($address,'<');
|
|
$pos3 = strpos($address,'(');
|
|
if (is_int($pos))
|
|
{
|
|
$pos2 = strpos($address,'>');
|
|
if ($pos2 == $pos+1)
|
|
{
|
|
$addr_details->adl = 'nobody@nowhere';
|
|
}
|
|
else
|
|
{
|
|
$addr_details->adl = substr($address,$pos+1,$pos2 - $pos -1);
|
|
}
|
|
if ($pos)
|
|
{
|
|
$addr_details->personal = substr($address,0,$pos - 1);
|
|
}
|
|
}
|
|
elseif (is_int($pos3))
|
|
{
|
|
$pos2 = strpos($address,')');
|
|
if ($pos2 == $pos3+1)
|
|
{
|
|
$addr_details->personal = 'nobody';
|
|
}
|
|
else
|
|
{
|
|
$addr_details->personal = substr($address, $pos3+1, $pos2-$pos3 - 1);
|
|
}
|
|
if ($pos3)
|
|
{
|
|
$addr_details->adl = substr($address,0,$pos3 - 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$addr_details->adl = $address;
|
|
$addr_details->personal = $address;
|
|
}
|
|
$pos3 = strpos($addr_details->adl,'@');
|
|
if (!$pos3)
|
|
{
|
|
if (!$pos)
|
|
{
|
|
$addr_details->mailbox = $addr_details->adl;
|
|
}
|
|
$addr_details->host = $GLOBALS['phpgw_info']['server']['imap_suffix'];
|
|
$details[$i] = $addr_details;
|
|
return $details;
|
|
}
|
|
$addr_details->mailbox = substr($addr_details->adl,0,$pos3);
|
|
$addr_details->host = substr($addr_details->adl,$pos3+1);
|
|
$pos = ereg("\"",$addr_details->personal);
|
|
if ($pos)
|
|
{
|
|
$addr_details->personal = substr($addr_details->personal,1,strlen($addr_details->personal)-2);
|
|
}
|
|
$pos = strpos($address,',');
|
|
if ($pos)
|
|
{
|
|
$address = trim(substr($address,$pos+1));
|
|
}
|
|
$details[$i] = $addr_details;
|
|
}
|
|
return $details;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
* More Data Communications (dcom) With POP3 Server
|
|
\**************************************************************************/
|
|
|
|
/**************************************************************************\
|
|
* DELETE a Message From the Server
|
|
\**************************************************************************/
|
|
/*!
|
|
@function delete
|
|
@abstract implements IMAP_DELETE
|
|
@param $stream_notused : socket class handles stream reference internally
|
|
@param $msg_num : either an integer OR a comma seperated string of integers and/or ranges (21:23, 26, 69)
|
|
@param $flags : integer - FT_UID (not implimented)
|
|
@result returns True if able to mark a message for deletion, False if not
|
|
@discussion Similar to an IMAP server, POP3 must be expunged to actually delete marked messages
|
|
This is done (1) by immediately closing the connection after your done marking, this will cause POP3 to expunge
|
|
or (2) by issuing PHP's buildin IMAP_EXPUNGE command which we DO NOT emulate here
|
|
@author Angles
|
|
@access public
|
|
*/
|
|
function delete($stream_notused,$msg_num,$flags="")
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Entering delete<br>'; }
|
|
// in PHP 4 msg_num can be
|
|
// a) an integer referencing a single message
|
|
// b1) a comma seperated list of message numbers "1,2,6"
|
|
// b2) and/or a range of messages format [STARTRANGE][COLON][ENDRANGE] "1:5" "6:*"
|
|
// make an array of message numbers to delete
|
|
$tmp_array = Array();
|
|
$tmp_array = explode(',',(string)$msg_num);
|
|
// process the array, and clean any empty elements (explode can suck like that sometimes)
|
|
$msg_num_array = Array();
|
|
for($i=0;$i < count($tmp_array);$i++)
|
|
{
|
|
$this_element = (string)$tmp_array[$i];
|
|
if ($this->debug >= 2) { echo 'pop3: delete prep: this_element: '.$this_element.'<br>'; }
|
|
$this_element = trim($this_element);
|
|
// do nothing if this is an empty array element
|
|
if ($this_element != '')
|
|
{
|
|
// not empty - process it
|
|
// do we have a range
|
|
$cookie = strpos($this_element,':');
|
|
if ($cookie > 0)
|
|
{
|
|
$start_num = substr($this_element,0,$cookie);
|
|
$end_num = substr($this_element,$cookie+1);
|
|
// wildcard * used?
|
|
if ($end_num == '*')
|
|
{
|
|
$end_num = $this->num_msg($stream_notused);
|
|
}
|
|
// make sure we are dealing with integers now
|
|
$start_num = (int)$start_num;
|
|
$end_num = (int)$end_num;
|
|
// add each number in this range to the msg_num_array
|
|
for($z=$start_num; $z >= $end_num; $z++)
|
|
{
|
|
// add to the msg_num_array
|
|
$new_idx = count($msg_num_array);
|
|
$msg_num_array[$new_idx] = (int)$z;
|
|
if ($this->debug >= 2) { echo 'pop3: delete prep: range: msg_num_array['.$new_idx.'] = '.$z.'<br>'; }
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// not a range, should be a single msg_num
|
|
// add to the msg_num_array
|
|
$new_idx = count($msg_num_array);
|
|
$msg_num_array[$new_idx] = (int)$this_element;
|
|
if ($this->debug >= 2) { echo 'pop3: delete prep: msg_num_array['.$new_idx.'] = '.$this_element.'<br>'; }
|
|
}
|
|
}
|
|
}
|
|
// we should now have a reliable array of msg_nums we need to delete from the server
|
|
for($i=0;$i < count($msg_num_array);$i++)
|
|
{
|
|
$this_msg_num = $msg_num_array[$i];
|
|
if ($this->debug >= 2) { echo 'pop3: delete: deleting this_msg_num '.$this_msg_num.'<br>'; }
|
|
if (!$this->msg2socket('DELE '.$this_msg_num,"^\+ok",&$response))
|
|
{
|
|
$this->error();
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving delete with error deleting msgnum '.$this_msg_num.'<br>'; }
|
|
return False;
|
|
}
|
|
}
|
|
// these messages are now marked for deletion by the POP3 server
|
|
// they will be expunged when user sucessfully explicitly logs out
|
|
// if we make it here I have to assume no errors
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving delete<br>'; }
|
|
return True;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
* Get Message Headers From Server
|
|
\**************************************************************************/
|
|
/*!
|
|
@function fetchheader
|
|
@abstract implements IMAP_FETCHHEADER
|
|
@param $stream_notused : socket class handles stream reference internally
|
|
@param $msg_num : integer
|
|
@param $flags : integer - FT_UID; FT_INTERNAL; FT_PREFETCHTEXT
|
|
@result returns string which is complete, unfiltered RFC2822 format header of the specified message
|
|
@discussion This function implements the FT_PREFETCHTEXT text option
|
|
This function uses the helper function "get_header_raw"
|
|
@author Angles
|
|
@access public
|
|
*/
|
|
function fetchheader($stream_notused,$msg_num,$flags='')
|
|
{
|
|
// NEEDED: code for flags: FT_UID; FT_INTERNAL; FT_PREFETCHTEXT
|
|
if ($this->debug >= 1) { echo 'pop3: Entering fetchheader<br>'; }
|
|
|
|
$header_glob = $this->get_header_raw($stream_notused,$msg_num,$flags);
|
|
|
|
// do we also need to get the text of the message?
|
|
if ((int)$flags == FT_PREFETCHTEXT)
|
|
{
|
|
// what the user really wants here is the whole enchalada, i.e. the headers AND the message
|
|
$header_glob = $header_glob
|
|
."\r\n"
|
|
.$this->get_body($stream_notused,$msg_num,$flags);
|
|
}
|
|
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving fetchheader<br>'; }
|
|
return $header_glob;
|
|
}
|
|
|
|
/*!
|
|
@function get_header_array
|
|
@abstract Custom Function - Similar to IMAP_FETCHHEADER - EXCEPT returns a string list array
|
|
@param $stream_notused : socket class handles stream reference internally
|
|
@param $msg_num : integer
|
|
@param $flags : integer - FT_UID; (FT_INTERNAL; FT_PREFETCHTEXT) none implemented
|
|
@result returns headers exploded into a string list array, one array element per Un-Folded header line
|
|
@discussion This function UN-FOLDS the headers as per RFC2822 "folding, so each element is
|
|
in fact the intended complete header line, eliminates partial "folded" lines
|
|
@author Angles
|
|
@access public (custom function, also used privately)
|
|
*/
|
|
function get_header_array($stream_notused,$msg_num,$flags='')
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Entering get_header_array<br>'; }
|
|
// do we have a cached header_array ?
|
|
if ((count($this->header_array) > 0)
|
|
&& ((int)$this->header_array_msgnum == (int)($msg_num)))
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving get_header_array returning cached data<br>'; }
|
|
return $this->header_array;
|
|
}
|
|
// NO cached data, get it
|
|
// first get the raw glob header
|
|
$header_glob = $this->get_header_raw($stream_notused,$msg_num,$flags);
|
|
// unwrap any wrapped headers - using CR_LF_TAB as rfc822 "whitespace"
|
|
$header_glob = str_replace("\r\n\t",' ',$header_glob);
|
|
// unwrap any wrapped headers - using CR_LF_SPACE as rfc822 "whitespace"
|
|
$header_glob = str_replace("\r\n ",' ',$header_glob);
|
|
// make the header blob into an array of strings, one array element per header line, throw away blank lines
|
|
$header_array = Array();
|
|
$header_array = $this->glob_to_array($header_glob, False, '', True);
|
|
// cache this data for future use
|
|
$this->header_array = $header_array;
|
|
$this->header_array_msgnum = (int)($msg_num);
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving get_header_array<br>'; }
|
|
return $header_array;
|
|
}
|
|
|
|
/*!
|
|
@function get_header_raw
|
|
@abstract HELPER function for "fetchheader" / IMAP_FETCHHEADER
|
|
@param $stream_notused : socket class handles stream reference internally
|
|
@param $msg_num : integer
|
|
@param $flags : Not Used in helper function
|
|
@result returns returns unprocessed glob header string of the specified message
|
|
@discussion This function causes a fetch of the complete, unfiltered RFC2822 format
|
|
header of the specified message as a text string and returns that text string (i.e. glob)
|
|
@author Angles
|
|
@access private
|
|
*/
|
|
function get_header_raw($stream_notused,$msg_num,$flags='')
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Entering get_header_raw<br>'; }
|
|
if ((!isset($msg_num))
|
|
|| (trim((string)$msg_num) == ''))
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving get_header_raw with error: Invalid msg_num<br>'; }
|
|
return False;
|
|
}
|
|
// do we have a cached header_glob ?
|
|
if (($this->header_glob != '')
|
|
&& ((int)$this->header_glob_msgnum == (int)($msg_num)))
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving get_header_raw returning cached data<br>'; }
|
|
return $this->header_glob;
|
|
}
|
|
// NO cached data, get it
|
|
if ($this->debug >= 1) { echo 'pop3: get_header_raw: issuing: TOP '.$msg_num.' 0 <br>'; }
|
|
if (!$this->msg2socket('TOP '.$msg_num.' 0',"^\+ok",&$response))
|
|
{
|
|
$this->error();
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving get_header_raw with error<br>'; }
|
|
return False;
|
|
}
|
|
$glob = $this->read_port_glob('.');
|
|
// save this info for future ues
|
|
$this->header_glob = $glob;
|
|
$this->header_glob_msgnum = (int)$msg_num;
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving get_header_raw<br>'; }
|
|
return $glob;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
* Get Message Body (Parts) From Server
|
|
\**************************************************************************/
|
|
|
|
/*!
|
|
@function fetchbody
|
|
@abstract implements IMAP_FETCHBODY
|
|
@param $stream_notused : socket class handles stream reference internally
|
|
@param $msg_num : integer
|
|
@param $part_num : integer or a string of integers seperated by dots "2.4.1"
|
|
references the MIME part number, or section, inside of the message
|
|
@param $flags : Not Used in helper function
|
|
@result returns string which is the desired message / part
|
|
@discussion NOTE: as of Oct 17, 2001, the $part_num used here is not always
|
|
the same as the part number used for official imap servers. But because this same
|
|
class produced the fetchstructure, and provided it to the client, and that client
|
|
will again use this class to get that part, the part number is consistant internally
|
|
and is MUCH easier to implement in the fetchbody code. However, in the future, the
|
|
part numbering logic in fetchbody will be coded to exactly match what an official imap
|
|
server would expect. In the msg_msg class I refer to this "inexact" part number
|
|
as "mime number dumb" as it is based only on the part's position in the
|
|
fetchstructure array, before the processing to convert to official imap part
|
|
number, which msg_msg class refers to as "mime number smart", which
|
|
is used to access mime parts when using PHP's builtin IMAP module.
|
|
@author Angles
|
|
@access public
|
|
*/
|
|
function fetchbody($stream_notused,$msg_num,$part_num='',$flags='')
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: Entering fetchbody<br>'; }
|
|
if ($this->debug >= 1) { echo 'pop3: fetchbody: attempt to return part '.$part_num.'<br>'; }
|
|
// totally under construction
|
|
|
|
// FORCE a pass thru fetchstructure to ENSURE all necessary data is present and cached
|
|
if ($this->debug >= 2) { echo 'pop3: fetchbody: force a pass thru fetchstructure to ensure necessary data is present and cached<br>'; }
|
|
$bogus_data = $this->fetchstructure($stream_notused,$msg_num,$flags);
|
|
|
|
// EXTREMELY BASIC part handling
|
|
// handle request for top level message headers
|
|
if ((int)$part_num == 0)
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: fetchbody: returning top-level headers, part '.$part_num.', internally ['.$the_part.']<br>'; }
|
|
// grab the headers, as a glob, i.e. a string NOT an array
|
|
$header_glob = $this->get_header_raw($stream_notused,$msg_num,'');
|
|
// put this data in the var we will return below
|
|
$body_blob = $header_glob;
|
|
}
|
|
// handle 1st level parts
|
|
elseif (strlen((string)$part_num) == 1)
|
|
{
|
|
// convert to fetchstructure part number
|
|
$the_part = (int)$part_num;
|
|
$the_part = $the_part - 1;
|
|
// return part one
|
|
if ($this->debug >= 1) { echo 'pop3: fetchbody: returning part '.$part_num.', internally ['.$the_part.']<br>'; }
|
|
if ((!isset($this->msg_structure->parts[$the_part]->custom['part_start']))
|
|
|| (!isset($this->msg_structure->parts[$the_part]->custom['part_start'])))
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: fetchbody: ERROR: required part data not present for '.$part_num.', internally ['.$the_part.']<br>'; }
|
|
// screw it, just return the whole thing
|
|
if ($this->debug >= 1) { echo 'pop3: fetchbody - using fallback pass thru<br>'; }
|
|
$body_blob = $this->get_body($stream_notused,$msg_num,$flags,False);
|
|
}
|
|
else
|
|
{
|
|
// attempt to make the part
|
|
$part_start = (int)$this->msg_structure->parts[$the_part]->custom['part_start'];
|
|
$part_end = (int)$this->msg_structure->parts[$the_part]->custom['part_end'];
|
|
if ($this->debug >= 1) { echo 'pop3: fetchbody: returning part '.$part_num.' starts ['.$part_start.'] ends ['.$part_end.']<br>'; }
|
|
// assemble the body [art part
|
|
$body_blob = '';
|
|
for($i=$part_start;$i < $part_end+1;$i++)
|
|
{
|
|
$body_blob .= $this->body_array[$i]."\r\n";
|
|
}
|
|
}
|
|
}
|
|
// handle multiple parts
|
|
elseif (strlen((string)$part_num) > 2)
|
|
{
|
|
// explode part number into its component part numbers
|
|
$the_part_array = Array();
|
|
$the_part_array = explode('.',$part_num);
|
|
// convert to fetchstructure part number
|
|
for($i=0;$i < count($the_part_array);$i++)
|
|
{
|
|
$the_part_array[$i] = (int)$the_part_array[$i];
|
|
$the_part_array[$i] = $the_part_array[$i] - 1;
|
|
}
|
|
// build the recursive parts structure to obtain this parts data
|
|
// use REFERENCES to do this
|
|
$temp_part = &$this->msg_structure;
|
|
for($i=0;$i < count($the_part_array);$i++)
|
|
{
|
|
$target_part = $temp_part->parts[$the_part_array[$i]];
|
|
$temp_part = &$target_part;
|
|
}
|
|
// verify part data exists
|
|
if ($this->debug >= 1) { echo 'pop3: fetchbody: returning part '.$part_num.', internally ['.serialize($the_part_array).']<br>'; }
|
|
if ((!isset($target_part->custom['part_start']))
|
|
|| (!isset($target_part->custom['part_start'])))
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: fetchbody: ERROR: required part data not present for '.$part_num.', internally ['.serialize($the_part).']<br>'; }
|
|
// screw it, just return the whole thing
|
|
if ($this->debug >= 1) { echo 'pop3: fetchbody - using fallback pass thru<br>'; }
|
|
$body_blob = $this->get_body($stream_notused,$msg_num,$flags,False);
|
|
}
|
|
else
|
|
{
|
|
// attempt to make the part
|
|
$part_start = (int)$target_part->custom['part_start'];
|
|
$part_end = (int)$target_part->custom['part_end'];
|
|
if ($this->debug >= 1) { echo 'pop3: fetchbody: returning part '.$part_num.' starts ['.$part_start.'] ends ['.$part_end.']<br>'; }
|
|
// assemble the body [art part
|
|
$body_blob = '';
|
|
for($i=$part_start;$i < $part_end+1;$i++)
|
|
{
|
|
$body_blob .= $this->body_array[$i]."\r\n";
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// screw it, just return the whole thing
|
|
if ($this->debug >= 1) { echo 'pop3: fetchbody - something is unsupported, using fallback pass thru<br>'; }
|
|
// the false arg here is a temporary, custom option, says to NOT include the headers in the return
|
|
$body_blob = $this->get_body($stream_notused,$msg_num,$flags,False);
|
|
}
|
|
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving fetchbody<br>'; }
|
|
return $body_blob;
|
|
}
|
|
|
|
/*!
|
|
@function get_body
|
|
@abstract implements IMAP_BODY
|
|
@param $stream_notused : socket class handles stream reference internally
|
|
@param $msg_num : integer
|
|
@param $flags : integer - FT_UID; FT_INTERNAL; FT_PEEK; FT_NOT
|
|
@param$phpgw_include_header : boolean (for custom use - not a PHP option)
|
|
@result returns string which is a verbatim copy of the message body (i.e. glob)
|
|
@discussion This function implements the IMAP_BODY and also includes a custom
|
|
boolean param "phpgw_include_header" which also includes unfiltered headers in the return string
|
|
@author Angles
|
|
@access public
|
|
*/
|
|
function get_body($stream_notused,$msg_num,$flags='',$phpgw_include_header=True)
|
|
{
|
|
// NEEDED: code for flags: FT_UID; maybe FT_INTERNAL; FT_NOT; flag FT_PEEK has no effect on POP3
|
|
if ($this->debug >= 1) { echo 'pop3: Entering get_body<br>'; }
|
|
|
|
// do we have a cached body_array ?
|
|
if ((count($this->body_array) > 0)
|
|
&& ((int)$this->body_array_msgnum == (int)($msg_num))
|
|
// do we have a cached header_array ?
|
|
&& (count($this->header_array) > 0)
|
|
&& ((int)$this->header_array_msgnum == (int)($msg_num)))
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: get_body: using cached body_array and header_array data imploded into a glob<br>'; }
|
|
// implode the header_array into a glob
|
|
$header_glob = implode("\r\n",$this->header_array);
|
|
// implode the body_array into a glob
|
|
$body_glob = implode("\r\n",$this->body_array);
|
|
}
|
|
else
|
|
{
|
|
if ($this->debug >= 1) { echo 'pop3: get_body: NO Cached Data<br>'; }
|
|
// NO cached data we can use
|
|
// issue command to retrieve body
|
|
if (!$this->msg2socket('RETR '.$msg_num,"^\+ok",&$response))
|
|
{
|
|
$this->error();
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving get_body with error<br>'; }
|
|
return False;
|
|
}
|
|
// --- Get Header ---
|
|
// we can NOT cache the header in THIS function because we may need to BYPASS them
|
|
// to do that we need to grab it from the stream, then start filling body_glob
|
|
// AFTER we have passed the header in the stream
|
|
$header_glob = '';
|
|
while ($line = $this->read_port())
|
|
{
|
|
if ((chop($line) == '.')
|
|
|| (chop($line) == ''))
|
|
{
|
|
break;
|
|
}
|
|
$header_glob .= $line;
|
|
}
|
|
// --- Get Body ---
|
|
// we know we have passed the headers because we did that above
|
|
$body_glob = '';
|
|
$body_glob = $this->read_port_glob('.');
|
|
// --- Explode Into an Array and Save for Future use with Fetchstructure
|
|
$this->body_array = explode("\r\n",$body_glob);
|
|
$this->body_array_msgnum = (int)$msg_num;
|
|
}
|
|
// --- Include Headers With Body Or Not ---
|
|
if (($flags == FT_NOT) || ($phpgw_include_header == True))
|
|
{
|
|
// we need to include the header here
|
|
$body_glob = $header_glob ."\r\n" .$body_glob;
|
|
}
|
|
/*
|
|
if ($this->debug >= 2)
|
|
{
|
|
echo 'pop3: get_body DUMP<br>= = = First DUMP: header_glob<br>';
|
|
echo '<pre>'.htmlspecialchars($header_glob).'</pre><br><br>';
|
|
echo 'pop3: get_body DUMP<br>= = = Second DUMP: body_glob<br>';
|
|
echo '<pre>'.htmlspecialchars($body_glob).'</pre><br><br>';
|
|
}
|
|
*/
|
|
if ($this->debug >= 1) { echo 'pop3: Leaving get_body<br>'; }
|
|
return $body_glob;
|
|
}
|
|
}
|
|
?>
|