PageRenderTime 53ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/libs/Nette.Addons/nSMTPMailer/nSMTPMailer/Communicator.php

https://code.google.com/
PHP | 370 lines | 135 code | 57 blank | 178 comment | 26 complexity | 421cf8953521a761b3afd18ac7ad3177 MD5 | raw file
Possible License(s): Apache-2.0, GPL-2.0
  1. <?php
  2. namespace Nette\Mail\SMTPClient;
  3. /**
  4. * A class that communicates with the server, sends commands and receives
  5. * responses
  6. * Copyright (c) 2009, Martin Pecka (peci1@seznam.cz)
  7. * All rights reserved.
  8. *
  9. * Redistribution and use in source and binary forms, with or without
  10. * modification, are permitted provided that the following conditions are met:
  11. * * Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. * * Redistributions in binary form must reproduce the above copyright
  14. * notice, this list of conditions and the following disclaimer in the
  15. * documentation and/or other materials provided with the distribution.
  16. * * Neither the name Martin Pecka nor the
  17. * names of contributors may be used to endorse or promote products
  18. * derived from this software without specific prior written permission.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  21. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  22. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  23. * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  24. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  25. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  26. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  27. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  28. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  29. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30. * @package nSMTPMailer
  31. * @version 1.0.0
  32. * @copyright (c) 2009 Martin Pecka (Clevis)
  33. * @author Martin Pecka <peci1@seznam.cz>
  34. * @license See license.txt
  35. */
  36. class /*Nette\Mail\SMTPClient\*/Communicator
  37. {
  38. /** @const int Number of retries if the connection failed */
  39. const MAX_CONNECTION_RETRIES = 3;
  40. /** @const int The delay between two connection attempts (in seconds) */
  41. const CONNECTION_RETRY_DELAY = 5;
  42. /** @const int The maximal length of single response line in characters */
  43. const MAX_RESPONSE_LENGTH = 515;
  44. /** @const int The maximal number of lines in a server response
  45. * (basically used for infinte cycles detection)*/
  46. const MAX_RESPONSE_LINES = 100;
  47. /** @const string The string representing end of line */
  48. const EOL = "\r\n";
  49. /** @var Handle of the connection to the server. */
  50. protected $connection = NULL;
  51. /** @var string The transport used for the connection (one of
  52. * stream_get_transports() values - usually tcp, tls, ssl) */
  53. protected $transport = 'tcp';
  54. /** @var string The host to connect to */
  55. protected $host = '127.0.0.1';
  56. /** @var int The port to connect to */
  57. protected $port = 25;
  58. /** @var int The connection timeout in seconds */
  59. protected $timeout = 300;
  60. /** @var SMTP\Response The last response read from the server */
  61. protected $lastResponse = NULL;
  62. /**
  63. * Create the communicator with the given credentials
  64. *
  65. * @param string $host The host to connect to
  66. * @param int $port The port to connect to
  67. * @param string $transport The transport used for the connection (one of
  68. * stream_get_transports() values - usually tcp, tls, ssl)
  69. * @param int $timeout The connection timeout in seconds
  70. * @return void
  71. *
  72. * @throws InvalidArgumentException If one of the arguments is invalid
  73. */
  74. public function __construct($host, $port, $transport, $timeout = 300)
  75. {
  76. $this->setHost($host);
  77. $this->setPort($port);
  78. $this->setTransport($transport);
  79. $this->setTimeout($timeout);
  80. }
  81. /**
  82. * Connects to the given server and stores the connection in
  83. * $this->connection. If the connection fails, tries up to
  84. * self::MAX_CONNECTION_RETRIES reconnections.
  85. *
  86. * Requires the transport, host, port and timeout to be correctly set
  87. *
  88. * @return void
  89. *
  90. * @throws InvalidStateException If the connection failed
  91. */
  92. public function connect()
  93. {
  94. for ($i = 0; $i < self::MAX_CONNECTION_RETRIES; $i++) {
  95. SMTPClient::debug(
  96. 'Connection attempt #%u to %s://%s:%u with timeout %u seconds',
  97. $i, $this->transport, $this->host, $this->port,
  98. $this->timeout);
  99. $this->connection = @fsockopen(
  100. $this->transport . '://' . $this->host,
  101. $this->port,
  102. $errorNumber,
  103. $errorMessage,
  104. $this->timeout);
  105. if ($this->connection !== FALSE)
  106. return;
  107. SMTPClient::debug(
  108. 'Connection attempt #%u failed with code %u and message %s',
  109. $i, $errorNumber, $errorMessage);
  110. if ($i < self::MAX_CONNECTION_RETRIES - 1)
  111. SMTPClient::debug('Will try to connect again in %u seconds',
  112. self::CONNECTION_RETRY_DELAY);
  113. sleep(self::CONNECTION_RETRY_DELAY);
  114. }
  115. throw new \InvalidStateException(
  116. 'Could not connect to ' . $this->transport.'://'.$this->host . ':'
  117. . $this->port);
  118. }
  119. /**
  120. * Sends the command to the server and reads the response to it.
  121. *
  122. * @param string|NULL|array $command The command to send (can use printf
  123. * style). If NULL, no command is sent
  124. * and this just reads a server response.
  125. * If it is an array, all items will be
  126. * treated like lines of commands and
  127. * will be sent to the server while the
  128. * response will be read only after all
  129. * of them are sent.
  130. * @param mixed $args,... Optional printf-like arguments
  131. *
  132. * @return Response The server response
  133. *
  134. * @throws InvalidStateException If the communicator is not connected
  135. * @throws InvalidArgumentException If printf-like arguments are used for
  136. * non-textual command (ie. array command)
  137. * @throws IOException If writing or reading over network failed
  138. */
  139. public function getResponse($command)
  140. {
  141. if ($this->connection === NULL)
  142. throw new \InvalidStateException('Cannot communicate with SMTP ' .
  143. 'server when not connected.');
  144. if ($command !== NULL) {
  145. if (func_num_args() > 1) {
  146. if (!is_string($command))
  147. throw new \InvalidArgumentException(
  148. 'Cannot use printf-like arguments to a command which' .
  149. ' is not a string!');
  150. $args = func_get_args(); array_shift($args);
  151. $command = vsprintf($command, $args);
  152. }
  153. if (!is_array($command)) $command = array($command);
  154. foreach ($command as $line) {
  155. //send the command to the server
  156. SMTPClient::debug("\n\nCOMMAND: %s\n=======", $line);
  157. if (@fwrite($this->connection, $line . self::EOL) === FALSE) {
  158. if (@fwrite($this->connection, $line . self::EOL) === FALSE) {
  159. throw new \IOException('Could not send the command to the ' .
  160. 'server: ' . $command);
  161. }
  162. }
  163. }
  164. }
  165. /*
  166. * The reply text may be longer than a single line; in these cases the
  167. * complete text must be marked so the SMTP client knows when it can
  168. * stop reading the reply. This requires a special format to indicate a
  169. * multiple line reply.
  170. *
  171. * The format for multiline replies requires that every line, except the
  172. * last, begin with the reply code, followed immediately by a hyphen,
  173. * "-" (also known as minus), followed by text. The last line will
  174. * begin with the reply code, followed immediately by <SP>, optionally
  175. * some text, and <CRLF>. As noted above, servers SHOULD send the <SP>
  176. * if subsequent text is not sent, but clients MUST be prepared for it
  177. * to be omitted.
  178. *
  179. * For example:
  180. *
  181. * 123-First line
  182. * 123-Second line
  183. * 123-234 text beginning with numbers
  184. * 123 The last line
  185. *
  186. * In many cases the SMTP client then simply needs to search for a line
  187. * beginning with the reply code followed by <SP> or <CRLF> and ignore
  188. * all preceding lines. In a few cases, there is important data for the
  189. * client in the reply "text". The client will be able to identify
  190. * these cases from the current context.
  191. */
  192. $response['code'] = '';
  193. $response['text'] = array();
  194. $responsePart = '';
  195. $i=0;
  196. //we fetch lines of the response
  197. while ($line = fgets($this->connection, self::MAX_RESPONSE_LENGTH)) {
  198. //fgets leaves the CRLF at the end of the string
  199. $line = rtrim($line);
  200. //blank line shouldn't appear, it indicates nothing more is to
  201. //be read
  202. if (strlen($line) < 3)
  203. break;
  204. //if we loop a lot of times, something went wrong, so
  205. //give up the reading
  206. if ($i > self::MAX_RESPONSE_LINES)
  207. throw new \InvalidStateException(
  208. 'There was a problem reading server response. The client' .
  209. 'could not detect its end.');
  210. SMTPClient::debug('RESPONSE LINE #%u: %s', ++$i, $line);
  211. //first 3 charachters denote the response code
  212. $response['code'] = substr($line, 0, 3);
  213. //and everything after the 4th character is the response text
  214. //note that the response text can be empty, in that case the
  215. //response text will be false ( @see substr() )
  216. $response['text'][] = substr($line, 4);
  217. //If we encountered a response, where space follows response code,
  218. //it is the last line. Other lines have a hyphen (-) instead
  219. if(substr($line, 3, 1) == ' ')
  220. break;
  221. }
  222. $this->lastResponse =
  223. new Response((int)$response['code'], $response['text']);
  224. return $this->lastResponse;
  225. }
  226. /**
  227. * Sets the transport and checks the value to be correct
  228. *
  229. * @param string $transport The new transport value (one of
  230. * stream_get_transports() values)
  231. *
  232. * @return Communicator Provides fluent interface
  233. *
  234. * @throws InvalidArgumentException If the transport is empty
  235. */
  236. public function setTransport($transport)
  237. {
  238. if ($transport == '') //intentionally ==
  239. throw new InvalidArgumentException(
  240. 'Invalid value of the SmtpClient transport: ' . $transport);
  241. if (!in_array($transport, stream_get_transports()))
  242. throw new InvalidArgumentException(
  243. 'Transport layer ' . $transport . ' is not supported by ' .
  244. 'the local PHP installation.');
  245. $this->transport = $transport;
  246. return $this;
  247. }
  248. /**
  249. * Sets the host and checks the value to be correct
  250. *
  251. * @param string $host The new host value
  252. * @return Communicator Provides fluent interface
  253. *
  254. * @throws InvalidArgumentException If the host is empty
  255. */
  256. public function setHost($host)
  257. {
  258. if ($host == '') //intentionally ==
  259. throw new \InvalidArgumentException(
  260. 'Invalid value of the SmtpClient host: ' . $host);
  261. $this->host = $host;
  262. return $this;
  263. }
  264. /**
  265. * Sets the port and checks the value to be correct
  266. *
  267. * @param int $port The new port value
  268. * @return Communicator Provides fluent interface
  269. *
  270. * @throws InvalidArgumentException If the port is not a number
  271. */
  272. public function setPort($port)
  273. {
  274. if ($port == '' || !is_numeric($port)) //intentionally ==
  275. throw new \InvalidArgumentException(
  276. 'Invalid value of the SmtpClient port: ' . $host);
  277. $this->port = $port;
  278. return $this;
  279. }
  280. /**
  281. * Sets the connection timeout and checks the value to be correct
  282. *
  283. * @param int $timeout The new timeout value
  284. * @return Communicator Provides fluent interface
  285. *
  286. * @throws InvalidArgumentException If the timeout is not a number
  287. */
  288. public function setTimeout($timeout)
  289. {
  290. if ($timeout == '' || !is_numeric($timeout)) //intentionally ==
  291. throw new \InvalidArgumentException(
  292. 'Invalid value of the SmtpClient timeout: ' . $timeout);
  293. $this->timeout = $timeout;
  294. return $this;
  295. }
  296. /**
  297. * Return the last server response
  298. *
  299. * @return Response The last server response
  300. */
  301. public function getLastResponse() { return $this->lastResponse; }
  302. /**
  303. * Returns the active connection.
  304. *
  305. * @return The active connection
  306. */
  307. public function getConnection() { return $this->connection; }
  308. }
  309. /* ?> omitted intentionally */