1<?php
2/**
3 * Profiling of diverse mail functions
4 *
5 * For Apache FCGI you need the following rewrite rule:
6 *
7 * 	RewriteEngine on
8 * 	RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
9 *
10 * Otherwise authentication request will be send over and over again, as password is NOT available to PHP!
11 *
12 * @link http://www.egroupware.org
13 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
14 * @package mail
15 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
16 * @copyright (c) 2014 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
17 * @version $Id$
18 */
19
20$starttime = microtime(true);
21
22$GLOBALS['egw_info'] = array(
23	'flags' => array(
24		'disable_Template_class' => True,
25		'noheader'  => True,
26		'currentapp' => 'mail',
27		'autocreate_session_callback' => 'EGroupware\\Api\\Header\\Authenticate::autocreate_session_callback',
28		'auth_realm' => 'EGroupware mail profile',
29	)
30);
31include(dirname(__DIR__).'/header.inc.php');
32
33$headertime = microtime(true);
34
35use EGroupware\Api\Mail\Account as emailadmin_account;
36use EGroupware\Api\Mail\Imap as emailadmin_imap;
37
38// on which mail account do we work, if not specified use default one (connects to imap server!)
39$acc_id = isset($_GET['acc_id']) && (int)$_GET['acc_id'] > 0 ? (int)$_GET['acc_id'] : emailadmin_account::get_default_acc_id();
40// calling emailadmin_account::read with explicit account_id to not cache object for current user!
41$account = emailadmin_account::read($acc_id, $GLOBALS['egw_info']['user']['account_id']);
42
43// switching off caching by default
44// if caching is enabled mail_times will always provit from previous running horde_times!
45$cache = isset($_GET['cache']) && $_GET['cache'];
46if (!$cache) unset(emailadmin_imap::$default_params['cache']);
47
48$accounttime = microtime(true);
49
50$times = array(
51	'header' => $headertime - $starttime,
52	'acc_id' => $acc_id,
53	'account' => (string)$account,
54	'cache' => $cache,
55	'read' => $accounttime - $headertime,
56);
57
58php_times($account, $times);
59horde_times($account, $times);
60mail_times($acc_id, $times);
61
62Header('Content-Type: application/json; charset=utf-8');
63echo json_encode($times, JSON_PRETTY_PRINT);
64
65function php_times($account, array &$times, $prefix='php_')
66{
67	$starttime = microtime(true);
68	switch($account->acc_imap_ssl & ~emailadmin_account::SSL_VERIFY)
69	{
70		case emailadmin_account::SSL_SSL:
71			$schema = 'ssl';
72			break;
73		case emailadmin_account::SSL_TLS:
74			$schema = 'tls';
75			break;
76		case emailadmin_account::SSL_STARTTLS:
77		default:
78			$schema = 'tcp';
79			break;
80	}
81	$error_number = $error_string = null;
82	$stream = stream_socket_client(
83		$schema . '://' . $account->acc_imap_host . ':' . $account->acc_imap_port,
84		$error_number,
85		$error_string,
86		20,
87		STREAM_CLIENT_CONNECT,
88		/* @todo: As of PHP 5.6, TLS connections require valid certs.
89		 * However, this is BC-breaking to this library. For now, keep
90		 * pre-5.6 behavior. */
91		stream_context_create(array(
92			'ssl' => array(
93				'verify_peer' => false,
94				'verify_peer_name' => false
95			)
96		))
97	);
98	$connect_response = fgets($stream);
99
100	// starttls (untested)
101	if ($stream && ($account->acc_imap_ssl & ~emailadmin_account::SSL_VERIFY) == emailadmin_account::SSL_STARTTLS)
102	{
103		fwrite($stream, "10 STARTTLS\r\n");
104		stream_socket_enable_crypto($stream, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
105		$starttls_response = fgets($stream);
106	}
107	stream_set_timeout($stream, 20);
108
109	if (function_exists('stream_set_read_buffer')) {
110		stream_set_read_buffer($stream, 0);
111	}
112	stream_set_write_buffer($stream, 0);
113
114	$connect = microtime(true);
115
116	fwrite($stream, "20 LOGIN $account->acc_imap_username $account->acc_imap_password\r\n");
117	$login_response = fgets($stream);
118	$endtime = microtime(true);
119
120	$times += array(
121		$prefix.'connect' => $connect - $starttime,
122		//$prefix.'connect_response' => $connect_response,
123		$prefix.'login' => $endtime - $starttime,
124		//$prefix.'login_response' => $login_response,
125	);
126
127	fclose($stream);
128	unset($connect_response, $starttls_response, $login_response, $error_number, $error_string);
129}
130
131function mail_times($acc_id, array &$times, $prefix='mail_')
132{
133	global $cache;
134	$starttime = microtime(true);
135	// instanciate mail for given acc_id - have to set it as preference ;-)
136	$GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'] = $acc_id;
137	// instanciation should call openConnection
138	$mail_ui = new mail_ui();
139	$mail_ui->mail_bo->openConnection($acc_id);
140	$logintime = microtime(true);
141
142	// fetch mailboxes
143	$mboxes = $mail_ui->getFolderTree(/*$_fetchCounters=*/false, null, /*$_subscribedOnly=*/true,
144		/*$_returnNodeOnly=*/true, $cache, /*$_popWizard=*/false);
145	$listmailboxestime = microtime(true);
146
147	// get first 20 mails
148	$query = array(
149		'start' => 0,
150		'num_rows' => 20,
151		'filter' => 'any',
152		'filter2' => 'quick',
153		'search' => '',
154		'order' => 'date',
155		'sort' => 'DESC',
156	);
157	$rows = $readonlys = array();
158	$mail_ui->get_rows($query, $rows, $readonlys);
159	$fetchtime = microtime(true);
160
161	if (isset($_GET['uid']) && (int)$_GET['uid'] > 0)
162	{
163		$uid = (int)$_GET['uid'];
164	}
165	else	// use uid of first returned row
166	{
167		$row = array_shift($rows);
168		$uid = $row['uid'];
169	}
170	$mail_ui->get_load_email_data($uid, null, 'INBOX');
171	$bodytime = microtime(true);
172
173	$times += array(
174		$prefix.'login' => $logintime - $starttime,
175		$prefix.'listmailboxes' => $listmailboxestime - $logintime,
176		$prefix.'fetch' => $fetchtime - $listmailboxestime,
177		$prefix.'total' => $fetchtime - $starttime,
178		$prefix.'body' => $bodytime - $fetchtime,
179		//$prefix.'mboxes' => $mboxes,
180	);
181	unset($mboxes);
182	$mail_ui->mail_bo->icServer->close();
183	$mail_ui->mail_bo->icServer->logout();
184}
185
186function horde_times(emailadmin_account $account, array &$times, $prefix='horde_')
187{
188	$starttime = microtime(true);
189	$imap = $account->imapServer();
190
191	// connect to imap server
192	$imap->login();
193	$logintime = microtime(true);
194
195	// list all subscribed mailboxes incl. attributes and children
196	$mboxes = $imap->listMailboxes('*', Horde_Imap_Client::MBOX_SUBSCRIBED, array(
197		'attributes' => true,
198		'children' => true,
199	));
200	$listmailboxestime = microtime(true);
201
202	// fetch 20 newest mails
203	horde_fetch($imap);
204	$fetchtime = microtime(true);
205
206	$times += array(
207		$prefix.'login' => $logintime - $starttime,
208		$prefix.'listmailboxes' => $listmailboxestime - $logintime,
209		$prefix.'fetch' => $fetchtime - $listmailboxestime,
210		$prefix.'total' => $fetchtime - $starttime,
211		//$prefix.'mboxes' => $mboxes,
212	);
213	unset($mboxes);
214	$imap->close();
215	$imap->logout();
216}
217
218function horde_fetch(Horde_Imap_Client_Socket $client, $mailbox='INBOX')
219{
220	$squery = new Horde_Imap_Client_Search_Query();
221	// using a date filter to limit returned uids, gives huge speed improvement on big mailboxes, because less uids returned
222	//$squery->dateSearch(new DateTime('-30days'), Horde_Imap_Client_Search_Query::DATE_SINCE, false, false);
223	$squery->flag('DELETED', $set=false);
224	$sorted = $client->search($mailbox, $squery, array(
225		'sort' => array(Horde_Imap_Client::SORT_REVERSE, Horde_Imap_Client::SORT_SEQUENCE),
226	));
227
228	$first20uids = new Horde_Imap_Client_Ids();
229	$first20uids->add(array_slice($sorted['match']->ids, 0, 20));
230
231	$fquery = new Horde_Imap_Client_Fetch_Query();
232	$fquery->headers('headers', array('Subject', 'From', 'To', 'Cc', 'Date'), array('peek' => true,'cache' => true));
233	$fquery->structure();
234	$fquery->flags();
235	$fquery->imapDate();
236
237	return $client->fetch($mailbox, $fquery, array(
238		'ids' => $first20uids,
239	));
240}
241