#!/usr/bin/env php
<?php
/**
 * Generated by Box.
 *
 * @link https://github.com/herrera-io/php-box/
 */
if (class_exists('Phar')) {
Phar::mapPhar('default.phar');
require 'phar://' . __FILE__ . '/bin/email';
}
__HALT_COMPILER(); ?>
                   vendor/autoload.php   cX   A@qY      %   vendor/composer/autoload_classmap.php   cX   b      "   vendor/composer/autoload_files.php/  cX/  1      '   vendor/composer/autoload_namespaces.php   cX   qAm      !   vendor/composer/autoload_psr4.php  cX  N6      !   vendor/composer/autoload_real.php	  cX	  ߶      #   vendor/composer/autoload_static.php  cX  xb         vendor/composer/ClassLoader.php1  cX1  g      '   vendor/guzzlehttp/guzzle/src/Client.php8  cX8  bnG      0   vendor/guzzlehttp/guzzle/src/ClientInterface.php  cX  b      1   vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php  cX  |biL      :   vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php
  cX
  Ϲd       5   vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php9
  cX9
  ;      8   vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php  cX  lPT      1   vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php(  cX(  p      ?   vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php   cX   &      :   vendor/guzzlehttp/guzzle/src/Exception/ClientException.php   cX   g'K      ;   vendor/guzzlehttp/guzzle/src/Exception/ConnectException.php  cX  /      :   vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.phpD   cXD   D&      ;   vendor/guzzlehttp/guzzle/src/Exception/RequestException.php  cX  3      8   vendor/guzzlehttp/guzzle/src/Exception/SeekException.phpL  cXL  X      :   vendor/guzzlehttp/guzzle/src/Exception/ServerException.php   cX   M      D   vendor/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.phpc   cXc   ߶      <   vendor/guzzlehttp/guzzle/src/Exception/TransferException.phpw   cXw   Q      *   vendor/guzzlehttp/guzzle/src/functions.php>&  cX>&  D9˶      2   vendor/guzzlehttp/guzzle/src/functions_include.php   cX   I۱      4   vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php$J  cX$J  o`LQ      =   vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php  cX        4   vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.php  cX  hEGb      9   vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php  cX  O      3   vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php	  cX	        4   vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php  cX  'ɍ      .   vendor/guzzlehttp/guzzle/src/Handler/Proxy.php  cX  Xh      6   vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php<  cX<  l|      -   vendor/guzzlehttp/guzzle/src/HandlerStack.php  cX  d<猶      1   vendor/guzzlehttp/guzzle/src/MessageFormatter.php3  cX3  i.      +   vendor/guzzlehttp/guzzle/src/Middleware.phpj&  cXj&  w׶      %   vendor/guzzlehttp/guzzle/src/Pool.php,  cX,  +E      6   vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php8  cX8  P-      3   vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php  cX  QG      /   vendor/guzzlehttp/guzzle/src/RequestOptions.php%  cX%  Gs      0   vendor/guzzlehttp/guzzle/src/RetryMiddleware.php,  cX,         .   vendor/guzzlehttp/guzzle/src/TransferStats.php  cX  >      ,   vendor/guzzlehttp/guzzle/src/UriTemplate.php   cX   ܶ      5   vendor/guzzlehttp/promises/src/AggregateException.php{  cX{  ME8      8   vendor/guzzlehttp/promises/src/CancellationException.php   cX   KP      .   vendor/guzzlehttp/promises/src/EachPromise.phpn  cXn  ,eC       3   vendor/guzzlehttp/promises/src/FulfilledPromise.php  cX        ,   vendor/guzzlehttp/promises/src/functions.phpB5  cXB5  Y      4   vendor/guzzlehttp/promises/src/functions_include.php   cX   ߇'      *   vendor/guzzlehttp/promises/src/Promise.phpw!  cXw!  Vp      3   vendor/guzzlehttp/promises/src/PromiseInterface.php  cX  s:      4   vendor/guzzlehttp/promises/src/PromisorInterface.php   cX         2   vendor/guzzlehttp/promises/src/RejectedPromise.php  cX  5      5   vendor/guzzlehttp/promises/src/RejectionException.php  cX  3      ,   vendor/guzzlehttp/promises/src/TaskQueue.php  cX  ć      ;   vendor/guzzlehttp/promises/tests/AggregateExceptionTest.php~  cX~  8Y1      .   vendor/guzzlehttp/promises/tests/bootstrap.php   cX   惶      4   vendor/guzzlehttp/promises/tests/EachPromiseTest.phpR-  cXR-  <[      9   vendor/guzzlehttp/promises/tests/FulfilledPromiseTest.phpI  cXI  PN      2   vendor/guzzlehttp/promises/tests/functionsTest.phpW  cXW  J[[      7   vendor/guzzlehttp/promises/tests/NotPromiseInstance.php  cX  aK      0   vendor/guzzlehttp/promises/tests/PromiseTest.phpI  cXI  ,      8   vendor/guzzlehttp/promises/tests/RejectedPromiseTest.php  cX  Y/      ;   vendor/guzzlehttp/promises/tests/RejectionExceptionTest.php  cX  |      2   vendor/guzzlehttp/promises/tests/TaskQueueTest.php:  cX:  zj      .   vendor/guzzlehttp/promises/tests/Thennable.php  cX  ˭      +   vendor/guzzlehttp/psr7/src/AppendStream.php}  cX}  C      +   vendor/guzzlehttp/psr7/src/BufferStream.php  cX         ,   vendor/guzzlehttp/psr7/src/CachingStream.php  cX  ;n      -   vendor/guzzlehttp/psr7/src/DroppingStream.php8  cX8  WG      '   vendor/guzzlehttp/psr7/src/FnStream.phpL  cXL  R<      (   vendor/guzzlehttp/psr7/src/functions.phpt^  cXt^  '3      0   vendor/guzzlehttp/psr7/src/functions_include.php   cX   H      ,   vendor/guzzlehttp/psr7/src/InflateStream.php  cX  z-      -   vendor/guzzlehttp/psr7/src/LazyOpenStream.phpp  cXp  K1      *   vendor/guzzlehttp/psr7/src/LimitStream.phpr  cXr  M      +   vendor/guzzlehttp/psr7/src/MessageTrait.php  cX  `d      .   vendor/guzzlehttp/psr7/src/MultipartStream.php7  cX7  Vj      +   vendor/guzzlehttp/psr7/src/NoSeekStream.php  cX  l      )   vendor/guzzlehttp/psr7/src/PumpStream.php  cX  ]      &   vendor/guzzlehttp/psr7/src/Request.php<  cX<  2      '   vendor/guzzlehttp/psr7/src/Response.php  cX  4      ,   vendor/guzzlehttp/psr7/src/ServerRequest.phpu"  cXu"  >=      %   vendor/guzzlehttp/psr7/src/Stream.php  cX  sf      3   vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php  cX  \=      ,   vendor/guzzlehttp/psr7/src/StreamWrapper.php
  cX
  oஶ      +   vendor/guzzlehttp/psr7/src/UploadedFile.phpw  cXw  >i      "   vendor/guzzlehttp/psr7/src/Uri.phpw=  cXw=  c{Ķ      1   vendor/guzzlehttp/psr7/tests/AppendStreamTest.php  cX  +AYP      *   vendor/guzzlehttp/psr7/tests/bootstrap.php   cX    VF      1   vendor/guzzlehttp/psr7/tests/BufferStreamTest.php  cX        2   vendor/guzzlehttp/psr7/tests/CachingStreamTest.php  cX  B      3   vendor/guzzlehttp/psr7/tests/DroppingStreamTest.php  cX  F3      -   vendor/guzzlehttp/psr7/tests/FnStreamTest.php	  cX	  t      .   vendor/guzzlehttp/psr7/tests/FunctionsTest.phpFT  cXFT  [r      2   vendor/guzzlehttp/psr7/tests/InflateStreamTest.php  cX  ᱥ      3   vendor/guzzlehttp/psr7/tests/LazyOpenStreamTest.php5  cX5  
      0   vendor/guzzlehttp/psr7/tests/LimitStreamTest.phpp  cXp  Ƕ      4   vendor/guzzlehttp/psr7/tests/MultipartStreamTest.php  cX  凶      1   vendor/guzzlehttp/psr7/tests/NoSeekStreamTest.phpq  cXq  N$%      /   vendor/guzzlehttp/psr7/tests/PumpStreamTest.php  cX        ,   vendor/guzzlehttp/psr7/tests/RequestTest.php  cX  ؞ؗ      -   vendor/guzzlehttp/psr7/tests/ResponseTest.php#  cX#  b      2   vendor/guzzlehttp/psr7/tests/ServerRequestTest.php?J  cX?J  N      9   vendor/guzzlehttp/psr7/tests/StreamDecoratorTraitTest.php  cX  p      +   vendor/guzzlehttp/psr7/tests/StreamTest.php  cX  x<1      2   vendor/guzzlehttp/psr7/tests/StreamWrapperTest.php  cX  JX      1   vendor/guzzlehttp/psr7/tests/UploadedFileTest.php!  cX!  ȭL      (   vendor/guzzlehttp/psr7/tests/UriTest.php7U  cX7U  "e      Q   vendor/mailgun/mailgun-php/src/Mailgun/Connection/Exceptions/GenericHTTPError.phpM  cXM  Y떶      S   vendor/mailgun/mailgun-php/src/Mailgun/Connection/Exceptions/InvalidCredentials.php^   cX^   Μ      P   vendor/mailgun/mailgun-php/src/Mailgun/Connection/Exceptions/MissingEndpoint.php[   cX[   7      Z   vendor/mailgun/mailgun-php/src/Mailgun/Connection/Exceptions/MissingRequiredParameters.phpe   cXe   Q      T   vendor/mailgun/mailgun-php/src/Mailgun/Connection/Exceptions/NoDomainsConfigured.php_   cX_         @   vendor/mailgun/mailgun-php/src/Mailgun/Connection/RestClient.php"  cX"  ζ      8   vendor/mailgun/mailgun-php/src/Mailgun/Constants/Api.php&  cX&  P      F   vendor/mailgun/mailgun-php/src/Mailgun/Constants/ExceptionMessages.php  cX  [E      =   vendor/mailgun/mailgun-php/src/Mailgun/Lists/OptInHandler.phpw  cXw  sg3      2   vendor/mailgun/mailgun-php/src/Mailgun/Mailgun.php  cX        @   vendor/mailgun/mailgun-php/src/Mailgun/Messages/BatchMessage.php  cX  	q      O   vendor/mailgun/mailgun-php/src/Mailgun/Messages/Exceptions/InvalidParameter.phpZ   cXZ   7?      S   vendor/mailgun/mailgun-php/src/Mailgun/Messages/Exceptions/InvalidParameterType.php^   cX^   ÈG      \   vendor/mailgun/mailgun-php/src/Mailgun/Messages/Exceptions/MissingRequiredMIMEParameters.phpg   cXg   G      P   vendor/mailgun/mailgun-php/src/Mailgun/Messages/Exceptions/TooManyParameters.php[   cX[   x^      B   vendor/mailgun/mailgun-php/src/Mailgun/Messages/MessageBuilder.php61  cX61  s      .   vendor/mailgun/mailgun-php/tests/Bootstrap.phpi   cXi   bbv      0   vendor/php-http/discovery/src/ClassDiscovery.phpC  cXC  q      :   vendor/php-http/discovery/src/HttpAsyncClientDiscovery.php  cX  P      5   vendor/php-http/discovery/src/HttpClientDiscovery.php  cX  QK      9   vendor/php-http/discovery/src/MessageFactoryDiscovery.php  cX        3   vendor/php-http/discovery/src/NotFoundException.php   cX   {      8   vendor/php-http/discovery/src/StreamFactoryDiscovery.php  cX  ۶      5   vendor/php-http/discovery/src/UriFactoryDiscovery.php  cX  /      .   vendor/php-http/guzzle6-adapter/src/Client.php  cX  44      /   vendor/php-http/guzzle6-adapter/src/Promise.php  cX  eo      7   vendor/php-http/httplug/src/Exception/HttpException.phpt  cXt  4      :   vendor/php-http/httplug/src/Exception/NetworkException.phpM  cXM  'XO      :   vendor/php-http/httplug/src/Exception/RequestException.php  cX  п~      ;   vendor/php-http/httplug/src/Exception/TransferException.php  cX        )   vendor/php-http/httplug/src/Exception.php   cX   E
HP      /   vendor/php-http/httplug/src/HttpAsyncClient.php  cX  #      *   vendor/php-http/httplug/src/HttpClient.php  cX  g      0   vendor/php-http/promise/src/FulfilledPromise.php  cX  <      '   vendor/php-http/promise/src/Promise.php  cX   Ѷ      /   vendor/php-http/promise/src/RejectedPromise.php  cX  8      0   vendor/psr/http-message/src/MessageInterface.php  cX  &c
      0   vendor/psr/http-message/src/RequestInterface.php  cX  o      1   vendor/psr/http-message/src/ResponseInterface.php
  cX
  +      6   vendor/psr/http-message/src/ServerRequestInterface.phpW'  cXW'  7Ķ      /   vendor/psr/http-message/src/StreamInterface.php  cX  =fbr      5   vendor/psr/http-message/src/UploadedFileInterface.phpK  cXK  k\ȶ      ,   vendor/psr/http-message/src/UriInterface.php11  cX11  0w      ?   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Attachment.php  cX  ?      ]   vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.phpq  cXq  7      O   vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php  cX  ʸO      N   vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php  cX  .͍޶      W   vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php  cX  cÒ      \   vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php;	  cX;	  a`B      R   vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/UsAsciiReader.php  cX        O   vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.phpk  cXk        D   vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader.php  cX  뷣      h   vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory/SimpleCharacterReaderFactory.php  cX  x6W      K   vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory.php  cX  1      Y   vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/ArrayCharacterStream.php!  cX!  ;l      V   vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php  cX  1>      D   vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream.php  cX  Fb|      F   vendor/swiftmailer/swiftmailer/lib/classes/Swift/ConfigurableSpool.phpX  cXX  W:H      H   vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php?&  cX?&  i Ͷ      H   vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyException.phpL  cXL  {vs      A   vendor/swiftmailer/swiftmailer/lib/classes/Swift/EmbeddedFile.phpN  cXN  S6      J   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Base64Encoder.php5  cX5  A      F   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.phpT*  cXT*  Xg      K   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Rfc2231Encoder.phpF	  cXF	  A      <   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php  cX  !(Ƕ      =   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoding.php  cX  kL      H   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandEvent.php  cX  C      K   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandListener.php?  cX?        A   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/Event.php	  cX	  9=      K   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventDispatcher.php	  cX	  z      I   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventListener.php_  cX_  ;      G   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/EventObject.php6  cX6  ϶      I   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseEvent.php3  cX3  ?M      L   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseListener.phpC  cXC  lٱ      E   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendEvent.php
  cX
  =      H   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendListener.php  cX  7      Q   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SimpleEventDispatcher.php  cX        P   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php4  cX4  Y      S   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeListener.php  cX  uR      S   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionEvent.php!  cX!  oʞ      V   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionListener.php|  cX|  qJ      F   vendor/swiftmailer/swiftmailer/lib/classes/Swift/FailoverTransport.php|  cX|  r      >   vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileSpool.php  cX  Y#      ?   vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php  cX         ?   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Filterable.php  cX  S      :   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Image.php  cX  LX@      D   vendor/swiftmailer/swiftmailer/lib/classes/Swift/InputByteStream.php  cX  S(j      @   vendor/swiftmailer/swiftmailer/lib/classes/Swift/IoException.php  cX  1J      K   vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/ArrayKeyCache.php  cX  ~yD      J   vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.phpy#  cXy#  ,߶      Q   vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/KeyCacheInputStream.php  cX  N$S      J   vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/NullKeyCache.php
  cX
  .*      W   vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/SimpleKeyCacheInputStream.php
  cX
  PZ      =   vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache.phpK  cXK  W[      J   vendor/swiftmailer/swiftmailer/lib/classes/Swift/LoadBalancedTransport.php  cX        R   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/ArrayRecipientIterator.php  cX  G      M   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/RecipientIterator.php  cX  YV      ;   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.phpO  cXO  nU      B   vendor/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php  cX  L˶      @   vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php
  cX
  q%      <   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.phpX  cXX  3      D   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.phpL  cXL  GNa      I   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php  cX  r      ]   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/Base64ContentEncoder.phpH  cXH        _   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php  cX  ޶      \   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/PlainContentEncoder.php  cX  e      Y   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php  cX  Ŏk      ^   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php	  cX	  +'w      Z   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php  cX  h.      H   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder.php  cX  8+Vi      F   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EmbeddedFile.php  cX        J   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EncodingObserver.php<  cX<  sfr      A   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Grammar.phpz  cXz  K      @   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Header.php  cX  >=      [   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/Base64HeaderEncoder.phpM  cXM  ު      W   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php  cX  !2       G   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder.php  cX  J      G   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderFactory.phpo  cXo  XC3      P   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/AbstractHeader.phpN2  cXN2  B      L   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/DateHeader.php
  cX
  }      V   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/IdentificationHeader.php  cX  3M?      O   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.phpy$  cXy$        P   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/OpenDKIMHeader.php	  cX	        U   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php  cX  u'"      L   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/PathHeader.php  cX  )G      T   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/UnstructuredHeader.phpl	  cXl	  af      C   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderSet.phpR  cXR  z      A   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Message.php  cX  	      D   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimeEntity.php  cX  ?n      B   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimePart.php  cX  8K      M   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ParameterizedHeader.php  cX  -N      M   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderFactory.php  cX  5      I   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php*  cX*  +Z      G   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.phpA  cXA  +      J   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php[  cX[  5      =   vendor/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php  cX  ؐ      B   vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php  cX  ɂJt      E   vendor/swiftmailer/swiftmailer/lib/classes/Swift/OutputByteStream.phph  cXh  ̃d      L   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/AntiFloodPlugin.php=  cX=  ?C      S   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php/  cX/  =o      S   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Decorator/Replacements.php  cX  #      L   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/DecoratorPlugin.php!  cX!  yD      N   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php  cX  =      C   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Logger.phpf  cXf   wy      I   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/LoggerPlugin.php;  cX;  %!-D      P   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php  cX  :      O   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/EchoLogger.php8  cX8  n      J   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.phpy  cXy  >?      O   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Connection.php  cX  *5      N   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Exception.phpF  cXF         P   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/PopBeforeSmtpPlugin.php9  cX9  U      N   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.phpw  cXw        E   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporter.phpM  cXM  o0      K   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ReporterPlugin.php  cX  ?      R   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HitReporter.php  cX  mJ      S   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php  cX  <t>      D   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Sleeper.php  cX  C      L   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ThrottlerPlugin.php  cX  ;ж      B   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Timer.php  cX        @   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Preferences.php	  cX	  n(      M   vendor/swiftmailer/swiftmailer/lib/classes/Swift/ReplacementFilterFactory.php%  cX%  TpN      K   vendor/swiftmailer/swiftmailer/lib/classes/Swift/RfcComplianceException.php*  cX*  >rd      F   vendor/swiftmailer/swiftmailer/lib/classes/Swift/SendmailTransport.php  cX  1a      B   vendor/swiftmailer/swiftmailer/lib/classes/Swift/SignedMessage.php  cX  3D      ;   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.phpp  cXp  A4      G   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/BodySigner.php  cX  [GZ      G   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.phpE  cXE  4}      L   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php3  cX3  (a      I   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/HeaderSigner.php  cX        K   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/OpenDKIMSigner.php  cX  _      H   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php:  cX:  n6      B   vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php  cX  qݶ      :   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Spool.php  cX  ma1      C   vendor/swiftmailer/swiftmailer/lib/classes/Swift/SpoolTransport.php1  cX1  Ỷ      A   vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilter.php  cX  `      ]   vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/ByteArrayReplacementFilter.phpH  cXH  0C      Z   vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php  cX        a   vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php  cX  Ͼ       C   vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php  cX  F淶      T   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/AbstractSmtpTransport.phpW9  cXW9  {p      ^   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php  cX  RI      \   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php]  cX]  ˔      [   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/NTLMAuthenticator.phpX  cXX  w#b5      \   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php  cX  3Q      ^   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/XOAuth2Authenticator.phpl  cXl  JZ¶      R   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.phpD  cXD  #+      P   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php)  cX)  ܉      K   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpHandler.php	  cX	  R>      M   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php<+  cX<+  5,      P   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.phpi	  cXi	  b      G   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/IoBuffer.php  cX  쬶      T   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/LoadBalancedTransport.php  cX  |      J   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php  cX  m      L   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailTransport.php($  cX($  ^j      L   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/NullTransport.php  cX  u깶      P   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php  cX  4      P   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php  cX  }zS      H   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SmtpAgent.php  cX  |Ӷ      M   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SpoolTransport.phpW
  cXW
  \      K   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php%  cX%  ˯      >   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport.php  cX  J      G   vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php  cX        =   vendor/swiftmailer/swiftmailer/lib/classes/Swift/Validate.php  cX  ʶ      4   vendor/swiftmailer/swiftmailer/lib/classes/Swift.php"  cX"  v      A   vendor/swiftmailer/swiftmailer/lib/dependency_maps/cache_deps.phpv  cXv  8˶      C   vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php   cX   @      @   vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php  cX  wT      E   vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php	  cX	  Vc,      1   vendor/swiftmailer/swiftmailer/lib/mime_types.php  cX  Y	%h      2   vendor/swiftmailer/swiftmailer/lib/preferences.phpw  cXw        1   vendor/swiftmailer/swiftmailer/lib/swift_init.php  cX  n֦      5   vendor/swiftmailer/swiftmailer/lib/swift_required.php  cX  |Ͷ      :   vendor/swiftmailer/swiftmailer/lib/swift_required_pear.php  cX  	2      H   vendor/swiftmailer/swiftmailer/lib/swiftmailer_generate_mimes_config.phpR  cXR        R   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/AttachmentAcceptanceTest.php"  cX"  oKdζ      a   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/ByteStream/FileByteStreamAcceptanceTest.php  cX  ̶      {   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/CharacterReaderFactory/SimpleCharacterReaderFactoryAcceptanceTest.php  cX  m/\      [   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php  cX  `ٶ      T   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EmbeddedFileAcceptanceTest.php,  cX,  [      ]   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Base64EncoderAcceptanceTest.php  cX  ZQi      Y   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.phpT  cXT  cԶ      ^   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Rfc2231EncoderAcceptanceTest.php  cX  \(B      P   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EncodingAcceptanceTest.phpg  cXg  "ʶ      ^   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/ArrayKeyCacheAcceptanceTest.php  cX  m{      ]   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php,  cX,  Xx      O   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MessageAcceptanceTest.phpn  cXn  <      W   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php  cX  on*6      p   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/Base64ContentEncoderAcceptanceTest.php#  cX#  aj      r   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/NativeQpContentEncoderAcceptanceTest.php  cX  Цƶ      o   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/PlainContentEncoderAcceptanceTest.php  cX  J      l   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.phpg  cXg  K      Y   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php  cX  b      n   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/HeaderEncoder/Base64HeaderEncoderAcceptanceTest.php  cX  F      U   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php  cX        Z   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php  cX  ܶ      P   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MimePartAcceptanceTest.php  cX  =P      s   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php  cX  (}      j   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php  cX  o      f   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php  cX  s3      b   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SocketTimeoutTest.phpz  cXz  I      h   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php  cX  J
      h   vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php  cX  8      2   vendor/swiftmailer/swiftmailer/tests/bootstrap.php  cX  X      =   vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug111Test.php-  cX-  1l      =   vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug118Test.php  cX  vYH      =   vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug206Test.php,  cX,  ݶ      =   vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug274Test.php\  cX\  4ݶ      <   vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug34Test.php	  cX	  B      <   vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug35Test.phpY	  cXY	  'q۶      <   vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug38Test.php2  cX2  :      =   vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug518Test.phpv  cXv  6      <   vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug51Test.php  cX  A敷      =   vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug534Test.phpb  cXb  o      =   vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug650Test.phpf  cXf  <L      <   vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug71Test.php  cX  i="z      <   vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug76Test.php	  cX	  IKֶ      \   vendor/swiftmailer/swiftmailer/tests/bug/Swift/BugFileByteStreamConsecutiveReadCallsTest.php  cX  -l      C   vendor/swiftmailer/swiftmailer/tests/fixtures/MimeEntityFixture.php  cX  rb      B   vendor/swiftmailer/swiftmailer/tests/IdenticalBinaryConstraint.php  cX  es      N   vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/AttachmentSmokeTest.phpS  cXS  q      I   vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/BasicSmokeTest.php  cX  +      V   vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.phpN  cXN  Ԟ      Q   vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/InternationalSmokeTest.php  cX  bB      8   vendor/swiftmailer/swiftmailer/tests/StreamCollector.php   cX   RB      A   vendor/swiftmailer/swiftmailer/tests/SwiftMailerSmokeTestCase.php  cX  ,b       <   vendor/swiftmailer/swiftmailer/tests/SwiftMailerTestCase.php  cX  5t      R   vendor/swiftmailer/swiftmailer/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php+  cX+  |      _   vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/GenericFixedWidthReaderTest.php_  cX_        U   vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/UsAsciiReaderTest.php]  cX]  p(ݶ      R   vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/Utf8ReaderTest.php  cX  ]5      \   vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.phpa:  cXa:  =      K   vendor/swiftmailer/swiftmailer/tests/unit/Swift/DependencyContainerTest.php  cX  a      M   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Base64EncoderTest.php  cX  U2      I   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/QpEncoderTest.php>  cX>  Y:      N   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Rfc2231EncoderTest.php  cX  }ud%      K   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/CommandEventTest.php>  cX>  a      J   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/EventObjectTest.phpw  cXw        L   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/ResponseEventTest.php  cX        H   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SendEventTest.php  cX  TF      T   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SimpleEventDispatcherTest.php  cX  P@      S   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportChangeEventTest.phpX  cXX  _      V   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportExceptionEventTest.php  cX  R      N   vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.phpu  cXu  @)[      Z   vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php	  cX	  nkz      U   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mailer/ArrayRecipientIteratorTest.php  cX  =      >   vendor/swiftmailer/swiftmailer/tests/unit/Swift/MailerTest.php  cX  K9      ?   vendor/swiftmailer/swiftmailer/tests/unit/Swift/MessageTest.phpy  cXy  19*      O   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AbstractMimeEntityTest.php̍  cX̍  -      G   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AttachmentTest.php+  cX+  R      `   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/Base64ContentEncoderTest.phpJ,  cXJ,        _   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php'  cX'  2i      \   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.phpLQ  cXLQ  {      I   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/EmbeddedFileTest.php  cX  Nö      ^   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php  cX  s޶      Z   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php!  cX!  q      O   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/DateHeaderTest.php  cX        Y   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/IdentificationHeaderTest.php  cX  $"      R   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/MailboxHeaderTest.php-  cX-  aouV      X   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.phpe<  cXe<  (S      O   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/PathHeaderTest.php	  cX	  f	      W   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/UnstructuredHeaderTest.php3  cX3  d0_      E   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/MimePartTest.phpY  cXY        P   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php  cX  q      L   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderSetTest.phpk  cXk        J   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMessageTest.phpm  cXm        M   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMimeEntityTest.php5  cX5  ֶ      O   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/AntiFloodPluginTest.php^  cX^  d      V   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php{  cX{  F_      O   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/DecoratorPluginTest.php#  cX#  ⑐      L   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/LoggerPluginTest.php8  cX8  mL      S   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/ArrayLoggerTest.php  cX  жe"      R   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/EchoLoggerTest.php  cX  '      S   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.phpc  cXc  "      Q   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/RedirectingPluginTest.php'  cX'  .aL      N   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ReporterPluginTest.phpU  cXU  my      U   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php=  cX=  @      V   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php/  cX/  _5.      O   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ThrottlerPluginTest.php  cX  
      J   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/DKIMSignerTest.phpw-  cXw-  ұݶ      N   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/OpenDKIMSignerTest.php2  cX2  ihw      K   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/SMimeSignerTest.phpoN  cXoN  ^L      `   vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php  cX  Qb      d   vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php  cX  *sC      ]   vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.phpr  cXr  R      Z   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.phpR  cXR   b      N   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpTest.phpq  cXq        a   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php  cX  L@      _   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.phpW  cXW  )<      ^   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/NTLMAuthenticatorTest.php'  cX'  o4a      _   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php  cX  
      S   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php!  cX!  7Y׶      a   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.phpJ  cXJ  `jiN      P   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransportTest.php&  cX&  {5      S   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/FailoverTransportTest.phpD  cXD  T      W   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/LoadBalancedTransportTest.phpb  cXb  mM      O   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/MailTransportTest.phpE  cXE  ޷      S   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/SendmailTransportTest.php  cX  k>      N   vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/StreamBufferTest.phpx  cXx  [S         src/DefaultConfig.php  cX  !}]i         src/DefineOptions.php!  cX!  j#̶         src/Helpers.php   cX   z         src/LoadConfig.php  cX  D*         src/SendEmail.php   cX   (         src/SendMailgunEmail.php  cX  )`         src/SendSmtpEmail.php
  cX
  !*         src/ShowUsage.php  cX  r      	   bin/email!  cX!  徶      <?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit2330e5f0bb40d3432d5d34c4ceb67556::getLoader();
<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
);
<?php

// autoload_files.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
    'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
    '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
    '2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php',
);
<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Mailgun\\Tests' => array($vendorDir . '/mailgun/mailgun-php/tests'),
    'Mailgun' => array($vendorDir . '/mailgun/mailgun-php/src'),
);
<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
    'Mreschke\\Email\\' => array($baseDir . '/src'),
    'Http\\Promise\\' => array($vendorDir . '/php-http/promise/src'),
    'Http\\Discovery\\' => array($vendorDir . '/php-http/discovery/src'),
    'Http\\Client\\' => array($vendorDir . '/php-http/httplug/src'),
    'Http\\Adapter\\Guzzle6\\' => array($vendorDir . '/php-http/guzzle6-adapter/src'),
    'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
    'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
    'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
);
<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInit2330e5f0bb40d3432d5d34c4ceb67556
{
    private static $loader;

    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        spl_autoload_register(array('ComposerAutoloaderInit2330e5f0bb40d3432d5d34c4ceb67556', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
        spl_autoload_unregister(array('ComposerAutoloaderInit2330e5f0bb40d3432d5d34c4ceb67556', 'loadClassLoader'));

        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
        if ($useStaticLoader) {
            require_once __DIR__ . '/autoload_static.php';

            call_user_func(\Composer\Autoload\ComposerStaticInit2330e5f0bb40d3432d5d34c4ceb67556::getInitializer($loader));
        } else {
            $map = require __DIR__ . '/autoload_namespaces.php';
            foreach ($map as $namespace => $path) {
                $loader->set($namespace, $path);
            }

            $map = require __DIR__ . '/autoload_psr4.php';
            foreach ($map as $namespace => $path) {
                $loader->setPsr4($namespace, $path);
            }

            $classMap = require __DIR__ . '/autoload_classmap.php';
            if ($classMap) {
                $loader->addClassMap($classMap);
            }
        }

        $loader->register(true);

        if ($useStaticLoader) {
            $includeFiles = Composer\Autoload\ComposerStaticInit2330e5f0bb40d3432d5d34c4ceb67556::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequire2330e5f0bb40d3432d5d34c4ceb67556($fileIdentifier, $file);
        }

        return $loader;
    }
}

function composerRequire2330e5f0bb40d3432d5d34c4ceb67556($fileIdentifier, $file)
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        require $file;

        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
    }
}
<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInit2330e5f0bb40d3432d5d34c4ceb67556
{
    public static $files = array (
        'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
        'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
        '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
        '2c102faa651ef8ea5874edb585946bce' => __DIR__ . '/..' . '/swiftmailer/swiftmailer/lib/swift_required.php',
    );

    public static $prefixLengthsPsr4 = array (
        'P' => 
        array (
            'Psr\\Http\\Message\\' => 17,
        ),
        'M' => 
        array (
            'Mreschke\\Email\\' => 15,
        ),
        'H' => 
        array (
            'Http\\Promise\\' => 13,
            'Http\\Discovery\\' => 15,
            'Http\\Client\\' => 12,
            'Http\\Adapter\\Guzzle6\\' => 21,
        ),
        'G' => 
        array (
            'GuzzleHttp\\Psr7\\' => 16,
            'GuzzleHttp\\Promise\\' => 19,
            'GuzzleHttp\\' => 11,
        ),
    );

    public static $prefixDirsPsr4 = array (
        'Psr\\Http\\Message\\' => 
        array (
            0 => __DIR__ . '/..' . '/psr/http-message/src',
        ),
        'Mreschke\\Email\\' => 
        array (
            0 => __DIR__ . '/../..' . '/src',
        ),
        'Http\\Promise\\' => 
        array (
            0 => __DIR__ . '/..' . '/php-http/promise/src',
        ),
        'Http\\Discovery\\' => 
        array (
            0 => __DIR__ . '/..' . '/php-http/discovery/src',
        ),
        'Http\\Client\\' => 
        array (
            0 => __DIR__ . '/..' . '/php-http/httplug/src',
        ),
        'Http\\Adapter\\Guzzle6\\' => 
        array (
            0 => __DIR__ . '/..' . '/php-http/guzzle6-adapter/src',
        ),
        'GuzzleHttp\\Psr7\\' => 
        array (
            0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
        ),
        'GuzzleHttp\\Promise\\' => 
        array (
            0 => __DIR__ . '/..' . '/guzzlehttp/promises/src',
        ),
        'GuzzleHttp\\' => 
        array (
            0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src',
        ),
    );

    public static $prefixesPsr0 = array (
        'M' => 
        array (
            'Mailgun\\Tests' => 
            array (
                0 => __DIR__ . '/..' . '/mailgun/mailgun-php/tests',
            ),
            'Mailgun' => 
            array (
                0 => __DIR__ . '/..' . '/mailgun/mailgun-php/src',
            ),
        ),
    );

    public static function getInitializer(ClassLoader $loader)
    {
        return \Closure::bind(function () use ($loader) {
            $loader->prefixLengthsPsr4 = ComposerStaticInit2330e5f0bb40d3432d5d34c4ceb67556::$prefixLengthsPsr4;
            $loader->prefixDirsPsr4 = ComposerStaticInit2330e5f0bb40d3432d5d34c4ceb67556::$prefixDirsPsr4;
            $loader->prefixesPsr0 = ComposerStaticInit2330e5f0bb40d3432d5d34c4ceb67556::$prefixesPsr0;

        }, null, ClassLoader::class);
    }
}
<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

/**
 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
 *
 *     $loader = new \Composer\Autoload\ClassLoader();
 *
 *     // register classes with namespaces
 *     $loader->add('Symfony\Component', __DIR__.'/component');
 *     $loader->add('Symfony',           __DIR__.'/framework');
 *
 *     // activate the autoloader
 *     $loader->register();
 *
 *     // to enable searching the include path (eg. for PEAR packages)
 *     $loader->setUseIncludePath(true);
 *
 * In this example, if you try to use a class in the Symfony\Component
 * namespace or one of its children (Symfony\Component\Console for instance),
 * the autoloader will first look for the class under the component/
 * directory, and it will then fallback to the framework/ directory if not
 * found before giving up.
 *
 * This class is loosely based on the Symfony UniversalClassLoader.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @see    http://www.php-fig.org/psr/psr-0/
 * @see    http://www.php-fig.org/psr/psr-4/
 */
class ClassLoader
{
    // PSR-4
    private $prefixLengthsPsr4 = array();
    private $prefixDirsPsr4 = array();
    private $fallbackDirsPsr4 = array();

    // PSR-0
    private $prefixesPsr0 = array();
    private $fallbackDirsPsr0 = array();

    private $useIncludePath = false;
    private $classMap = array();
    private $classMapAuthoritative = false;
    private $missingClasses = array();

    public function getPrefixes()
    {
        if (!empty($this->prefixesPsr0)) {
            return call_user_func_array('array_merge', $this->prefixesPsr0);
        }

        return array();
    }

    public function getPrefixesPsr4()
    {
        return $this->prefixDirsPsr4;
    }

    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

    public function getClassMap()
    {
        return $this->classMap;
    }

    /**
     * @param array $classMap Class to filename map
     */
    public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix, either
     * appending or prepending to the ones previously set for this prefix.
     *
     * @param string       $prefix  The prefix
     * @param array|string $paths   The PSR-0 root directories
     * @param bool         $prepend Whether to prepend the directories
     */
    public function add($prefix, $paths, $prepend = false)
    {
        if (!$prefix) {
            if ($prepend) {
                $this->fallbackDirsPsr0 = array_merge(
                    (array) $paths,
                    $this->fallbackDirsPsr0
                );
            } else {
                $this->fallbackDirsPsr0 = array_merge(
                    $this->fallbackDirsPsr0,
                    (array) $paths
                );
            }

            return;
        }

        $first = $prefix[0];
        if (!isset($this->prefixesPsr0[$first][$prefix])) {
            $this->prefixesPsr0[$first][$prefix] = (array) $paths;

            return;
        }
        if ($prepend) {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                (array) $paths,
                $this->prefixesPsr0[$first][$prefix]
            );
        } else {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $this->prefixesPsr0[$first][$prefix],
                (array) $paths
            );
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace, either
     * appending or prepending to the ones previously set for this namespace.
     *
     * @param string       $prefix  The prefix/namespace, with trailing '\\'
     * @param array|string $paths   The PSR-4 base directories
     * @param bool         $prepend Whether to prepend the directories
     *
     * @throws \InvalidArgumentException
     */
    public function addPsr4($prefix, $paths, $prepend = false)
    {
        if (!$prefix) {
            // Register directories for the root namespace.
            if ($prepend) {
                $this->fallbackDirsPsr4 = array_merge(
                    (array) $paths,
                    $this->fallbackDirsPsr4
                );
            } else {
                $this->fallbackDirsPsr4 = array_merge(
                    $this->fallbackDirsPsr4,
                    (array) $paths
                );
            }
        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
            // Register directories for a new namespace.
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        } elseif ($prepend) {
            // Prepend directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                (array) $paths,
                $this->prefixDirsPsr4[$prefix]
            );
        } else {
            // Append directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $this->prefixDirsPsr4[$prefix],
                (array) $paths
            );
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix,
     * replacing any others previously set for this prefix.
     *
     * @param string       $prefix The prefix
     * @param array|string $paths  The PSR-0 base directories
     */
    public function set($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr0 = (array) $paths;
        } else {
            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace,
     * replacing any others previously set for this namespace.
     *
     * @param string       $prefix The prefix/namespace, with trailing '\\'
     * @param array|string $paths  The PSR-4 base directories
     *
     * @throws \InvalidArgumentException
     */
    public function setPsr4($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr4 = (array) $paths;
        } else {
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        }
    }

    /**
     * Turns on searching the include path for class files.
     *
     * @param bool $useIncludePath
     */
    public function setUseIncludePath($useIncludePath)
    {
        $this->useIncludePath = $useIncludePath;
    }

    /**
     * Can be used to check if the autoloader uses the include path to check
     * for classes.
     *
     * @return bool
     */
    public function getUseIncludePath()
    {
        return $this->useIncludePath;
    }

    /**
     * Turns off searching the prefix and fallback directories for classes
     * that have not been registered with the class map.
     *
     * @param bool $classMapAuthoritative
     */
    public function setClassMapAuthoritative($classMapAuthoritative)
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
    }

    /**
     * Should class lookup fail if not found in the current class map?
     *
     * @return bool
     */
    public function isClassMapAuthoritative()
    {
        return $this->classMapAuthoritative;
    }

    /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

    /**
     * Unregisters this instance as an autoloader.
     */
    public function unregister()
    {
        spl_autoload_unregister(array($this, 'loadClass'));
    }

    /**
     * Loads the given class or interface.
     *
     * @param  string    $class The name of the class
     * @return bool|null True if loaded, null otherwise
     */
    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            includeFile($file);

            return true;
        }
    }

    /**
     * Finds the path to the file where the class is defined.
     *
     * @param string $class The name of the class
     *
     * @return string|false The path if found, false otherwise
     */
    public function findFile($class)
    {
        // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
        if ('\\' == $class[0]) {
            $class = substr($class, 1);
        }

        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
            return false;
        }

        $file = $this->findFileWithExtension($class, '.php');

        // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }

        if (false === $file) {
            // Remember that this class does not exist.
            $this->missingClasses[$class] = true;
        }

        return $file;
    }

    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

        // PSR-0 lookup
        if (false !== $pos = strrpos($class, '\\')) {
            // namespaced class name
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
        } else {
            // PEAR-like class name
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
        }

        if (isset($this->prefixesPsr0[$first])) {
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($dirs as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-0 fallback dirs
        foreach ($this->fallbackDirsPsr0 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                return $file;
            }
        }

        // PSR-0 include paths.
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
            return $file;
        }

        return false;
    }
}

/**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 */
function includeFile($file)
{
    include $file;
}
<?php
namespace GuzzleHttp;

use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Promise;
use GuzzleHttp\Psr7;
use Psr\Http\Message\UriInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * @method ResponseInterface get($uri, array $options = [])
 * @method ResponseInterface head($uri, array $options = [])
 * @method ResponseInterface put($uri, array $options = [])
 * @method ResponseInterface post($uri, array $options = [])
 * @method ResponseInterface patch($uri, array $options = [])
 * @method ResponseInterface delete($uri, array $options = [])
 * @method Promise\PromiseInterface getAsync($uri, array $options = [])
 * @method Promise\PromiseInterface headAsync($uri, array $options = [])
 * @method Promise\PromiseInterface putAsync($uri, array $options = [])
 * @method Promise\PromiseInterface postAsync($uri, array $options = [])
 * @method Promise\PromiseInterface patchAsync($uri, array $options = [])
 * @method Promise\PromiseInterface deleteAsync($uri, array $options = [])
 */
class Client implements ClientInterface
{
    /** @var array Default request options */
    private $config;

    /**
     * Clients accept an array of constructor parameters.
     *
     * Here's an example of creating a client using a base_uri and an array of
     * default request options to apply to each request:
     *
     *     $client = new Client([
     *         'base_uri'        => 'http://www.foo.com/1.0/',
     *         'timeout'         => 0,
     *         'allow_redirects' => false,
     *         'proxy'           => '192.168.16.1:10'
     *     ]);
     *
     * Client configuration settings include the following options:
     *
     * - handler: (callable) Function that transfers HTTP requests over the
     *   wire. The function is called with a Psr7\Http\Message\RequestInterface
     *   and array of transfer options, and must return a
     *   GuzzleHttp\Promise\PromiseInterface that is fulfilled with a
     *   Psr7\Http\Message\ResponseInterface on success. "handler" is a
     *   constructor only option that cannot be overridden in per/request
     *   options. If no handler is provided, a default handler will be created
     *   that enables all of the request options below by attaching all of the
     *   default middleware to the handler.
     * - base_uri: (string|UriInterface) Base URI of the client that is merged
     *   into relative URIs. Can be a string or instance of UriInterface.
     * - **: any request option
     *
     * @param array $config Client configuration settings.
     *
     * @see \GuzzleHttp\RequestOptions for a list of available request options.
     */
    public function __construct(array $config = [])
    {
        if (!isset($config['handler'])) {
            $config['handler'] = HandlerStack::create();
        }

        // Convert the base_uri to a UriInterface
        if (isset($config['base_uri'])) {
            $config['base_uri'] = Psr7\uri_for($config['base_uri']);
        }

        $this->configureDefaults($config);
    }

    public function __call($method, $args)
    {
        if (count($args) < 1) {
            throw new \InvalidArgumentException('Magic request methods require a URI and optional options array');
        }

        $uri = $args[0];
        $opts = isset($args[1]) ? $args[1] : [];

        return substr($method, -5) === 'Async'
            ? $this->requestAsync(substr($method, 0, -5), $uri, $opts)
            : $this->request($method, $uri, $opts);
    }

    public function sendAsync(RequestInterface $request, array $options = [])
    {
        // Merge the base URI into the request URI if needed.
        $options = $this->prepareDefaults($options);

        return $this->transfer(
            $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')),
            $options
        );
    }

    public function send(RequestInterface $request, array $options = [])
    {
        $options[RequestOptions::SYNCHRONOUS] = true;
        return $this->sendAsync($request, $options)->wait();
    }

    public function requestAsync($method, $uri = null, array $options = [])
    {
        $options = $this->prepareDefaults($options);
        // Remove request modifying parameter because it can be done up-front.
        $headers = isset($options['headers']) ? $options['headers'] : [];
        $body = isset($options['body']) ? $options['body'] : null;
        $version = isset($options['version']) ? $options['version'] : '1.1';
        // Merge the URI into the base URI.
        $uri = $this->buildUri($uri, $options);
        if (is_array($body)) {
            $this->invalidBody();
        }
        $request = new Psr7\Request($method, $uri, $headers, $body, $version);
        // Remove the option so that they are not doubly-applied.
        unset($options['headers'], $options['body'], $options['version']);

        return $this->transfer($request, $options);
    }

    public function request($method, $uri = null, array $options = [])
    {
        $options[RequestOptions::SYNCHRONOUS] = true;
        return $this->requestAsync($method, $uri, $options)->wait();
    }

    public function getConfig($option = null)
    {
        return $option === null
            ? $this->config
            : (isset($this->config[$option]) ? $this->config[$option] : null);
    }

    private function buildUri($uri, array $config)
    {
        if (!isset($config['base_uri'])) {
            return $uri instanceof UriInterface ? $uri : new Psr7\Uri($uri);
        }

        return Psr7\Uri::resolve(Psr7\uri_for($config['base_uri']), $uri);
    }

    /**
     * Configures the default options for a client.
     *
     * @param array $config
     */
    private function configureDefaults(array $config)
    {
        $defaults = [
            'allow_redirects' => RedirectMiddleware::$defaultSettings,
            'http_errors'     => true,
            'decode_content'  => true,
            'verify'          => true,
            'cookies'         => false
        ];

        // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set
        if ($proxy = getenv('HTTP_PROXY')) {
            $defaults['proxy']['http'] = $proxy;
        }

        if ($proxy = getenv('HTTPS_PROXY')) {
            $defaults['proxy']['https'] = $proxy;
        }

        if ($noProxy = getenv('NO_PROXY')) {
            $cleanedNoProxy = str_replace(' ', '', $noProxy);
            $defaults['proxy']['no'] = explode(',', $cleanedNoProxy);
        }

        $this->config = $config + $defaults;

        if (!empty($config['cookies']) && $config['cookies'] === true) {
            $this->config['cookies'] = new CookieJar();
        }

        // Add the default user-agent header.
        if (!isset($this->config['headers'])) {
            $this->config['headers'] = ['User-Agent' => default_user_agent()];
        } else {
            // Add the User-Agent header if one was not already set.
            foreach (array_keys($this->config['headers']) as $name) {
                if (strtolower($name) === 'user-agent') {
                    return;
                }
            }
            $this->config['headers']['User-Agent'] = default_user_agent();
        }
    }

    /**
     * Merges default options into the array.
     *
     * @param array $options Options to modify by reference
     *
     * @return array
     */
    private function prepareDefaults($options)
    {
        $defaults = $this->config;

        if (!empty($defaults['headers'])) {
            // Default headers are only added if they are not present.
            $defaults['_conditional'] = $defaults['headers'];
            unset($defaults['headers']);
        }

        // Special handling for headers is required as they are added as
        // conditional headers and as headers passed to a request ctor.
        if (array_key_exists('headers', $options)) {
            // Allows default headers to be unset.
            if ($options['headers'] === null) {
                $defaults['_conditional'] = null;
                unset($options['headers']);
            } elseif (!is_array($options['headers'])) {
                throw new \InvalidArgumentException('headers must be an array');
            }
        }

        // Shallow merge defaults underneath options.
        $result = $options + $defaults;

        // Remove null values.
        foreach ($result as $k => $v) {
            if ($v === null) {
                unset($result[$k]);
            }
        }

        return $result;
    }

    /**
     * Transfers the given request and applies request options.
     *
     * The URI of the request is not modified and the request options are used
     * as-is without merging in default options.
     *
     * @param RequestInterface $request
     * @param array            $options
     *
     * @return Promise\PromiseInterface
     */
    private function transfer(RequestInterface $request, array $options)
    {
        // save_to -> sink
        if (isset($options['save_to'])) {
            $options['sink'] = $options['save_to'];
            unset($options['save_to']);
        }

        // exceptions -> http_error
        if (isset($options['exceptions'])) {
            $options['http_errors'] = $options['exceptions'];
            unset($options['exceptions']);
        }

        $request = $this->applyOptions($request, $options);
        $handler = $options['handler'];

        try {
            return Promise\promise_for($handler($request, $options));
        } catch (\Exception $e) {
            return Promise\rejection_for($e);
        }
    }

    /**
     * Applies the array of request options to a request.
     *
     * @param RequestInterface $request
     * @param array            $options
     *
     * @return RequestInterface
     */
    private function applyOptions(RequestInterface $request, array &$options)
    {
        $modify = [];

        if (isset($options['form_params'])) {
            if (isset($options['multipart'])) {
                throw new \InvalidArgumentException('You cannot use '
                    . 'form_params and multipart at the same time. Use the '
                    . 'form_params option if you want to send application/'
                    . 'x-www-form-urlencoded requests, and the multipart '
                    . 'option to send multipart/form-data requests.');
            }
            $options['body'] = http_build_query($options['form_params'], '', '&');
            unset($options['form_params']);
            $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
        }

        if (isset($options['multipart'])) {
            $elements = $options['multipart'];
            unset($options['multipart']);
            $options['body'] = new Psr7\MultipartStream($elements);
        }

        if (!empty($options['decode_content'])
            && $options['decode_content'] !== true
        ) {
            $modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
        }

        if (isset($options['headers'])) {
            if (isset($modify['set_headers'])) {
                $modify['set_headers'] = $options['headers'] + $modify['set_headers'];
            } else {
                $modify['set_headers'] = $options['headers'];
            }
            unset($options['headers']);
        }

        if (isset($options['body'])) {
            if (is_array($options['body'])) {
                $this->invalidBody();
            }
            $modify['body'] = Psr7\stream_for($options['body']);
            unset($options['body']);
        }

        if (!empty($options['auth'])) {
            $value = $options['auth'];
            $type = is_array($value)
                ? (isset($value[2]) ? strtolower($value[2]) : 'basic')
                : $value;
            $config['auth'] = $value;
            switch (strtolower($type)) {
                case 'basic':
                    $modify['set_headers']['Authorization'] = 'Basic '
                        . base64_encode("$value[0]:$value[1]");
                    break;
                case 'digest':
                    // @todo: Do not rely on curl
                    $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST;
                    $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
                    break;
            }
        }

        if (isset($options['query'])) {
            $value = $options['query'];
            if (is_array($value)) {
                $value = http_build_query($value, null, '&', PHP_QUERY_RFC3986);
            }
            if (!is_string($value)) {
                throw new \InvalidArgumentException('query must be a string or array');
            }
            $modify['query'] = $value;
            unset($options['query']);
        }

        if (isset($options['json'])) {
            $jsonStr = \GuzzleHttp\json_encode($options['json']);
            $modify['body'] = Psr7\stream_for($jsonStr);
            $options['_conditional']['Content-Type'] = 'application/json';
            unset($options['json']);
        }

        $request = Psr7\modify_request($request, $modify);
        if ($request->getBody() instanceof Psr7\MultipartStream) {
            // Use a multipart/form-data POST if a Content-Type is not set.
            $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
                . $request->getBody()->getBoundary();
        }

        // Merge in conditional headers if they are not present.
        if (isset($options['_conditional'])) {
            // Build up the changes so it's in a single clone of the message.
            $modify = [];
            foreach ($options['_conditional'] as $k => $v) {
                if (!$request->hasHeader($k)) {
                    $modify['set_headers'][$k] = $v;
                }
            }
            $request = Psr7\modify_request($request, $modify);
            // Don't pass this internal value along to middleware/handlers.
            unset($options['_conditional']);
        }

        return $request;
    }

    private function invalidBody()
    {
        throw new \InvalidArgumentException('Passing in the "body" request '
            . 'option as an array to send a POST request has been deprecated. '
            . 'Please use the "form_params" request option to send a '
            . 'application/x-www-form-urlencoded request, or a the "multipart" '
            . 'request option to send a multipart/form-data request.');
    }
}
<?php
namespace GuzzleHttp;

use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;

/**
 * Client interface for sending HTTP requests.
 */
interface ClientInterface
{
    const VERSION = '6.2.0';

    /**
     * Send an HTTP request.
     *
     * @param RequestInterface $request Request to send
     * @param array            $options Request options to apply to the given
     *                                  request and to the transfer.
     *
     * @return ResponseInterface
     * @throws GuzzleException
     */
    public function send(RequestInterface $request, array $options = []);

    /**
     * Asynchronously send an HTTP request.
     *
     * @param RequestInterface $request Request to send
     * @param array            $options Request options to apply to the given
     *                                  request and to the transfer.
     *
     * @return PromiseInterface
     */
    public function sendAsync(RequestInterface $request, array $options = []);

    /**
     * Create and send an HTTP request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well.
     *
     * @param string                   $method  HTTP method.
     * @param string|UriInterface|null $uri     URI object or string (default null).
     * @param array                    $options Request options to apply.
     *
     * @return ResponseInterface
     * @throws GuzzleException
     */
    public function request($method, $uri = null, array $options = []);

    /**
     * Create and send an asynchronous HTTP request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well. Use an array to provide a URL
     * template and additional variables to use in the URL template expansion.
     *
     * @param string              $method  HTTP method
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     *
     * @return PromiseInterface
     */
    public function requestAsync($method, $uri, array $options = []);

    /**
     * Get a client configuration option.
     *
     * These options include default request options of the client, a "handler"
     * (if utilized by the concrete client), and a "base_uri" if utilized by
     * the concrete client.
     *
     * @param string|null $option The config option to retrieve.
     *
     * @return mixed
     */
    public function getConfig($option = null);
}
<?php
namespace GuzzleHttp\Cookie;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Cookie jar that stores cookies as an array
 */
class CookieJar implements CookieJarInterface
{
    /** @var SetCookie[] Loaded cookie data */
    private $cookies = [];

    /** @var bool */
    private $strictMode;

    /**
     * @param bool $strictMode   Set to true to throw exceptions when invalid
     *                           cookies are added to the cookie jar.
     * @param array $cookieArray Array of SetCookie objects or a hash of
     *                           arrays that can be used with the SetCookie
     *                           constructor
     */
    public function __construct($strictMode = false, $cookieArray = [])
    {
        $this->strictMode = $strictMode;

        foreach ($cookieArray as $cookie) {
            if (!($cookie instanceof SetCookie)) {
                $cookie = new SetCookie($cookie);
            }
            $this->setCookie($cookie);
        }
    }

    /**
     * Create a new Cookie jar from an associative array and domain.
     *
     * @param array  $cookies Cookies to create the jar from
     * @param string $domain  Domain to set the cookies to
     *
     * @return self
     */
    public static function fromArray(array $cookies, $domain)
    {
        $cookieJar = new self();
        foreach ($cookies as $name => $value) {
            $cookieJar->setCookie(new SetCookie([
                'Domain'  => $domain,
                'Name'    => $name,
                'Value'   => $value,
                'Discard' => true
            ]));
        }

        return $cookieJar;
    }

    /**
     * @deprecated
     */
    public static function getCookieValue($value)
    {
        return $value;
    }

    /**
     * Evaluate if this cookie should be persisted to storage
     * that survives between requests.
     *
     * @param SetCookie $cookie Being evaluated.
     * @param bool $allowSessionCookies If we should persist session cookies
     * @return bool
     */
    public static function shouldPersist(
        SetCookie $cookie,
        $allowSessionCookies = false
    ) {
        if ($cookie->getExpires() || $allowSessionCookies) {
            if (!$cookie->getDiscard()) {
                return true;
            }
        }

        return false;
    }

    public function toArray()
    {
        return array_map(function (SetCookie $cookie) {
            return $cookie->toArray();
        }, $this->getIterator()->getArrayCopy());
    }

    public function clear($domain = null, $path = null, $name = null)
    {
        if (!$domain) {
            $this->cookies = [];
            return;
        } elseif (!$path) {
            $this->cookies = array_filter(
                $this->cookies,
                function (SetCookie $cookie) use ($path, $domain) {
                    return !$cookie->matchesDomain($domain);
                }
            );
        } elseif (!$name) {
            $this->cookies = array_filter(
                $this->cookies,
                function (SetCookie $cookie) use ($path, $domain) {
                    return !($cookie->matchesPath($path) &&
                        $cookie->matchesDomain($domain));
                }
            );
        } else {
            $this->cookies = array_filter(
                $this->cookies,
                function (SetCookie $cookie) use ($path, $domain, $name) {
                    return !($cookie->getName() == $name &&
                        $cookie->matchesPath($path) &&
                        $cookie->matchesDomain($domain));
                }
            );
        }
    }

    public function clearSessionCookies()
    {
        $this->cookies = array_filter(
            $this->cookies,
            function (SetCookie $cookie) {
                return !$cookie->getDiscard() && $cookie->getExpires();
            }
        );
    }

    public function setCookie(SetCookie $cookie)
    {
        // If the name string is empty (but not 0), ignore the set-cookie
        // string entirely.
        $name = $cookie->getName();
        if (!$name && $name !== '0') {
            return false;
        }

        // Only allow cookies with set and valid domain, name, value
        $result = $cookie->validate();
        if ($result !== true) {
            if ($this->strictMode) {
                throw new \RuntimeException('Invalid cookie: ' . $result);
            } else {
                $this->removeCookieIfEmpty($cookie);
                return false;
            }
        }

        // Resolve conflicts with previously set cookies
        foreach ($this->cookies as $i => $c) {

            // Two cookies are identical, when their path, and domain are
            // identical.
            if ($c->getPath() != $cookie->getPath() ||
                $c->getDomain() != $cookie->getDomain() ||
                $c->getName() != $cookie->getName()
            ) {
                continue;
            }

            // The previously set cookie is a discard cookie and this one is
            // not so allow the new cookie to be set
            if (!$cookie->getDiscard() && $c->getDiscard()) {
                unset($this->cookies[$i]);
                continue;
            }

            // If the new cookie's expiration is further into the future, then
            // replace the old cookie
            if ($cookie->getExpires() > $c->getExpires()) {
                unset($this->cookies[$i]);
                continue;
            }

            // If the value has changed, we better change it
            if ($cookie->getValue() !== $c->getValue()) {
                unset($this->cookies[$i]);
                continue;
            }

            // The cookie exists, so no need to continue
            return false;
        }

        $this->cookies[] = $cookie;

        return true;
    }

    public function count()
    {
        return count($this->cookies);
    }

    public function getIterator()
    {
        return new \ArrayIterator(array_values($this->cookies));
    }

    public function extractCookies(
        RequestInterface $request,
        ResponseInterface $response
    ) {
        if ($cookieHeader = $response->getHeader('Set-Cookie')) {
            foreach ($cookieHeader as $cookie) {
                $sc = SetCookie::fromString($cookie);
                if (!$sc->getDomain()) {
                    $sc->setDomain($request->getUri()->getHost());
                }
                $this->setCookie($sc);
            }
        }
    }

    public function withCookieHeader(RequestInterface $request)
    {
        $values = [];
        $uri = $request->getUri();
        $scheme = $uri->getScheme();
        $host = $uri->getHost();
        $path = $uri->getPath() ?: '/';

        foreach ($this->cookies as $cookie) {
            if ($cookie->matchesPath($path) &&
                $cookie->matchesDomain($host) &&
                !$cookie->isExpired() &&
                (!$cookie->getSecure() || $scheme == 'https')
            ) {
                $values[] = $cookie->getName() . '='
                    . $cookie->getValue();
            }
        }

        return $values
            ? $request->withHeader('Cookie', implode('; ', $values))
            : $request;
    }

    /**
     * If a cookie already exists and the server asks to set it again with a
     * null value, the cookie must be deleted.
     *
     * @param SetCookie $cookie
     */
    private function removeCookieIfEmpty(SetCookie $cookie)
    {
        $cookieValue = $cookie->getValue();
        if ($cookieValue === null || $cookieValue === '') {
            $this->clear(
                $cookie->getDomain(),
                $cookie->getPath(),
                $cookie->getName()
            );
        }
    }
}
<?php
namespace GuzzleHttp\Cookie;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Stores HTTP cookies.
 *
 * It extracts cookies from HTTP requests, and returns them in HTTP responses.
 * CookieJarInterface instances automatically expire contained cookies when
 * necessary. Subclasses are also responsible for storing and retrieving
 * cookies from a file, database, etc.
 *
 * @link http://docs.python.org/2/library/cookielib.html Inspiration
 */
interface CookieJarInterface extends \Countable, \IteratorAggregate
{
    /**
     * Create a request with added cookie headers.
     *
     * If no matching cookies are found in the cookie jar, then no Cookie
     * header is added to the request and the same request is returned.
     *
     * @param RequestInterface $request Request object to modify.
     *
     * @return RequestInterface returns the modified request.
     */
    public function withCookieHeader(RequestInterface $request);

    /**
     * Extract cookies from an HTTP response and store them in the CookieJar.
     *
     * @param RequestInterface  $request  Request that was sent
     * @param ResponseInterface $response Response that was received
     */
    public function extractCookies(
        RequestInterface $request,
        ResponseInterface $response
    );

    /**
     * Sets a cookie in the cookie jar.
     *
     * @param SetCookie $cookie Cookie to set.
     *
     * @return bool Returns true on success or false on failure
     */
    public function setCookie(SetCookie $cookie);

    /**
     * Remove cookies currently held in the cookie jar.
     *
     * Invoking this method without arguments will empty the whole cookie jar.
     * If given a $domain argument only cookies belonging to that domain will
     * be removed. If given a $domain and $path argument, cookies belonging to
     * the specified path within that domain are removed. If given all three
     * arguments, then the cookie with the specified name, path and domain is
     * removed.
     *
     * @param string $domain Clears cookies matching a domain
     * @param string $path   Clears cookies matching a domain and path
     * @param string $name   Clears cookies matching a domain, path, and name
     *
     * @return CookieJarInterface
     */
    public function clear($domain = null, $path = null, $name = null);

    /**
     * Discard all sessions cookies.
     *
     * Removes cookies that don't have an expire field or a have a discard
     * field set to true. To be called when the user agent shuts down according
     * to RFC 2965.
     */
    public function clearSessionCookies();

    /**
     * Converts the cookie jar to an array.
     *
     * @return array
     */
    public function toArray();
}
<?php
namespace GuzzleHttp\Cookie;

/**
 * Persists non-session cookies using a JSON formatted file
 */
class FileCookieJar extends CookieJar
{
    /** @var string filename */
    private $filename;

    /** @var bool Control whether to persist session cookies or not. */
    private $storeSessionCookies;

    /**
     * Create a new FileCookieJar object
     *
     * @param string $cookieFile        File to store the cookie data
     * @param bool $storeSessionCookies Set to true to store session cookies
     *                                  in the cookie jar.
     *
     * @throws \RuntimeException if the file cannot be found or created
     */
    public function __construct($cookieFile, $storeSessionCookies = false)
    {
        $this->filename = $cookieFile;
        $this->storeSessionCookies = $storeSessionCookies;

        if (file_exists($cookieFile)) {
            $this->load($cookieFile);
        }
    }

    /**
     * Saves the file when shutting down
     */
    public function __destruct()
    {
        $this->save($this->filename);
    }

    /**
     * Saves the cookies to a file.
     *
     * @param string $filename File to save
     * @throws \RuntimeException if the file cannot be found or created
     */
    public function save($filename)
    {
        $json = [];
        foreach ($this as $cookie) {
            /** @var SetCookie $cookie */
            if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
                $json[] = $cookie->toArray();
            }
        }

        $jsonStr = \GuzzleHttp\json_encode($json);
        if (false === file_put_contents($filename, $jsonStr)) {
            throw new \RuntimeException("Unable to save file {$filename}");
        }
    }

    /**
     * Load cookies from a JSON formatted file.
     *
     * Old cookies are kept unless overwritten by newly loaded ones.
     *
     * @param string $filename Cookie file to load.
     * @throws \RuntimeException if the file cannot be loaded.
     */
    public function load($filename)
    {
        $json = file_get_contents($filename);
        if (false === $json) {
            throw new \RuntimeException("Unable to load file {$filename}");
        } elseif ($json === '') {
            return;
        }

        $data = \GuzzleHttp\json_decode($json, true);
        if (is_array($data)) {
            foreach (json_decode($json, true) as $cookie) {
                $this->setCookie(new SetCookie($cookie));
            }
        } elseif (strlen($data)) {
            throw new \RuntimeException("Invalid cookie file: {$filename}");
        }
    }
}
<?php
namespace GuzzleHttp\Cookie;

/**
 * Persists cookies in the client session
 */
class SessionCookieJar extends CookieJar
{
    /** @var string session key */
    private $sessionKey;
    
    /** @var bool Control whether to persist session cookies or not. */
    private $storeSessionCookies;

    /**
     * Create a new SessionCookieJar object
     *
     * @param string $sessionKey        Session key name to store the cookie 
     *                                  data in session
     * @param bool $storeSessionCookies Set to true to store session cookies
     *                                  in the cookie jar.
     */
    public function __construct($sessionKey, $storeSessionCookies = false)
    {
        $this->sessionKey = $sessionKey;
        $this->storeSessionCookies = $storeSessionCookies;
        $this->load();
    }

    /**
     * Saves cookies to session when shutting down
     */
    public function __destruct()
    {
        $this->save();
    }

    /**
     * Save cookies to the client session
     */
    public function save()
    {
        $json = [];
        foreach ($this as $cookie) {
            /** @var SetCookie $cookie */
            if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
                $json[] = $cookie->toArray();
            }
        }

        $_SESSION[$this->sessionKey] = json_encode($json);
    }

    /**
     * Load the contents of the client session into the data array
     */
    protected function load()
    {
        $cookieJar = isset($_SESSION[$this->sessionKey])
            ? $_SESSION[$this->sessionKey]
            : null;

        $data = json_decode($cookieJar, true);
        if (is_array($data)) {
            foreach ($data as $cookie) {
                $this->setCookie(new SetCookie($cookie));
            }
        } elseif (strlen($data)) {
            throw new \RuntimeException("Invalid cookie data");
        }
    }
}
<?php
namespace GuzzleHttp\Cookie;

/**
 * Set-Cookie object
 */
class SetCookie
{
    /** @var array */
    private static $defaults = [
        'Name'     => null,
        'Value'    => null,
        'Domain'   => null,
        'Path'     => '/',
        'Max-Age'  => null,
        'Expires'  => null,
        'Secure'   => false,
        'Discard'  => false,
        'HttpOnly' => false
    ];

    /** @var array Cookie data */
    private $data;

    /**
     * Create a new SetCookie object from a string
     *
     * @param string $cookie Set-Cookie header string
     *
     * @return self
     */
    public static function fromString($cookie)
    {
        // Create the default return array
        $data = self::$defaults;
        // Explode the cookie string using a series of semicolons
        $pieces = array_filter(array_map('trim', explode(';', $cookie)));
        // The name of the cookie (first kvp) must include an equal sign.
        if (empty($pieces) || !strpos($pieces[0], '=')) {
            return new self($data);
        }

        // Add the cookie pieces into the parsed data array
        foreach ($pieces as $part) {

            $cookieParts = explode('=', $part, 2);
            $key = trim($cookieParts[0]);
            $value = isset($cookieParts[1])
                ? trim($cookieParts[1], " \n\r\t\0\x0B")
                : true;

            // Only check for non-cookies when cookies have been found
            if (empty($data['Name'])) {
                $data['Name'] = $key;
                $data['Value'] = $value;
            } else {
                foreach (array_keys(self::$defaults) as $search) {
                    if (!strcasecmp($search, $key)) {
                        $data[$search] = $value;
                        continue 2;
                    }
                }
                $data[$key] = $value;
            }
        }

        return new self($data);
    }

    /**
     * @param array $data Array of cookie data provided by a Cookie parser
     */
    public function __construct(array $data = [])
    {
        $this->data = array_replace(self::$defaults, $data);
        // Extract the Expires value and turn it into a UNIX timestamp if needed
        if (!$this->getExpires() && $this->getMaxAge()) {
            // Calculate the Expires date
            $this->setExpires(time() + $this->getMaxAge());
        } elseif ($this->getExpires() && !is_numeric($this->getExpires())) {
            $this->setExpires($this->getExpires());
        }
    }

    public function __toString()
    {
        $str = $this->data['Name'] . '=' . $this->data['Value'] . '; ';
        foreach ($this->data as $k => $v) {
            if ($k != 'Name' && $k != 'Value' && $v !== null && $v !== false) {
                if ($k == 'Expires') {
                    $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; ';
                } else {
                    $str .= ($v === true ? $k : "{$k}={$v}") . '; ';
                }
            }
        }

        return rtrim($str, '; ');
    }

    public function toArray()
    {
        return $this->data;
    }

    /**
     * Get the cookie name
     *
     * @return string
     */
    public function getName()
    {
        return $this->data['Name'];
    }

    /**
     * Set the cookie name
     *
     * @param string $name Cookie name
     */
    public function setName($name)
    {
        $this->data['Name'] = $name;
    }

    /**
     * Get the cookie value
     *
     * @return string
     */
    public function getValue()
    {
        return $this->data['Value'];
    }

    /**
     * Set the cookie value
     *
     * @param string $value Cookie value
     */
    public function setValue($value)
    {
        $this->data['Value'] = $value;
    }

    /**
     * Get the domain
     *
     * @return string|null
     */
    public function getDomain()
    {
        return $this->data['Domain'];
    }

    /**
     * Set the domain of the cookie
     *
     * @param string $domain
     */
    public function setDomain($domain)
    {
        $this->data['Domain'] = $domain;
    }

    /**
     * Get the path
     *
     * @return string
     */
    public function getPath()
    {
        return $this->data['Path'];
    }

    /**
     * Set the path of the cookie
     *
     * @param string $path Path of the cookie
     */
    public function setPath($path)
    {
        $this->data['Path'] = $path;
    }

    /**
     * Maximum lifetime of the cookie in seconds
     *
     * @return int|null
     */
    public function getMaxAge()
    {
        return $this->data['Max-Age'];
    }

    /**
     * Set the max-age of the cookie
     *
     * @param int $maxAge Max age of the cookie in seconds
     */
    public function setMaxAge($maxAge)
    {
        $this->data['Max-Age'] = $maxAge;
    }

    /**
     * The UNIX timestamp when the cookie Expires
     *
     * @return mixed
     */
    public function getExpires()
    {
        return $this->data['Expires'];
    }

    /**
     * Set the unix timestamp for which the cookie will expire
     *
     * @param int $timestamp Unix timestamp
     */
    public function setExpires($timestamp)
    {
        $this->data['Expires'] = is_numeric($timestamp)
            ? (int) $timestamp
            : strtotime($timestamp);
    }

    /**
     * Get whether or not this is a secure cookie
     *
     * @return null|bool
     */
    public function getSecure()
    {
        return $this->data['Secure'];
    }

    /**
     * Set whether or not the cookie is secure
     *
     * @param bool $secure Set to true or false if secure
     */
    public function setSecure($secure)
    {
        $this->data['Secure'] = $secure;
    }

    /**
     * Get whether or not this is a session cookie
     *
     * @return null|bool
     */
    public function getDiscard()
    {
        return $this->data['Discard'];
    }

    /**
     * Set whether or not this is a session cookie
     *
     * @param bool $discard Set to true or false if this is a session cookie
     */
    public function setDiscard($discard)
    {
        $this->data['Discard'] = $discard;
    }

    /**
     * Get whether or not this is an HTTP only cookie
     *
     * @return bool
     */
    public function getHttpOnly()
    {
        return $this->data['HttpOnly'];
    }

    /**
     * Set whether or not this is an HTTP only cookie
     *
     * @param bool $httpOnly Set to true or false if this is HTTP only
     */
    public function setHttpOnly($httpOnly)
    {
        $this->data['HttpOnly'] = $httpOnly;
    }

    /**
     * Check if the cookie matches a path value.
     *
     * A request-path path-matches a given cookie-path if at least one of
     * the following conditions holds:
     *
     * - The cookie-path and the request-path are identical.
     * - The cookie-path is a prefix of the request-path, and the last
     *   character of the cookie-path is %x2F ("/").
     * - The cookie-path is a prefix of the request-path, and the first
     *   character of the request-path that is not included in the cookie-
     *   path is a %x2F ("/") character.
     *
     * @param string $requestPath Path to check against
     *
     * @return bool
     */
    public function matchesPath($requestPath)
    {
        $cookiePath = $this->getPath();

        // Match on exact matches or when path is the default empty "/"
        if ($cookiePath == '/' || $cookiePath == $requestPath) {
            return true;
        }

        // Ensure that the cookie-path is a prefix of the request path.
        if (0 !== strpos($requestPath, $cookiePath)) {
            return false;
        }

        // Match if the last character of the cookie-path is "/"
        if (substr($cookiePath, -1, 1) == '/') {
            return true;
        }

        // Match if the first character not included in cookie path is "/"
        return substr($requestPath, strlen($cookiePath), 1) == '/';
    }

    /**
     * Check if the cookie matches a domain value
     *
     * @param string $domain Domain to check against
     *
     * @return bool
     */
    public function matchesDomain($domain)
    {
        // Remove the leading '.' as per spec in RFC 6265.
        // http://tools.ietf.org/html/rfc6265#section-5.2.3
        $cookieDomain = ltrim($this->getDomain(), '.');

        // Domain not set or exact match.
        if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) {
            return true;
        }

        // Matching the subdomain according to RFC 6265.
        // http://tools.ietf.org/html/rfc6265#section-5.1.3
        if (filter_var($domain, FILTER_VALIDATE_IP)) {
            return false;
        }

        return (bool) preg_match('/\.' . preg_quote($cookieDomain) . '$/', $domain);
    }

    /**
     * Check if the cookie is expired
     *
     * @return bool
     */
    public function isExpired()
    {
        return $this->getExpires() && time() > $this->getExpires();
    }

    /**
     * Check if the cookie is valid according to RFC 6265
     *
     * @return bool|string Returns true if valid or an error message if invalid
     */
    public function validate()
    {
        // Names must not be empty, but can be 0
        $name = $this->getName();
        if (empty($name) && !is_numeric($name)) {
            return 'The cookie name must not be empty';
        }

        // Check if any of the invalid characters are present in the cookie name
        if (preg_match(
            '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/',
            $name)
        ) {
            return 'Cookie name must not contain invalid characters: ASCII '
                . 'Control characters (0-31;127), space, tab and the '
                . 'following characters: ()<>@,;:\"/?={}';
        }

        // Value must not be empty, but can be 0
        $value = $this->getValue();
        if (empty($value) && !is_numeric($value)) {
            return 'The cookie value must not be empty';
        }

        // Domains must not be empty, but can be 0
        // A "0" is not a valid internet domain, but may be used as server name
        // in a private network.
        $domain = $this->getDomain();
        if (empty($domain) && !is_numeric($domain)) {
            return 'The cookie domain must not be empty';
        }

        return true;
    }
}
<?php
namespace GuzzleHttp\Exception;

/**
 * Exception when an HTTP error occurs (4xx or 5xx error)
 */
class BadResponseException extends RequestException {}
<?php
namespace GuzzleHttp\Exception;

/**
 * Exception when a client error is encountered (4xx codes)
 */
class ClientException extends BadResponseException {}
<?php
namespace GuzzleHttp\Exception;

use Psr\Http\Message\RequestInterface;

/**
 * Exception thrown when a connection cannot be established.
 *
 * Note that no response is present for a ConnectException
 */
class ConnectException extends RequestException
{
    public function __construct(
        $message,
        RequestInterface $request,
        \Exception $previous = null,
        array $handlerContext = []
    ) {
        parent::__construct($message, $request, null, $previous, $handlerContext);
    }

    /**
     * @return null
     */
    public function getResponse()
    {
        return null;
    }

    /**
     * @return bool
     */
    public function hasResponse()
    {
        return false;
    }
}
<?php
namespace GuzzleHttp\Exception;

interface GuzzleException {}
<?php
namespace GuzzleHttp\Exception;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Promise\PromiseInterface;

/**
 * HTTP Request exception
 */
class RequestException extends TransferException
{
    /** @var RequestInterface */
    private $request;

    /** @var ResponseInterface */
    private $response;

    /** @var array */
    private $handlerContext;

    public function __construct(
        $message,
        RequestInterface $request,
        ResponseInterface $response = null,
        \Exception $previous = null,
        array $handlerContext = []
    ) {
        // Set the code of the exception if the response is set and not future.
        $code = $response && !($response instanceof PromiseInterface)
            ? $response->getStatusCode()
            : 0;
        parent::__construct($message, $code, $previous);
        $this->request = $request;
        $this->response = $response;
        $this->handlerContext = $handlerContext;
    }

    /**
     * Wrap non-RequestExceptions with a RequestException
     *
     * @param RequestInterface $request
     * @param \Exception       $e
     *
     * @return RequestException
     */
    public static function wrapException(RequestInterface $request, \Exception $e)
    {
        return $e instanceof RequestException
            ? $e
            : new RequestException($e->getMessage(), $request, null, $e);
    }

    /**
     * Factory method to create a new exception with a normalized error message
     *
     * @param RequestInterface  $request  Request
     * @param ResponseInterface $response Response received
     * @param \Exception        $previous Previous exception
     * @param array             $ctx      Optional handler context.
     *
     * @return self
     */
    public static function create(
        RequestInterface $request,
        ResponseInterface $response = null,
        \Exception $previous = null,
        array $ctx = []
    ) {
        if (!$response) {
            return new self(
                'Error completing request',
                $request,
                null,
                $previous,
                $ctx
            );
        }

        $level = floor($response->getStatusCode() / 100);
        if ($level == '4') {
            $label = 'Client error';
            $className = __NAMESPACE__ . '\\ClientException';
        } elseif ($level == '5') {
            $label = 'Server error';
            $className = __NAMESPACE__ . '\\ServerException';
        } else {
            $label = 'Unsuccessful request';
            $className = __CLASS__;
        }

        // Server Error: `GET /` resulted in a `404 Not Found` response:
        // <html> ... (truncated)
        $message = sprintf(
            '%s: `%s` resulted in a `%s` response',
            $label,
            $request->getMethod() . ' ' . $request->getUri(),
            $response->getStatusCode() . ' ' . $response->getReasonPhrase()
        );

        $summary = static::getResponseBodySummary($response);

        if ($summary !== null) {
            $message .= ":\n{$summary}\n";
        }

        return new $className($message, $request, $response, $previous, $ctx);
    }

    /**
     * Get a short summary of the response
     *
     * Will return `null` if the response is not printable.
     *
     * @param ResponseInterface $response
     *
     * @return string|null
     */
    public static function getResponseBodySummary(ResponseInterface $response)
    {
        $body = $response->getBody();

        if (!$body->isSeekable()) {
            return null;
        }

        $size = $body->getSize();
        $summary = $body->read(120);
        $body->rewind();

        if ($size > 120) {
            $summary .= ' (truncated...)';
        }

        // Matches any printable character, including unicode characters:
        // letters, marks, numbers, punctuation, spacing, and separators.
        if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) {
            return null;
        }

        return $summary;
    }

    /**
     * Get the request that caused the exception
     *
     * @return RequestInterface
     */
    public function getRequest()
    {
        return $this->request;
    }

    /**
     * Get the associated response
     *
     * @return ResponseInterface|null
     */
    public function getResponse()
    {
        return $this->response;
    }

    /**
     * Check if a response was received
     *
     * @return bool
     */
    public function hasResponse()
    {
        return $this->response !== null;
    }

    /**
     * Get contextual information about the error from the underlying handler.
     *
     * The contents of this array will vary depending on which handler you are
     * using. It may also be just an empty array. Relying on this data will
     * couple you to a specific handler, but can give more debug information
     * when needed.
     *
     * @return array
     */
    public function getHandlerContext()
    {
        return $this->handlerContext;
    }
}
<?php
namespace GuzzleHttp\Exception;

use Psr\Http\Message\StreamInterface;

/**
 * Exception thrown when a seek fails on a stream.
 */
class SeekException extends \RuntimeException implements GuzzleException
{
    private $stream;

    public function __construct(StreamInterface $stream, $pos = 0, $msg = '')
    {
        $this->stream = $stream;
        $msg = $msg ?: 'Could not seek the stream to position ' . $pos;
        parent::__construct($msg);
    }

    /**
     * @return StreamInterface
     */
    public function getStream()
    {
        return $this->stream;
    }
}
<?php
namespace GuzzleHttp\Exception;

/**
 * Exception when a server error is encountered (5xx codes)
 */
class ServerException extends BadResponseException {}
<?php
namespace GuzzleHttp\Exception;

class TooManyRedirectsException extends RequestException {}
<?php
namespace GuzzleHttp\Exception;

class TransferException extends \RuntimeException implements GuzzleException {}
<?php
namespace GuzzleHttp;

use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\Handler\CurlMultiHandler;
use GuzzleHttp\Handler\Proxy;
use GuzzleHttp\Handler\StreamHandler;
use Psr\Http\Message\StreamInterface;

/**
 * Expands a URI template
 *
 * @param string $template  URI template
 * @param array  $variables Template variables
 *
 * @return string
 */
function uri_template($template, array $variables)
{
    if (extension_loaded('uri_template')) {
        // @codeCoverageIgnoreStart
        return \uri_template($template, $variables);
        // @codeCoverageIgnoreEnd
    }

    static $uriTemplate;
    if (!$uriTemplate) {
        $uriTemplate = new UriTemplate();
    }

    return $uriTemplate->expand($template, $variables);
}

/**
 * Debug function used to describe the provided value type and class.
 *
 * @param mixed $input
 *
 * @return string Returns a string containing the type of the variable and
 *                if a class is provided, the class name.
 */
function describe_type($input)
{
    switch (gettype($input)) {
        case 'object':
            return 'object(' . get_class($input) . ')';
        case 'array':
            return 'array(' . count($input) . ')';
        default:
            ob_start();
            var_dump($input);
            // normalize float vs double
            return str_replace('double(', 'float(', rtrim(ob_get_clean()));
    }
}

/**
 * Parses an array of header lines into an associative array of headers.
 *
 * @param array $lines Header lines array of strings in the following
 *                     format: "Name: Value"
 * @return array
 */
function headers_from_lines($lines)
{
    $headers = [];

    foreach ($lines as $line) {
        $parts = explode(':', $line, 2);
        $headers[trim($parts[0])][] = isset($parts[1])
            ? trim($parts[1])
            : null;
    }

    return $headers;
}

/**
 * Returns a debug stream based on the provided variable.
 *
 * @param mixed $value Optional value
 *
 * @return resource
 */
function debug_resource($value = null)
{
    if (is_resource($value)) {
        return $value;
    } elseif (defined('STDOUT')) {
        return STDOUT;
    }

    return fopen('php://output', 'w');
}

/**
 * Chooses and creates a default handler to use based on the environment.
 *
 * The returned handler is not wrapped by any default middlewares.
 *
 * @throws \RuntimeException if no viable Handler is available.
 * @return callable Returns the best handler for the given system.
 */
function choose_handler()
{
    $handler = null;
    if (function_exists('curl_multi_exec') && function_exists('curl_exec')) {
        $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler());
    } elseif (function_exists('curl_exec')) {
        $handler = new CurlHandler();
    } elseif (function_exists('curl_multi_exec')) {
        $handler = new CurlMultiHandler();
    }

    if (ini_get('allow_url_fopen')) {
        $handler = $handler
            ? Proxy::wrapStreaming($handler, new StreamHandler())
            : new StreamHandler();
    } elseif (!$handler) {
        throw new \RuntimeException('GuzzleHttp requires cURL, the '
            . 'allow_url_fopen ini setting, or a custom HTTP handler.');
    }

    return $handler;
}

/**
 * Get the default User-Agent string to use with Guzzle
 *
 * @return string
 */
function default_user_agent()
{
    static $defaultAgent = '';

    if (!$defaultAgent) {
        $defaultAgent = 'GuzzleHttp/' . Client::VERSION;
        if (extension_loaded('curl') && function_exists('curl_version')) {
            $defaultAgent .= ' curl/' . \curl_version()['version'];
        }
        $defaultAgent .= ' PHP/' . PHP_VERSION;
    }

    return $defaultAgent;
}

/**
 * Returns the default cacert bundle for the current system.
 *
 * First, the openssl.cafile and curl.cainfo php.ini settings are checked.
 * If those settings are not configured, then the common locations for
 * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
 * and Windows are checked. If any of these file locations are found on
 * disk, they will be utilized.
 *
 * Note: the result of this function is cached for subsequent calls.
 *
 * @return string
 * @throws \RuntimeException if no bundle can be found.
 */
function default_ca_bundle()
{
    static $cached = null;
    static $cafiles = [
        // Red Hat, CentOS, Fedora (provided by the ca-certificates package)
        '/etc/pki/tls/certs/ca-bundle.crt',
        // Ubuntu, Debian (provided by the ca-certificates package)
        '/etc/ssl/certs/ca-certificates.crt',
        // FreeBSD (provided by the ca_root_nss package)
        '/usr/local/share/certs/ca-root-nss.crt',
        // OS X provided by homebrew (using the default path)
        '/usr/local/etc/openssl/cert.pem',
        // Google app engine
        '/etc/ca-certificates.crt',
        // Windows?
        'C:\\windows\\system32\\curl-ca-bundle.crt',
        'C:\\windows\\curl-ca-bundle.crt',
    ];

    if ($cached) {
        return $cached;
    }

    if ($ca = ini_get('openssl.cafile')) {
        return $cached = $ca;
    }

    if ($ca = ini_get('curl.cainfo')) {
        return $cached = $ca;
    }

    foreach ($cafiles as $filename) {
        if (file_exists($filename)) {
            return $cached = $filename;
        }
    }

    throw new \RuntimeException(<<< EOT
No system CA bundle could be found in any of the the common system locations.
PHP versions earlier than 5.6 are not properly configured to use the system's
CA bundle by default. In order to verify peer certificates, you will need to
supply the path on disk to a certificate bundle to the 'verify' request
option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not
need a specific certificate bundle, then Mozilla provides a commonly used CA
bundle which can be downloaded here (provided by the maintainer of cURL):
https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt. Once
you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP
ini setting to point to the path to the file, allowing you to omit the 'verify'
request option. See http://curl.haxx.se/docs/sslcerts.html for more
information.
EOT
    );
}

/**
 * Creates an associative array of lowercase header names to the actual
 * header casing.
 *
 * @param array $headers
 *
 * @return array
 */
function normalize_header_keys(array $headers)
{
    $result = [];
    foreach (array_keys($headers) as $key) {
        $result[strtolower($key)] = $key;
    }

    return $result;
}

/**
 * Returns true if the provided host matches any of the no proxy areas.
 *
 * This method will strip a port from the host if it is present. Each pattern
 * can be matched with an exact match (e.g., "foo.com" == "foo.com") or a
 * partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" ==
 * "baz.foo.com", but ".foo.com" != "foo.com").
 *
 * Areas are matched in the following cases:
 * 1. "*" (without quotes) always matches any hosts.
 * 2. An exact match.
 * 3. The area starts with "." and the area is the last part of the host. e.g.
 *    '.mit.edu' will match any host that ends with '.mit.edu'.
 *
 * @param string $host         Host to check against the patterns.
 * @param array  $noProxyArray An array of host patterns.
 *
 * @return bool
 */
function is_host_in_noproxy($host, array $noProxyArray)
{
    if (strlen($host) === 0) {
        throw new \InvalidArgumentException('Empty host provided');
    }

    // Strip port if present.
    if (strpos($host, ':')) {
        $host = explode($host, ':', 2)[0];
    }

    foreach ($noProxyArray as $area) {
        // Always match on wildcards.
        if ($area === '*') {
            return true;
        } elseif (empty($area)) {
            // Don't match on empty values.
            continue;
        } elseif ($area === $host) {
            // Exact matches.
            return true;
        } else {
            // Special match if the area when prefixed with ".". Remove any
            // existing leading "." and add a new leading ".".
            $area = '.' . ltrim($area, '.');
            if (substr($host, -(strlen($area))) === $area) {
                return true;
            }
        }
    }

    return false;
}

/**
 * Wrapper for json_decode that throws when an error occurs.
 *
 * @param string $json    JSON data to parse
 * @param bool $assoc     When true, returned objects will be converted
 *                        into associative arrays.
 * @param int    $depth   User specified recursion depth.
 * @param int    $options Bitmask of JSON decode options.
 *
 * @return mixed
 * @throws \InvalidArgumentException if the JSON cannot be decoded.
 * @link http://www.php.net/manual/en/function.json-decode.php
 */
function json_decode($json, $assoc = false, $depth = 512, $options = 0)
{
    $data = \json_decode($json, $assoc, $depth, $options);
    if (JSON_ERROR_NONE !== json_last_error()) {
        throw new \InvalidArgumentException(
            'json_decode error: ' . json_last_error_msg());
    }

    return $data;
}

/**
 * Wrapper for JSON encoding that throws when an error occurs.
 *
 * @param string $value   The value being encoded
 * @param int    $options JSON encode option bitmask
 * @param int    $depth   Set the maximum depth. Must be greater than zero.
 *
 * @return string
 * @throws \InvalidArgumentException if the JSON cannot be encoded.
 * @link http://www.php.net/manual/en/function.json-encode.php
 */
function json_encode($value, $options = 0, $depth = 512)
{
    $json = \json_encode($value, $options, $depth);
    if (JSON_ERROR_NONE !== json_last_error()) {
        throw new \InvalidArgumentException(
            'json_encode error: ' . json_last_error_msg());
    }

    return $json;
}
<?php

// Don't redefine the functions if included multiple times.
if (!function_exists('GuzzleHttp\uri_template')) {
    require __DIR__ . '/functions.php';
}
<?php
namespace GuzzleHttp\Handler;

use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\LazyOpenStream;
use GuzzleHttp\TransferStats;
use Psr\Http\Message\RequestInterface;

/**
 * Creates curl resources from a request
 */
class CurlFactory implements CurlFactoryInterface
{
    /** @var array */
    private $handles;

    /** @var int Total number of idle handles to keep in cache */
    private $maxHandles;

    /**
     * @param int $maxHandles Maximum number of idle handles.
     */
    public function __construct($maxHandles)
    {
        $this->maxHandles = $maxHandles;
    }

    public function create(RequestInterface $request, array $options)
    {
        if (isset($options['curl']['body_as_string'])) {
            $options['_body_as_string'] = $options['curl']['body_as_string'];
            unset($options['curl']['body_as_string']);
        }

        $easy = new EasyHandle;
        $easy->request = $request;
        $easy->options = $options;
        $conf = $this->getDefaultConf($easy);
        $this->applyMethod($easy, $conf);
        $this->applyHandlerOptions($easy, $conf);
        $this->applyHeaders($easy, $conf);
        unset($conf['_headers']);

        // Add handler options from the request configuration options
        if (isset($options['curl'])) {
            $conf = array_replace($conf, $options['curl']);
        }

        $conf[CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy);
        $easy->handle = $this->handles
            ? array_pop($this->handles)
            : curl_init();
        curl_setopt_array($easy->handle, $conf);

        return $easy;
    }

    public function release(EasyHandle $easy)
    {
        $resource = $easy->handle;
        unset($easy->handle);

        if (count($this->handles) >= $this->maxHandles) {
            curl_close($resource);
        } else {
            // Remove all callback functions as they can hold onto references
            // and are not cleaned up by curl_reset. Using curl_setopt_array
            // does not work for some reason, so removing each one
            // individually.
            curl_setopt($resource, CURLOPT_HEADERFUNCTION, null);
            curl_setopt($resource, CURLOPT_READFUNCTION, null);
            curl_setopt($resource, CURLOPT_WRITEFUNCTION, null);
            curl_setopt($resource, CURLOPT_PROGRESSFUNCTION, null);
            curl_reset($resource);
            $this->handles[] = $resource;
        }
    }

    /**
     * Completes a cURL transaction, either returning a response promise or a
     * rejected promise.
     *
     * @param callable             $handler
     * @param EasyHandle           $easy
     * @param CurlFactoryInterface $factory Dictates how the handle is released
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public static function finish(
        callable $handler,
        EasyHandle $easy,
        CurlFactoryInterface $factory
    ) {
        if (isset($easy->options['on_stats'])) {
            self::invokeStats($easy);
        }

        if (!$easy->response || $easy->errno) {
            return self::finishError($handler, $easy, $factory);
        }

        // Return the response if it is present and there is no error.
        $factory->release($easy);

        // Rewind the body of the response if possible.
        $body = $easy->response->getBody();
        if ($body->isSeekable()) {
            $body->rewind();
        }

        return new FulfilledPromise($easy->response);
    }

    private static function invokeStats(EasyHandle $easy)
    {
        $curlStats = curl_getinfo($easy->handle);
        $stats = new TransferStats(
            $easy->request,
            $easy->response,
            $curlStats['total_time'],
            $easy->errno,
            $curlStats
        );
        call_user_func($easy->options['on_stats'], $stats);
    }

    private static function finishError(
        callable $handler,
        EasyHandle $easy,
        CurlFactoryInterface $factory
    ) {
        // Get error information and release the handle to the factory.
        $ctx = [
            'errno' => $easy->errno,
            'error' => curl_error($easy->handle),
        ] + curl_getinfo($easy->handle);
        $factory->release($easy);

        // Retry when nothing is present or when curl failed to rewind.
        if (empty($easy->options['_err_message'])
            && (!$easy->errno || $easy->errno == 65)
        ) {
            return self::retryFailedRewind($handler, $easy, $ctx);
        }

        return self::createRejection($easy, $ctx);
    }

    private static function createRejection(EasyHandle $easy, array $ctx)
    {
        static $connectionErrors = [
            CURLE_OPERATION_TIMEOUTED  => true,
            CURLE_COULDNT_RESOLVE_HOST => true,
            CURLE_COULDNT_CONNECT      => true,
            CURLE_SSL_CONNECT_ERROR    => true,
            CURLE_GOT_NOTHING          => true,
        ];

        // If an exception was encountered during the onHeaders event, then
        // return a rejected promise that wraps that exception.
        if ($easy->onHeadersException) {
            return new RejectedPromise(
                new RequestException(
                    'An error was encountered during the on_headers event',
                    $easy->request,
                    $easy->response,
                    $easy->onHeadersException,
                    $ctx
                )
            );
        }

        $message = sprintf(
            'cURL error %s: %s (%s)',
            $ctx['errno'],
            $ctx['error'],
            'see http://curl.haxx.se/libcurl/c/libcurl-errors.html'
        );

        // Create a connection exception if it was a specific error code.
        $error = isset($connectionErrors[$easy->errno])
            ? new ConnectException($message, $easy->request, null, $ctx)
            : new RequestException($message, $easy->request, $easy->response, null, $ctx);

        return new RejectedPromise($error);
    }

    private function getDefaultConf(EasyHandle $easy)
    {
        $conf = [
            '_headers'             => $easy->request->getHeaders(),
            CURLOPT_CUSTOMREQUEST  => $easy->request->getMethod(),
            CURLOPT_URL            => (string) $easy->request->getUri(),
            CURLOPT_RETURNTRANSFER => false,
            CURLOPT_HEADER         => false,
            CURLOPT_CONNECTTIMEOUT => 150,
        ];

        if (defined('CURLOPT_PROTOCOLS')) {
            $conf[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
        }

        $version = $easy->request->getProtocolVersion();
        if ($version == 1.1) {
            $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
        } elseif ($version == 2.0) {
            $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
        } else {
            $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
        }

        return $conf;
    }

    private function applyMethod(EasyHandle $easy, array &$conf)
    {
        $body = $easy->request->getBody();
        $size = $body->getSize();

        if ($size === null || $size > 0) {
            $this->applyBody($easy->request, $easy->options, $conf);
            return;
        }

        $method = $easy->request->getMethod();
        if ($method === 'PUT' || $method === 'POST') {
            // See http://tools.ietf.org/html/rfc7230#section-3.3.2
            if (!$easy->request->hasHeader('Content-Length')) {
                $conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
            }
        } elseif ($method === 'HEAD') {
            $conf[CURLOPT_NOBODY] = true;
            unset(
                $conf[CURLOPT_WRITEFUNCTION],
                $conf[CURLOPT_READFUNCTION],
                $conf[CURLOPT_FILE],
                $conf[CURLOPT_INFILE]
            );
        }
    }

    private function applyBody(RequestInterface $request, array $options, array &$conf)
    {
        $size = $request->hasHeader('Content-Length')
            ? (int) $request->getHeaderLine('Content-Length')
            : null;

        // Send the body as a string if the size is less than 1MB OR if the
        // [curl][body_as_string] request value is set.
        if (($size !== null && $size < 1000000) ||
            !empty($options['_body_as_string'])
        ) {
            $conf[CURLOPT_POSTFIELDS] = (string) $request->getBody();
            // Don't duplicate the Content-Length header
            $this->removeHeader('Content-Length', $conf);
            $this->removeHeader('Transfer-Encoding', $conf);
        } else {
            $conf[CURLOPT_UPLOAD] = true;
            if ($size !== null) {
                $conf[CURLOPT_INFILESIZE] = $size;
                $this->removeHeader('Content-Length', $conf);
            }
            $body = $request->getBody();
            if ($body->isSeekable()) {
                $body->rewind();
            }
            $conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
                return $body->read($length);
            };
        }

        // If the Expect header is not present, prevent curl from adding it
        if (!$request->hasHeader('Expect')) {
            $conf[CURLOPT_HTTPHEADER][] = 'Expect:';
        }

        // cURL sometimes adds a content-type by default. Prevent this.
        if (!$request->hasHeader('Content-Type')) {
            $conf[CURLOPT_HTTPHEADER][] = 'Content-Type:';
        }
    }

    private function applyHeaders(EasyHandle $easy, array &$conf)
    {
        foreach ($conf['_headers'] as $name => $values) {
            foreach ($values as $value) {
                $conf[CURLOPT_HTTPHEADER][] = "$name: $value";
            }
        }

        // Remove the Accept header if one was not set
        if (!$easy->request->hasHeader('Accept')) {
            $conf[CURLOPT_HTTPHEADER][] = 'Accept:';
        }
    }

    /**
     * Remove a header from the options array.
     *
     * @param string $name    Case-insensitive header to remove
     * @param array  $options Array of options to modify
     */
    private function removeHeader($name, array &$options)
    {
        foreach (array_keys($options['_headers']) as $key) {
            if (!strcasecmp($key, $name)) {
                unset($options['_headers'][$key]);
                return;
            }
        }
    }

    private function applyHandlerOptions(EasyHandle $easy, array &$conf)
    {
        $options = $easy->options;
        if (isset($options['verify'])) {
            if ($options['verify'] === false) {
                unset($conf[CURLOPT_CAINFO]);
                $conf[CURLOPT_SSL_VERIFYHOST] = 0;
                $conf[CURLOPT_SSL_VERIFYPEER] = false;
            } else {
                $conf[CURLOPT_SSL_VERIFYHOST] = 2;
                $conf[CURLOPT_SSL_VERIFYPEER] = true;
                if (is_string($options['verify'])) {
                    $conf[CURLOPT_CAINFO] = $options['verify'];
                    if (!file_exists($options['verify'])) {
                        throw new \InvalidArgumentException(
                            "SSL CA bundle not found: {$options['verify']}"
                        );
                    }
                }
            }
        }

        if (!empty($options['decode_content'])) {
            $accept = $easy->request->getHeaderLine('Accept-Encoding');
            if ($accept) {
                $conf[CURLOPT_ENCODING] = $accept;
            } else {
                $conf[CURLOPT_ENCODING] = '';
                // Don't let curl send the header over the wire
                $conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
            }
        }

        if (isset($options['sink'])) {
            $sink = $options['sink'];
            if (!is_string($sink)) {
                $sink = \GuzzleHttp\Psr7\stream_for($sink);
            } elseif (!is_dir(dirname($sink))) {
                // Ensure that the directory exists before failing in curl.
                throw new \RuntimeException(sprintf(
                    'Directory %s does not exist for sink value of %s',
                    dirname($sink),
                    $sink
                ));
            } else {
                $sink = new LazyOpenStream($sink, 'w+');
            }
            $easy->sink = $sink;
            $conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) {
                return $sink->write($write);
            };
        } else {
            // Use a default temp stream if no sink was set.
            $conf[CURLOPT_FILE] = fopen('php://temp', 'w+');
            $easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]);
        }

        if (isset($options['timeout'])) {
            $conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
        }

        if (isset($options['connect_timeout'])) {
            $conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000;
        }

        if (isset($options['proxy'])) {
            if (!is_array($options['proxy'])) {
                $conf[CURLOPT_PROXY] = $options['proxy'];
            } else {
                $scheme = $easy->request->getUri()->getScheme();
                if (isset($options['proxy'][$scheme])) {
                    $host = $easy->request->getUri()->getHost();
                    if (!isset($options['proxy']['no']) ||
                        !\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no'])
                    ) {
                        $conf[CURLOPT_PROXY] = $options['proxy'][$scheme];
                    }
                }
            }
        }

        if (isset($options['cert'])) {
            $cert = $options['cert'];
            if (is_array($cert)) {
                $conf[CURLOPT_SSLCERTPASSWD] = $cert[1];
                $cert = $cert[0];
            }
            if (!file_exists($cert)) {
                throw new \InvalidArgumentException(
                    "SSL certificate not found: {$cert}"
                );
            }
            $conf[CURLOPT_SSLCERT] = $cert;
        }

        if (isset($options['ssl_key'])) {
            $sslKey = $options['ssl_key'];
            if (is_array($sslKey)) {
                $conf[CURLOPT_SSLKEYPASSWD] = $sslKey[1];
                $sslKey = $sslKey[0];
            }
            if (!file_exists($sslKey)) {
                throw new \InvalidArgumentException(
                    "SSL private key not found: {$sslKey}"
                );
            }
            $conf[CURLOPT_SSLKEY] = $sslKey;
        }

        if (isset($options['progress'])) {
            $progress = $options['progress'];
            if (!is_callable($progress)) {
                throw new \InvalidArgumentException(
                    'progress client option must be callable'
                );
            }
            $conf[CURLOPT_NOPROGRESS] = false;
            $conf[CURLOPT_PROGRESSFUNCTION] = function () use ($progress) {
                $args = func_get_args();
                // PHP 5.5 pushed the handle onto the start of the args
                if (is_resource($args[0])) {
                    array_shift($args);
                }
                call_user_func_array($progress, $args);
            };
        }

        if (!empty($options['debug'])) {
            $conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($options['debug']);
            $conf[CURLOPT_VERBOSE] = true;
        }
    }

    /**
     * This function ensures that a response was set on a transaction. If one
     * was not set, then the request is retried if possible. This error
     * typically means you are sending a payload, curl encountered a
     * "Connection died, retrying a fresh connect" error, tried to rewind the
     * stream, and then encountered a "necessary data rewind wasn't possible"
     * error, causing the request to be sent through curl_multi_info_read()
     * without an error status.
     */
    private static function retryFailedRewind(
        callable $handler,
        EasyHandle $easy,
        array $ctx
    ) {
        try {
            // Only rewind if the body has been read from.
            $body = $easy->request->getBody();
            if ($body->tell() > 0) {
                $body->rewind();
            }
        } catch (\RuntimeException $e) {
            $ctx['error'] = 'The connection unexpectedly failed without '
                . 'providing an error. The request would have been retried, '
                . 'but attempting to rewind the request body failed. '
                . 'Exception: ' . $e;
            return self::createRejection($easy, $ctx);
        }

        // Retry no more than 3 times before giving up.
        if (!isset($easy->options['_curl_retries'])) {
            $easy->options['_curl_retries'] = 1;
        } elseif ($easy->options['_curl_retries'] == 2) {
            $ctx['error'] = 'The cURL request was retried 3 times '
                . 'and did not succeed. The most likely reason for the failure '
                . 'is that cURL was unable to rewind the body of the request '
                . 'and subsequent retries resulted in the same error. Turn on '
                . 'the debug option to see what went wrong. See '
                . 'https://bugs.php.net/bug.php?id=47204 for more information.';
            return self::createRejection($easy, $ctx);
        } else {
            $easy->options['_curl_retries']++;
        }

        return $handler($easy->request, $easy->options);
    }

    private function createHeaderFn(EasyHandle $easy)
    {
        if (!isset($easy->options['on_headers'])) {
            $onHeaders = null;
        } elseif (!is_callable($easy->options['on_headers'])) {
            throw new \InvalidArgumentException('on_headers must be callable');
        } else {
            $onHeaders = $easy->options['on_headers'];
        }

        return function ($ch, $h) use (
            $onHeaders,
            $easy,
            &$startingResponse
        ) {
            $value = trim($h);
            if ($value === '') {
                $startingResponse = true;
                $easy->createResponse();
                if ($onHeaders) {
                    try {
                        $onHeaders($easy->response);
                    } catch (\Exception $e) {
                        // Associate the exception with the handle and trigger
                        // a curl header write error by returning 0.
                        $easy->onHeadersException = $e;
                        return -1;
                    }
                }
            } elseif ($startingResponse) {
                $startingResponse = false;
                $easy->headers = [$value];
            } else {
                $easy->headers[] = $value;
            }
            return strlen($h);
        };
    }
}
<?php
namespace GuzzleHttp\Handler;

use Psr\Http\Message\RequestInterface;

interface CurlFactoryInterface
{
    /**
     * Creates a cURL handle resource.
     *
     * @param RequestInterface $request Request
     * @param array            $options Transfer options
     *
     * @return EasyHandle
     * @throws \RuntimeException when an option cannot be applied
     */
    public function create(RequestInterface $request, array $options);

    /**
     * Release an easy handle, allowing it to be reused or closed.
     *
     * This function must call unset on the easy handle's "handle" property.
     *
     * @param EasyHandle $easy
     */
    public function release(EasyHandle $easy);
}
<?php
namespace GuzzleHttp\Handler;

use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;

/**
 * HTTP handler that uses cURL easy handles as a transport layer.
 *
 * When using the CurlHandler, custom curl options can be specified as an
 * associative array of curl option constants mapping to values in the
 * **curl** key of the "client" key of the request.
 */
class CurlHandler
{
    /** @var CurlFactoryInterface */
    private $factory;

    /**
     * Accepts an associative array of options:
     *
     * - factory: Optional curl factory used to create cURL handles.
     *
     * @param array $options Array of options to use with the handler
     */
    public function __construct(array $options = [])
    {
        $this->factory = isset($options['handle_factory'])
            ? $options['handle_factory']
            : new CurlFactory(3);
    }

    public function __invoke(RequestInterface $request, array $options)
    {
        if (isset($options['delay'])) {
            usleep($options['delay'] * 1000);
        }

        $easy = $this->factory->create($request, $options);
        curl_exec($easy->handle);
        $easy->errno = curl_errno($easy->handle);

        return CurlFactory::finish($this, $easy, $this->factory);
    }
}
<?php
namespace GuzzleHttp\Handler;

use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;

/**
 * Returns an asynchronous response using curl_multi_* functions.
 *
 * When using the CurlMultiHandler, custom curl options can be specified as an
 * associative array of curl option constants mapping to values in the
 * **curl** key of the provided request options.
 *
 * @property resource $_mh Internal use only. Lazy loaded multi-handle.
 */
class CurlMultiHandler
{
    /** @var CurlFactoryInterface */
    private $factory;
    private $selectTimeout;
    private $active;
    private $handles = [];
    private $delays = [];

    /**
     * This handler accepts the following options:
     *
     * - handle_factory: An optional factory  used to create curl handles
     * - select_timeout: Optional timeout (in seconds) to block before timing
     *   out while selecting curl handles. Defaults to 1 second.
     *
     * @param array $options
     */
    public function __construct(array $options = [])
    {
        $this->factory = isset($options['handle_factory'])
            ? $options['handle_factory'] : new CurlFactory(50);
        $this->selectTimeout = isset($options['select_timeout'])
            ? $options['select_timeout'] : 1;
    }

    public function __get($name)
    {
        if ($name === '_mh') {
            return $this->_mh = curl_multi_init();
        }

        throw new \BadMethodCallException();
    }

    public function __destruct()
    {
        if (isset($this->_mh)) {
            curl_multi_close($this->_mh);
            unset($this->_mh);
        }
    }

    public function __invoke(RequestInterface $request, array $options)
    {
        $easy = $this->factory->create($request, $options);
        $id = (int) $easy->handle;

        $promise = new Promise(
            [$this, 'execute'],
            function () use ($id) { return $this->cancel($id); }
        );

        $this->addRequest(['easy' => $easy, 'deferred' => $promise]);

        return $promise;
    }

    /**
     * Ticks the curl event loop.
     */
    public function tick()
    {
        // Add any delayed handles if needed.
        if ($this->delays) {
            $currentTime = microtime(true);
            foreach ($this->delays as $id => $delay) {
                if ($currentTime >= $delay) {
                    unset($this->delays[$id]);
                    curl_multi_add_handle(
                        $this->_mh,
                        $this->handles[$id]['easy']->handle
                    );
                }
            }
        }

        // Step through the task queue which may add additional requests.
        P\queue()->run();

        if ($this->active &&
            curl_multi_select($this->_mh, $this->selectTimeout) === -1
        ) {
            // Perform a usleep if a select returns -1.
            // See: https://bugs.php.net/bug.php?id=61141
            usleep(250);
        }

        while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM);

        $this->processMessages();
    }

    /**
     * Runs until all outstanding connections have completed.
     */
    public function execute()
    {
        $queue = P\queue();

        while ($this->handles || !$queue->isEmpty()) {
            // If there are no transfers, then sleep for the next delay
            if (!$this->active && $this->delays) {
                usleep($this->timeToNext());
            }
            $this->tick();
        }
    }

    private function addRequest(array $entry)
    {
        $easy = $entry['easy'];
        $id = (int) $easy->handle;
        $this->handles[$id] = $entry;
        if (empty($easy->options['delay'])) {
            curl_multi_add_handle($this->_mh, $easy->handle);
        } else {
            $this->delays[$id] = microtime(true) + ($easy->options['delay'] / 1000);
        }
    }

    /**
     * Cancels a handle from sending and removes references to it.
     *
     * @param int $id Handle ID to cancel and remove.
     *
     * @return bool True on success, false on failure.
     */
    private function cancel($id)
    {
        // Cannot cancel if it has been processed.
        if (!isset($this->handles[$id])) {
            return false;
        }

        $handle = $this->handles[$id]['easy']->handle;
        unset($this->delays[$id], $this->handles[$id]);
        curl_multi_remove_handle($this->_mh, $handle);
        curl_close($handle);

        return true;
    }

    private function processMessages()
    {
        while ($done = curl_multi_info_read($this->_mh)) {
            $id = (int) $done['handle'];
            curl_multi_remove_handle($this->_mh, $done['handle']);

            if (!isset($this->handles[$id])) {
                // Probably was cancelled.
                continue;
            }

            $entry = $this->handles[$id];
            unset($this->handles[$id], $this->delays[$id]);
            $entry['easy']->errno = $done['result'];
            $entry['deferred']->resolve(
                CurlFactory::finish(
                    $this,
                    $entry['easy'],
                    $this->factory
                )
            );
        }
    }

    private function timeToNext()
    {
        $currentTime = microtime(true);
        $nextTime = PHP_INT_MAX;
        foreach ($this->delays as $time) {
            if ($time < $nextTime) {
                $nextTime = $time;
            }
        }

        return max(0, $nextTime - $currentTime) * 1000000;
    }
}
<?php
namespace GuzzleHttp\Handler;

use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;

/**
 * Represents a cURL easy handle and the data it populates.
 *
 * @internal
 */
final class EasyHandle
{
    /** @var resource cURL resource */
    public $handle;

    /** @var StreamInterface Where data is being written */
    public $sink;

    /** @var array Received HTTP headers so far */
    public $headers = [];

    /** @var ResponseInterface Received response (if any) */
    public $response;

    /** @var RequestInterface Request being sent */
    public $request;

    /** @var array Request options */
    public $options = [];

    /** @var int cURL error number (if any) */
    public $errno = 0;

    /** @var \Exception Exception during on_headers (if any) */
    public $onHeadersException;

    /**
     * Attach a response to the easy handle based on the received headers.
     *
     * @throws \RuntimeException if no headers have been received.
     */
    public function createResponse()
    {
        if (empty($this->headers)) {
            throw new \RuntimeException('No headers have been received');
        }

        // HTTP-version SP status-code SP reason-phrase
        $startLine = explode(' ', array_shift($this->headers), 3);
        $headers = \GuzzleHttp\headers_from_lines($this->headers);
        $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);

        if (!empty($this->options['decode_content'])
            && isset($normalizedKeys['content-encoding'])
        ) {
            $headers['x-encoded-content-encoding']
                = $headers[$normalizedKeys['content-encoding']];
            unset($headers[$normalizedKeys['content-encoding']]);
            if (isset($normalizedKeys['content-length'])) {
                $headers['x-encoded-content-length']
                    = $headers[$normalizedKeys['content-length']];

                $bodyLength = (int) $this->sink->getSize();
                if ($bodyLength) {
                    $headers[$normalizedKeys['content-length']] = $bodyLength;
                } else {
                    unset($headers[$normalizedKeys['content-length']]);
                }
            }
        }

        // Attach a response to the easy handle with the parsed headers.
        $this->response = new Response(
            $startLine[1],
            $headers,
            $this->sink,
            substr($startLine[0], 5),
            isset($startLine[2]) ? (string) $startLine[2] : null
        );
    }

    public function __get($name)
    {
        $msg = $name === 'handle'
            ? 'The EasyHandle has been released'
            : 'Invalid property: ' . $name;
        throw new \BadMethodCallException($msg);
    }
}
<?php
namespace GuzzleHttp\Handler;

use GuzzleHttp\HandlerStack;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\TransferStats;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Handler that returns responses or throw exceptions from a queue.
 */
class MockHandler implements \Countable
{
    private $queue;
    private $lastRequest;
    private $lastOptions;
    private $onFulfilled;
    private $onRejected;

    /**
     * Creates a new MockHandler that uses the default handler stack list of
     * middlewares.
     *
     * @param array $queue Array of responses, callables, or exceptions.
     * @param callable $onFulfilled Callback to invoke when the return value is fulfilled.
     * @param callable $onRejected  Callback to invoke when the return value is rejected.
     *
     * @return MockHandler
     */
    public static function createWithMiddleware(
        array $queue = null,
        callable $onFulfilled = null,
        callable $onRejected = null
    ) {
        return HandlerStack::create(new self($queue, $onFulfilled, $onRejected));
    }

    /**
     * The passed in value must be an array of
     * {@see Psr7\Http\Message\ResponseInterface} objects, Exceptions,
     * callables, or Promises.
     *
     * @param array $queue
     * @param callable $onFulfilled Callback to invoke when the return value is fulfilled.
     * @param callable $onRejected  Callback to invoke when the return value is rejected.
     */
    public function __construct(
        array $queue = null,
        callable $onFulfilled = null,
        callable $onRejected = null
    ) {
        $this->onFulfilled = $onFulfilled;
        $this->onRejected = $onRejected;

        if ($queue) {
            call_user_func_array([$this, 'append'], $queue);
        }
    }

    public function __invoke(RequestInterface $request, array $options)
    {
        if (!$this->queue) {
            throw new \OutOfBoundsException('Mock queue is empty');
        }

        if (isset($options['delay'])) {
            usleep($options['delay'] * 1000);
        }

        $this->lastRequest = $request;
        $this->lastOptions = $options;
        $response = array_shift($this->queue);

        if (is_callable($response)) {
            $response = call_user_func($response, $request, $options);
        }

        $response = $response instanceof \Exception
            ? new RejectedPromise($response)
            : \GuzzleHttp\Promise\promise_for($response);

        return $response->then(
            function ($value) use ($request, $options) {
                $this->invokeStats($request, $options, $value);
                if ($this->onFulfilled) {
                    call_user_func($this->onFulfilled, $value);
                }
                if (isset($options['sink'])) {
                    $contents = (string) $value->getBody();
                    $sink = $options['sink'];

                    if (is_resource($sink)) {
                        fwrite($sink, $contents);
                    } elseif (is_string($sink)) {
                        file_put_contents($sink, $contents);
                    } elseif ($sink instanceof \Psr\Http\Message\StreamInterface) {
                        $sink->write($contents);
                    }
                }

                return $value;
            },
            function ($reason) use ($request, $options) {
                $this->invokeStats($request, $options, null, $reason);
                if ($this->onRejected) {
                    call_user_func($this->onRejected, $reason);
                }
                return new RejectedPromise($reason);
            }
        );
    }

    /**
     * Adds one or more variadic requests, exceptions, callables, or promises
     * to the queue.
     */
    public function append()
    {
        foreach (func_get_args() as $value) {
            if ($value instanceof ResponseInterface
                || $value instanceof \Exception
                || $value instanceof PromiseInterface
                || is_callable($value)
            ) {
                $this->queue[] = $value;
            } else {
                throw new \InvalidArgumentException('Expected a response or '
                    . 'exception. Found ' . \GuzzleHttp\describe_type($value));
            }
        }
    }

    /**
     * Get the last received request.
     *
     * @return RequestInterface
     */
    public function getLastRequest()
    {
        return $this->lastRequest;
    }

    /**
     * Get the last received request options.
     *
     * @return RequestInterface
     */
    public function getLastOptions()
    {
        return $this->lastOptions;
    }

    /**
     * Returns the number of remaining items in the queue.
     *
     * @return int
     */
    public function count()
    {
        return count($this->queue);
    }

    private function invokeStats(
        RequestInterface $request,
        array $options,
        ResponseInterface $response = null,
        $reason = null
    ) {
        if (isset($options['on_stats'])) {
            $stats = new TransferStats($request, $response, 0, $reason);
            call_user_func($options['on_stats'], $stats);
        }
    }
}
<?php
namespace GuzzleHttp\Handler;

use GuzzleHttp\RequestOptions;
use Psr\Http\Message\RequestInterface;

/**
 * Provides basic proxies for handlers.
 */
class Proxy
{
    /**
     * Sends synchronous requests to a specific handler while sending all other
     * requests to another handler.
     *
     * @param callable $default Handler used for normal responses
     * @param callable $sync    Handler used for synchronous responses.
     *
     * @return callable Returns the composed handler.
     */
    public static function wrapSync(
        callable $default,
        callable $sync
    ) {
        return function (RequestInterface $request, array $options) use ($default, $sync) {
            return empty($options[RequestOptions::SYNCHRONOUS])
                ? $default($request, $options)
                : $sync($request, $options);
        };
    }

    /**
     * Sends streaming requests to a streaming compatible handler while sending
     * all other requests to a default handler.
     *
     * This, for example, could be useful for taking advantage of the
     * performance benefits of curl while still supporting true streaming
     * through the StreamHandler.
     *
     * @param callable $default   Handler used for non-streaming responses
     * @param callable $streaming Handler used for streaming responses
     *
     * @return callable Returns the composed handler.
     */
    public static function wrapStreaming(
        callable $default,
        callable $streaming
    ) {
        return function (RequestInterface $request, array $options) use ($default, $streaming) {
            return empty($options['stream'])
                ? $default($request, $options)
                : $streaming($request, $options);
        };
    }
}
<?php
namespace GuzzleHttp\Handler;

use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7;
use GuzzleHttp\TransferStats;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;

/**
 * HTTP handler that uses PHP's HTTP stream wrapper.
 */
class StreamHandler
{
    private $lastHeaders = [];

    /**
     * Sends an HTTP request.
     *
     * @param RequestInterface $request Request to send.
     * @param array            $options Request transfer options.
     *
     * @return PromiseInterface
     */
    public function __invoke(RequestInterface $request, array $options)
    {
        // Sleep if there is a delay specified.
        if (isset($options['delay'])) {
            usleep($options['delay'] * 1000);
        }

        $startTime = isset($options['on_stats']) ? microtime(true) : null;

        try {
            // Does not support the expect header.
            $request = $request->withoutHeader('Expect');

            // Append a content-length header if body size is zero to match
            // cURL's behavior.
            if (0 === $request->getBody()->getSize()) {
                $request = $request->withHeader('Content-Length', 0);
            }

            return $this->createResponse(
                $request,
                $options,
                $this->createStream($request, $options),
                $startTime
            );
        } catch (\InvalidArgumentException $e) {
            throw $e;
        } catch (\Exception $e) {
            // Determine if the error was a networking error.
            $message = $e->getMessage();
            // This list can probably get more comprehensive.
            if (strpos($message, 'getaddrinfo') // DNS lookup failed
                || strpos($message, 'Connection refused')
                || strpos($message, "couldn't connect to host") // error on HHVM
            ) {
                $e = new ConnectException($e->getMessage(), $request, $e);
            }
            $e = RequestException::wrapException($request, $e);
            $this->invokeStats($options, $request, $startTime, null, $e);

            return new RejectedPromise($e);
        }
    }

    private function invokeStats(
        array $options,
        RequestInterface $request,
        $startTime,
        ResponseInterface $response = null,
        $error = null
    ) {
        if (isset($options['on_stats'])) {
            $stats = new TransferStats(
                $request,
                $response,
                microtime(true) - $startTime,
                $error,
                []
            );
            call_user_func($options['on_stats'], $stats);
        }
    }

    private function createResponse(
        RequestInterface $request,
        array $options,
        $stream,
        $startTime
    ) {
        $hdrs = $this->lastHeaders;
        $this->lastHeaders = [];
        $parts = explode(' ', array_shift($hdrs), 3);
        $ver = explode('/', $parts[0])[1];
        $status = $parts[1];
        $reason = isset($parts[2]) ? $parts[2] : null;
        $headers = \GuzzleHttp\headers_from_lines($hdrs);
        list ($stream, $headers) = $this->checkDecode($options, $headers, $stream);
        $stream = Psr7\stream_for($stream);
        $sink = $this->createSink($stream, $options);
        $response = new Psr7\Response($status, $headers, $sink, $ver, $reason);

        if (isset($options['on_headers'])) {
            try {
                $options['on_headers']($response);
            } catch (\Exception $e) {
                $msg = 'An error was encountered during the on_headers event';
                $ex = new RequestException($msg, $request, $response, $e);
                return new RejectedPromise($ex);
            }
        }

        if ($sink !== $stream) {
            $this->drain($stream, $sink);
        }

        $this->invokeStats($options, $request, $startTime, $response, null);

        return new FulfilledPromise($response);
    }

    private function createSink(StreamInterface $stream, array $options)
    {
        if (!empty($options['stream'])) {
            return $stream;
        }

        $sink = isset($options['sink'])
            ? $options['sink']
            : fopen('php://temp', 'r+');

        return is_string($sink)
            ? new Psr7\LazyOpenStream($sink, 'w+')
            : Psr7\stream_for($sink);
    }

    private function checkDecode(array $options, array $headers, $stream)
    {
        // Automatically decode responses when instructed.
        if (!empty($options['decode_content'])) {
            $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
            if (isset($normalizedKeys['content-encoding'])) {
                $encoding = $headers[$normalizedKeys['content-encoding']];
                if ($encoding[0] == 'gzip' || $encoding[0] == 'deflate') {
                    $stream = new Psr7\InflateStream(
                        Psr7\stream_for($stream)
                    );
                    $headers['x-encoded-content-encoding']
                        = $headers[$normalizedKeys['content-encoding']];
                    // Remove content-encoding header
                    unset($headers[$normalizedKeys['content-encoding']]);
                    // Fix content-length header
                    if (isset($normalizedKeys['content-length'])) {
                        $headers['x-encoded-content-length']
                            = $headers[$normalizedKeys['content-length']];

                        $length = (int) $stream->getSize();
                        if ($length == 0) {
                            unset($headers[$normalizedKeys['content-length']]);
                        } else {
                            $headers[$normalizedKeys['content-length']] = [$length];
                        }
                    }
                }
            }
        }

        return [$stream, $headers];
    }

    /**
     * Drains the source stream into the "sink" client option.
     *
     * @param StreamInterface $source
     * @param StreamInterface $sink
     *
     * @return StreamInterface
     * @throws \RuntimeException when the sink option is invalid.
     */
    private function drain(StreamInterface $source, StreamInterface $sink)
    {
        Psr7\copy_to_stream($source, $sink);
        $sink->seek(0);
        $source->close();

        return $sink;
    }

    /**
     * Create a resource and check to ensure it was created successfully
     *
     * @param callable $callback Callable that returns stream resource
     *
     * @return resource
     * @throws \RuntimeException on error
     */
    private function createResource(callable $callback)
    {
        $errors = null;
        set_error_handler(function ($_, $msg, $file, $line) use (&$errors) {
            $errors[] = [
                'message' => $msg,
                'file'    => $file,
                'line'    => $line
            ];
            return true;
        });

        $resource = $callback();
        restore_error_handler();

        if (!$resource) {
            $message = 'Error creating resource: ';
            foreach ($errors as $err) {
                foreach ($err as $key => $value) {
                    $message .= "[$key] $value" . PHP_EOL;
                }
            }
            throw new \RuntimeException(trim($message));
        }

        return $resource;
    }

    private function createStream(RequestInterface $request, array $options)
    {
        static $methods;
        if (!$methods) {
            $methods = array_flip(get_class_methods(__CLASS__));
        }

        // HTTP/1.1 streams using the PHP stream wrapper require a
        // Connection: close header
        if ($request->getProtocolVersion() == '1.1'
            && !$request->hasHeader('Connection')
        ) {
            $request = $request->withHeader('Connection', 'close');
        }

        // Ensure SSL is verified by default
        if (!isset($options['verify'])) {
            $options['verify'] = true;
        }

        $params = [];
        $context = $this->getDefaultContext($request, $options);

        if (isset($options['on_headers']) && !is_callable($options['on_headers'])) {
            throw new \InvalidArgumentException('on_headers must be callable');
        }

        if (!empty($options)) {
            foreach ($options as $key => $value) {
                $method = "add_{$key}";
                if (isset($methods[$method])) {
                    $this->{$method}($request, $context, $value, $params);
                }
            }
        }

        if (isset($options['stream_context'])) {
            if (!is_array($options['stream_context'])) {
                throw new \InvalidArgumentException('stream_context must be an array');
            }
            $context = array_replace_recursive(
                $context,
                $options['stream_context']
            );
        }

        $context = $this->createResource(
            function () use ($context, $params) {
                return stream_context_create($context, $params);
            }
        );

        return $this->createResource(
            function () use ($request, &$http_response_header, $context) {
                $resource = fopen($request->getUri(), 'r', null, $context);
                $this->lastHeaders = $http_response_header;
                return $resource;
            }
        );
    }

    private function getDefaultContext(RequestInterface $request)
    {
        $headers = '';
        foreach ($request->getHeaders() as $name => $value) {
            foreach ($value as $val) {
                $headers .= "$name: $val\r\n";
            }
        }

        $context = [
            'http' => [
                'method'           => $request->getMethod(),
                'header'           => $headers,
                'protocol_version' => $request->getProtocolVersion(),
                'ignore_errors'    => true,
                'follow_location'  => 0,
            ],
        ];

        $body = (string) $request->getBody();

        if (!empty($body)) {
            $context['http']['content'] = $body;
            // Prevent the HTTP handler from adding a Content-Type header.
            if (!$request->hasHeader('Content-Type')) {
                $context['http']['header'] .= "Content-Type:\r\n";
            }
        }

        $context['http']['header'] = rtrim($context['http']['header']);

        return $context;
    }

    private function add_proxy(RequestInterface $request, &$options, $value, &$params)
    {
        if (!is_array($value)) {
            $options['http']['proxy'] = $value;
        } else {
            $scheme = $request->getUri()->getScheme();
            if (isset($value[$scheme])) {
                if (!isset($value['no'])
                    || !\GuzzleHttp\is_host_in_noproxy(
                        $request->getUri()->getHost(),
                        $value['no']
                    )
                ) {
                    $options['http']['proxy'] = $value[$scheme];
                }
            }
        }
    }

    private function add_timeout(RequestInterface $request, &$options, $value, &$params)
    {
        $options['http']['timeout'] = $value;
    }

    private function add_verify(RequestInterface $request, &$options, $value, &$params)
    {
        if ($value === true) {
            // PHP 5.6 or greater will find the system cert by default. When
            // < 5.6, use the Guzzle bundled cacert.
            if (PHP_VERSION_ID < 50600) {
                $options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle();
            }
        } elseif (is_string($value)) {
            $options['ssl']['cafile'] = $value;
            if (!file_exists($value)) {
                throw new \RuntimeException("SSL CA bundle not found: $value");
            }
        } elseif ($value === false) {
            $options['ssl']['verify_peer'] = false;
            $options['ssl']['verify_peer_name'] = false;
            return;
        } else {
            throw new \InvalidArgumentException('Invalid verify request option');
        }

        $options['ssl']['verify_peer'] = true;
        $options['ssl']['verify_peer_name'] = true;
        $options['ssl']['allow_self_signed'] = false;
    }

    private function add_cert(RequestInterface $request, &$options, $value, &$params)
    {
        if (is_array($value)) {
            $options['ssl']['passphrase'] = $value[1];
            $value = $value[0];
        }

        if (!file_exists($value)) {
            throw new \RuntimeException("SSL certificate not found: {$value}");
        }

        $options['ssl']['local_cert'] = $value;
    }

    private function add_progress(RequestInterface $request, &$options, $value, &$params)
    {
        $this->addNotification(
            $params,
            function ($code, $a, $b, $c, $transferred, $total) use ($value) {
                if ($code == STREAM_NOTIFY_PROGRESS) {
                    $value($total, $transferred, null, null);
                }
            }
        );
    }

    private function add_debug(RequestInterface $request, &$options, $value, &$params)
    {
        if ($value === false) {
            return;
        }

        static $map = [
            STREAM_NOTIFY_CONNECT       => 'CONNECT',
            STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
            STREAM_NOTIFY_AUTH_RESULT   => 'AUTH_RESULT',
            STREAM_NOTIFY_MIME_TYPE_IS  => 'MIME_TYPE_IS',
            STREAM_NOTIFY_FILE_SIZE_IS  => 'FILE_SIZE_IS',
            STREAM_NOTIFY_REDIRECTED    => 'REDIRECTED',
            STREAM_NOTIFY_PROGRESS      => 'PROGRESS',
            STREAM_NOTIFY_FAILURE       => 'FAILURE',
            STREAM_NOTIFY_COMPLETED     => 'COMPLETED',
            STREAM_NOTIFY_RESOLVE       => 'RESOLVE',
        ];
        static $args = ['severity', 'message', 'message_code',
            'bytes_transferred', 'bytes_max'];

        $value = \GuzzleHttp\debug_resource($value);
        $ident = $request->getMethod() . ' ' . $request->getUri();
        $this->addNotification(
            $params,
            function () use ($ident, $value, $map, $args) {
                $passed = func_get_args();
                $code = array_shift($passed);
                fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
                foreach (array_filter($passed) as $i => $v) {
                    fwrite($value, $args[$i] . ': "' . $v . '" ');
                }
                fwrite($value, "\n");
            }
        );
    }

    private function addNotification(array &$params, callable $notify)
    {
        // Wrap the existing function if needed.
        if (!isset($params['notification'])) {
            $params['notification'] = $notify;
        } else {
            $params['notification'] = $this->callArray([
                $params['notification'],
                $notify
            ]);
        }
    }

    private function callArray(array $functions)
    {
        return function () use ($functions) {
            $args = func_get_args();
            foreach ($functions as $fn) {
                call_user_func_array($fn, $args);
            }
        };
    }
}
<?php
namespace GuzzleHttp;

use Psr\Http\Message\RequestInterface;

/**
 * Creates a composed Guzzle handler function by stacking middlewares on top of
 * an HTTP handler function.
 */
class HandlerStack
{
    /** @var callable */
    private $handler;

    /** @var array */
    private $stack = [];

    /** @var callable|null */
    private $cached;

    /**
     * Creates a default handler stack that can be used by clients.
     *
     * The returned handler will wrap the provided handler or use the most
     * appropriate default handler for you system. The returned HandlerStack has
     * support for cookies, redirects, HTTP error exceptions, and preparing a body
     * before sending.
     *
     * The returned handler stack can be passed to a client in the "handler"
     * option.
     *
     * @param callable $handler HTTP handler function to use with the stack. If no
     *                          handler is provided, the best handler for your
     *                          system will be utilized.
     *
     * @return HandlerStack
     */
    public static function create(callable $handler = null)
    {
        $stack = new self($handler ?: choose_handler());
        $stack->push(Middleware::httpErrors(), 'http_errors');
        $stack->push(Middleware::redirect(), 'allow_redirects');
        $stack->push(Middleware::cookies(), 'cookies');
        $stack->push(Middleware::prepareBody(), 'prepare_body');

        return $stack;
    }

    /**
     * @param callable $handler Underlying HTTP handler.
     */
    public function __construct(callable $handler = null)
    {
        $this->handler = $handler;
    }

    /**
     * Invokes the handler stack as a composed handler
     *
     * @param RequestInterface $request
     * @param array            $options
     */
    public function __invoke(RequestInterface $request, array $options)
    {
        $handler = $this->resolve();

        return $handler($request, $options);
    }

    /**
     * Dumps a string representation of the stack.
     *
     * @return string
     */
    public function __toString()
    {
        $depth = 0;
        $stack = [];
        if ($this->handler) {
            $stack[] = "0) Handler: " . $this->debugCallable($this->handler);
        }

        $result = '';
        foreach (array_reverse($this->stack) as $tuple) {
            $depth++;
            $str = "{$depth}) Name: '{$tuple[1]}', ";
            $str .= "Function: " . $this->debugCallable($tuple[0]);
            $result = "> {$str}\n{$result}";
            $stack[] = $str;
        }

        foreach (array_keys($stack) as $k) {
            $result .= "< {$stack[$k]}\n";
        }

        return $result;
    }

    /**
     * Set the HTTP handler that actually returns a promise.
     *
     * @param callable $handler Accepts a request and array of options and
     *                          returns a Promise.
     */
    public function setHandler(callable $handler)
    {
        $this->handler = $handler;
        $this->cached = null;
    }

    /**
     * Returns true if the builder has a handler.
     *
     * @return bool
     */
    public function hasHandler()
    {
        return (bool) $this->handler;
    }

    /**
     * Unshift a middleware to the bottom of the stack.
     *
     * @param callable $middleware Middleware function
     * @param string   $name       Name to register for this middleware.
     */
    public function unshift(callable $middleware, $name = null)
    {
        array_unshift($this->stack, [$middleware, $name]);
        $this->cached = null;
    }

    /**
     * Push a middleware to the top of the stack.
     *
     * @param callable $middleware Middleware function
     * @param string   $name       Name to register for this middleware.
     */
    public function push(callable $middleware, $name = '')
    {
        $this->stack[] = [$middleware, $name];
        $this->cached = null;
    }

    /**
     * Add a middleware before another middleware by name.
     *
     * @param string   $findName   Middleware to find
     * @param callable $middleware Middleware function
     * @param string   $withName   Name to register for this middleware.
     */
    public function before($findName, callable $middleware, $withName = '')
    {
        $this->splice($findName, $withName, $middleware, true);
    }

    /**
     * Add a middleware after another middleware by name.
     *
     * @param string   $findName   Middleware to find
     * @param callable $middleware Middleware function
     * @param string   $withName   Name to register for this middleware.
     */
    public function after($findName, callable $middleware, $withName = '')
    {
        $this->splice($findName, $withName, $middleware, false);
    }

    /**
     * Remove a middleware by instance or name from the stack.
     *
     * @param callable|string $remove Middleware to remove by instance or name.
     */
    public function remove($remove)
    {
        $this->cached = null;
        $idx = is_callable($remove) ? 0 : 1;
        $this->stack = array_values(array_filter(
            $this->stack,
            function ($tuple) use ($idx, $remove) {
                return $tuple[$idx] !== $remove;
            }
        ));
    }

    /**
     * Compose the middleware and handler into a single callable function.
     *
     * @return callable
     */
    public function resolve()
    {
        if (!$this->cached) {
            if (!($prev = $this->handler)) {
                throw new \LogicException('No handler has been specified');
            }

            foreach (array_reverse($this->stack) as $fn) {
                $prev = $fn[0]($prev);
            }

            $this->cached = $prev;
        }

        return $this->cached;
    }

    /**
     * @param $name
     * @return int
     */
    private function findByName($name)
    {
        foreach ($this->stack as $k => $v) {
            if ($v[1] === $name) {
                return $k;
            }
        }

        throw new \InvalidArgumentException("Middleware not found: $name");
    }

    /**
     * Splices a function into the middleware list at a specific position.
     *
     * @param          $findName
     * @param          $withName
     * @param callable $middleware
     * @param          $before
     */
    private function splice($findName, $withName, callable $middleware, $before)
    {
        $this->cached = null;
        $idx = $this->findByName($findName);
        $tuple = [$middleware, $withName];

        if ($before) {
            if ($idx === 0) {
                array_unshift($this->stack, $tuple);
            } else {
                $replacement = [$tuple, $this->stack[$idx]];
                array_splice($this->stack, $idx, 1, $replacement);
            }
        } elseif ($idx === count($this->stack) - 1) {
            $this->stack[] = $tuple;
        } else {
            $replacement = [$this->stack[$idx], $tuple];
            array_splice($this->stack, $idx, 1, $replacement);
        }
    }

    /**
     * Provides a debug string for a given callable.
     *
     * @param array|callable $fn Function to write as a string.
     *
     * @return string
     */
    private function debugCallable($fn)
    {
        if (is_string($fn)) {
            return "callable({$fn})";
        }

        if (is_array($fn)) {
            return is_string($fn[0])
                ? "callable({$fn[0]}::{$fn[1]})"
                : "callable(['" . get_class($fn[0]) . "', '{$fn[1]}'])";
        }

        return 'callable(' . spl_object_hash($fn) . ')';
    }
}
<?php
namespace GuzzleHttp;

use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Formats log messages using variable substitutions for requests, responses,
 * and other transactional data.
 *
 * The following variable substitutions are supported:
 *
 * - {request}:        Full HTTP request message
 * - {response}:       Full HTTP response message
 * - {ts}:             ISO 8601 date in GMT
 * - {date_iso_8601}   ISO 8601 date in GMT
 * - {date_common_log} Apache common log date using the configured timezone.
 * - {host}:           Host of the request
 * - {method}:         Method of the request
 * - {uri}:            URI of the request
 * - {host}:           Host of the request
 * - {version}:        Protocol version
 * - {target}:         Request target of the request (path + query + fragment)
 * - {hostname}:       Hostname of the machine that sent the request
 * - {code}:           Status code of the response (if available)
 * - {phrase}:         Reason phrase of the response  (if available)
 * - {error}:          Any error messages (if available)
 * - {req_header_*}:   Replace `*` with the lowercased name of a request header to add to the message
 * - {res_header_*}:   Replace `*` with the lowercased name of a response header to add to the message
 * - {req_headers}:    Request headers
 * - {res_headers}:    Response headers
 * - {req_body}:       Request body
 * - {res_body}:       Response body
 */
class MessageFormatter
{
    /**
     * Apache Common Log Format.
     * @link http://httpd.apache.org/docs/2.4/logs.html#common
     * @var string
     */
    const CLF = "{hostname} {req_header_User-Agent} - [{date_common_log}] \"{method} {target} HTTP/{version}\" {code} {res_header_Content-Length}";
    const DEBUG = ">>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}";
    const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}';

    /** @var string Template used to format log messages */
    private $template;

    /**
     * @param string $template Log message template
     */
    public function __construct($template = self::CLF)
    {
        $this->template = $template ?: self::CLF;
    }

    /**
     * Returns a formatted message string.
     *
     * @param RequestInterface  $request  Request that was sent
     * @param ResponseInterface $response Response that was received
     * @param \Exception        $error    Exception that was received
     *
     * @return string
     */
    public function format(
        RequestInterface $request,
        ResponseInterface $response = null,
        \Exception $error = null
    ) {
        $cache = [];

        return preg_replace_callback(
            '/{\s*([A-Za-z_\-\.0-9]+)\s*}/',
            function (array $matches) use ($request, $response, $error, &$cache) {

                if (isset($cache[$matches[1]])) {
                    return $cache[$matches[1]];
                }

                $result = '';
                switch ($matches[1]) {
                    case 'request':
                        $result = Psr7\str($request);
                        break;
                    case 'response':
                        $result = $response ? Psr7\str($response) : '';
                        break;
                    case 'req_headers':
                        $result = trim($request->getMethod()
                                . ' ' . $request->getRequestTarget())
                            . ' HTTP/' . $request->getProtocolVersion() . "\r\n"
                            . $this->headers($request);
                        break;
                    case 'res_headers':
                        $result = $response ?
                            sprintf(
                                'HTTP/%s %d %s',
                                $response->getProtocolVersion(),
                                $response->getStatusCode(),
                                $response->getReasonPhrase()
                            ) . "\r\n" . $this->headers($response)
                            : 'NULL';
                        break;
                    case 'req_body':
                        $result = $request->getBody();
                        break;
                    case 'res_body':
                        $result = $response ? $response->getBody() : 'NULL';
                        break;
                    case 'ts':
                    case 'date_iso_8601':
                        $result = gmdate('c');
                        break;
                    case 'date_common_log':
                        $result = date('d/M/Y:H:i:s O');
                        break;
                    case 'method':
                        $result = $request->getMethod();
                        break;
                    case 'version':
                        $result = $request->getProtocolVersion();
                        break;
                    case 'uri':
                    case 'url':
                        $result = $request->getUri();
                        break;
                    case 'target':
                        $result = $request->getRequestTarget();
                        break;
                    case 'req_version':
                        $result = $request->getProtocolVersion();
                        break;
                    case 'res_version':
                        $result = $response
                            ? $response->getProtocolVersion()
                            : 'NULL';
                        break;
                    case 'host':
                        $result = $request->getHeaderLine('Host');
                        break;
                    case 'hostname':
                        $result = gethostname();
                        break;
                    case 'code':
                        $result = $response ? $response->getStatusCode() : 'NULL';
                        break;
                    case 'phrase':
                        $result = $response ? $response->getReasonPhrase() : 'NULL';
                        break;
                    case 'error':
                        $result = $error ? $error->getMessage() : 'NULL';
                        break;
                    default:
                        // handle prefixed dynamic headers
                        if (strpos($matches[1], 'req_header_') === 0) {
                            $result = $request->getHeaderLine(substr($matches[1], 11));
                        } elseif (strpos($matches[1], 'res_header_') === 0) {
                            $result = $response
                                ? $response->getHeaderLine(substr($matches[1], 11))
                                : 'NULL';
                        }
                }

                $cache[$matches[1]] = $result;
                return $result;
            },
            $this->template
        );
    }

    private function headers(MessageInterface $message)
    {
        $result = '';
        foreach ($message->getHeaders() as $name => $values) {
            $result .= $name . ': ' . implode(', ', $values) . "\r\n";
        }

        return trim($result);
    }
}
<?php
namespace GuzzleHttp;

use GuzzleHttp\Cookie\CookieJarInterface;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Psr7;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

/**
 * Functions used to create and wrap handlers with handler middleware.
 */
final class Middleware
{
    /**
     * Middleware that adds cookies to requests.
     *
     * The options array must be set to a CookieJarInterface in order to use
     * cookies. This is typically handled for you by a client.
     *
     * @return callable Returns a function that accepts the next handler.
     */
    public static function cookies()
    {
        return function (callable $handler) {
            return function ($request, array $options) use ($handler) {
                if (empty($options['cookies'])) {
                    return $handler($request, $options);
                } elseif (!($options['cookies'] instanceof CookieJarInterface)) {
                    throw new \InvalidArgumentException('cookies must be an instance of GuzzleHttp\Cookie\CookieJarInterface');
                }
                $cookieJar = $options['cookies'];
                $request = $cookieJar->withCookieHeader($request);
                return $handler($request, $options)
                    ->then(function ($response) use ($cookieJar, $request) {
                        $cookieJar->extractCookies($request, $response);
                        return $response;
                    }
                );
            };
        };
    }

    /**
     * Middleware that throws exceptions for 4xx or 5xx responses when the
     * "http_error" request option is set to true.
     *
     * @return callable Returns a function that accepts the next handler.
     */
    public static function httpErrors()
    {
        return function (callable $handler) {
            return function ($request, array $options) use ($handler) {
                if (empty($options['http_errors'])) {
                    return $handler($request, $options);
                }
                return $handler($request, $options)->then(
                    function (ResponseInterface $response) use ($request, $handler) {
                        $code = $response->getStatusCode();
                        if ($code < 400) {
                            return $response;
                        }
                        throw RequestException::create($request, $response);
                    }
                );
            };
        };
    }

    /**
     * Middleware that pushes history data to an ArrayAccess container.
     *
     * @param array $container Container to hold the history (by reference).
     *
     * @return callable Returns a function that accepts the next handler.
     * @throws \InvalidArgumentException if container is not an array or ArrayAccess.
     */
    public static function history(&$container)
    {
        if (!is_array($container) && !$container instanceof \ArrayAccess) {
            throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess');
        }

        return function (callable $handler) use (&$container) {
            return function ($request, array $options) use ($handler, &$container) {
                return $handler($request, $options)->then(
                    function ($value) use ($request, &$container, $options) {
                        $container[] = [
                            'request'  => $request,
                            'response' => $value,
                            'error'    => null,
                            'options'  => $options
                        ];
                        return $value;
                    },
                    function ($reason) use ($request, &$container, $options) {
                        $container[] = [
                            'request'  => $request,
                            'response' => null,
                            'error'    => $reason,
                            'options'  => $options
                        ];
                        return new RejectedPromise($reason);
                    }
                );
            };
        };
    }

    /**
     * Middleware that invokes a callback before and after sending a request.
     *
     * The provided listener cannot modify or alter the response. It simply
     * "taps" into the chain to be notified before returning the promise. The
     * before listener accepts a request and options array, and the after
     * listener accepts a request, options array, and response promise.
     *
     * @param callable $before Function to invoke before forwarding the request.
     * @param callable $after  Function invoked after forwarding.
     *
     * @return callable Returns a function that accepts the next handler.
     */
    public static function tap(callable $before = null, callable $after = null)
    {
        return function (callable $handler) use ($before, $after) {
            return function ($request, array $options) use ($handler, $before, $after) {
                if ($before) {
                    $before($request, $options);
                }
                $response = $handler($request, $options);
                if ($after) {
                    $after($request, $options, $response);
                }
                return $response;
            };
        };
    }

    /**
     * Middleware that handles request redirects.
     *
     * @return callable Returns a function that accepts the next handler.
     */
    public static function redirect()
    {
        return function (callable $handler) {
            return new RedirectMiddleware($handler);
        };
    }

    /**
     * Middleware that retries requests based on the boolean result of
     * invoking the provided "decider" function.
     *
     * If no delay function is provided, a simple implementation of exponential
     * backoff will be utilized.
     *
     * @param callable $decider Function that accepts the number of retries,
     *                          a request, [response], and [exception] and
     *                          returns true if the request is to be retried.
     * @param callable $delay   Function that accepts the number of retries and
     *                          returns the number of milliseconds to delay.
     *
     * @return callable Returns a function that accepts the next handler.
     */
    public static function retry(callable $decider, callable $delay = null)
    {
        return function (callable $handler) use ($decider, $delay) {
            return new RetryMiddleware($decider, $handler, $delay);
        };
    }

    /**
     * Middleware that logs requests, responses, and errors using a message
     * formatter.
     *
     * @param LoggerInterface  $logger Logs messages.
     * @param MessageFormatter $formatter Formatter used to create message strings.
     * @param string           $logLevel Level at which to log requests.
     *
     * @return callable Returns a function that accepts the next handler.
     */
    public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = LogLevel::INFO)
    {
        return function (callable $handler) use ($logger, $formatter, $logLevel) {
            return function ($request, array $options) use ($handler, $logger, $formatter, $logLevel) {
                return $handler($request, $options)->then(
                    function ($response) use ($logger, $request, $formatter, $logLevel) {
                        $message = $formatter->format($request, $response);
                        $logger->log($logLevel, $message);
                        return $response;
                    },
                    function ($reason) use ($logger, $request, $formatter) {
                        $response = $reason instanceof RequestException
                            ? $reason->getResponse()
                            : null;
                        $message = $formatter->format($request, $response, $reason);
                        $logger->notice($message);
                        return \GuzzleHttp\Promise\rejection_for($reason);
                    }
                );
            };
        };
    }

    /**
     * This middleware adds a default content-type if possible, a default
     * content-length or transfer-encoding header, and the expect header.
     *
     * @return callable
     */
    public static function prepareBody()
    {
        return function (callable $handler) {
            return new PrepareBodyMiddleware($handler);
        };
    }

    /**
     * Middleware that applies a map function to the request before passing to
     * the next handler.
     *
     * @param callable $fn Function that accepts a RequestInterface and returns
     *                     a RequestInterface.
     * @return callable
     */
    public static function mapRequest(callable $fn)
    {
        return function (callable $handler) use ($fn) {
            return function ($request, array $options) use ($handler, $fn) {
                return $handler($fn($request), $options);
            };
        };
    }

    /**
     * Middleware that applies a map function to the resolved promise's
     * response.
     *
     * @param callable $fn Function that accepts a ResponseInterface and
     *                     returns a ResponseInterface.
     * @return callable
     */
    public static function mapResponse(callable $fn)
    {
        return function (callable $handler) use ($fn) {
            return function ($request, array $options) use ($handler, $fn) {
                return $handler($request, $options)->then($fn);
            };
        };
    }
}
<?php
namespace GuzzleHttp;

use GuzzleHttp\Promise\PromisorInterface;
use Psr\Http\Message\RequestInterface;
use GuzzleHttp\Promise\EachPromise;

/**
 * Sends and iterator of requests concurrently using a capped pool size.
 *
 * The pool will read from an iterator until it is cancelled or until the
 * iterator is consumed. When a request is yielded, the request is sent after
 * applying the "request_options" request options (if provided in the ctor).
 *
 * When a function is yielded by the iterator, the function is provided the
 * "request_options" array that should be merged on top of any existing
 * options, and the function MUST then return a wait-able promise.
 */
class Pool implements PromisorInterface
{
    /** @var EachPromise */
    private $each;

    /**
     * @param ClientInterface $client   Client used to send the requests.
     * @param array|\Iterator $requests Requests or functions that return
     *                                  requests to send concurrently.
     * @param array           $config   Associative array of options
     *     - concurrency: (int) Maximum number of requests to send concurrently
     *     - options: Array of request options to apply to each request.
     *     - fulfilled: (callable) Function to invoke when a request completes.
     *     - rejected: (callable) Function to invoke when a request is rejected.
     */
    public function __construct(
        ClientInterface $client,
        $requests,
        array $config = []
    ) {
        // Backwards compatibility.
        if (isset($config['pool_size'])) {
            $config['concurrency'] = $config['pool_size'];
        } elseif (!isset($config['concurrency'])) {
            $config['concurrency'] = 25;
        }

        if (isset($config['options'])) {
            $opts = $config['options'];
            unset($config['options']);
        } else {
            $opts = [];
        }

        $iterable = \GuzzleHttp\Promise\iter_for($requests);
        $requests = function () use ($iterable, $client, $opts) {
            foreach ($iterable as $key => $rfn) {
                if ($rfn instanceof RequestInterface) {
                    yield $key => $client->sendAsync($rfn, $opts);
                } elseif (is_callable($rfn)) {
                    yield $key => $rfn($opts);
                } else {
                    throw new \InvalidArgumentException('Each value yielded by '
                        . 'the iterator must be a Psr7\Http\Message\RequestInterface '
                        . 'or a callable that returns a promise that fulfills '
                        . 'with a Psr7\Message\Http\ResponseInterface object.');
                }
            }
        };

        $this->each = new EachPromise($requests(), $config);
    }

    public function promise()
    {
        return $this->each->promise();
    }

    /**
     * Sends multiple requests concurrently and returns an array of responses
     * and exceptions that uses the same ordering as the provided requests.
     *
     * IMPORTANT: This method keeps every request and response in memory, and
     * as such, is NOT recommended when sending a large number or an
     * indeterminate number of requests concurrently.
     *
     * @param ClientInterface $client   Client used to send the requests
     * @param array|\Iterator $requests Requests to send concurrently.
     * @param array           $options  Passes through the options available in
     *                                  {@see GuzzleHttp\Pool::__construct}
     *
     * @return array Returns an array containing the response or an exception
     *               in the same order that the requests were sent.
     * @throws \InvalidArgumentException if the event format is incorrect.
     */
    public static function batch(
        ClientInterface $client,
        $requests,
        array $options = []
    ) {
        $res = [];
        self::cmpCallback($options, 'fulfilled', $res);
        self::cmpCallback($options, 'rejected', $res);
        $pool = new static($client, $requests, $options);
        $pool->promise()->wait();
        ksort($res);

        return $res;
    }

    private static function cmpCallback(array &$options, $name, array &$results)
    {
        if (!isset($options[$name])) {
            $options[$name] = function ($v, $k) use (&$results) {
                $results[$k] = $v;
            };
        } else {
            $currentFn = $options[$name];
            $options[$name] = function ($v, $k) use (&$results, $currentFn) {
                $currentFn($v, $k);
                $results[$k] = $v;
            };
        }
    }
}
<?php
namespace GuzzleHttp;

use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;

/**
 * Prepares requests that contain a body, adding the Content-Length,
 * Content-Type, and Expect headers.
 */
class PrepareBodyMiddleware
{
    /** @var callable  */
    private $nextHandler;

    /** @var array */
    private static $skipMethods = ['GET' => true, 'HEAD' => true];

    /**
     * @param callable $nextHandler Next handler to invoke.
     */
    public function __construct(callable $nextHandler)
    {
        $this->nextHandler = $nextHandler;
    }

    /**
     * @param RequestInterface $request
     * @param array            $options
     *
     * @return PromiseInterface
     */
    public function __invoke(RequestInterface $request, array $options)
    {
        $fn = $this->nextHandler;

        // Don't do anything if the request has no body.
        if (isset(self::$skipMethods[$request->getMethod()])
            || $request->getBody()->getSize() === 0
        ) {
            return $fn($request, $options);
        }

        $modify = [];

        // Add a default content-type if possible.
        if (!$request->hasHeader('Content-Type')) {
            if ($uri = $request->getBody()->getMetadata('uri')) {
                if ($type = Psr7\mimetype_from_filename($uri)) {
                    $modify['set_headers']['Content-Type'] = $type;
                }
            }
        }

        // Add a default content-length or transfer-encoding header.
        if (!isset(self::$skipMethods[$request->getMethod()])
            && !$request->hasHeader('Content-Length')
            && !$request->hasHeader('Transfer-Encoding')
        ) {
            $size = $request->getBody()->getSize();
            if ($size !== null) {
                $modify['set_headers']['Content-Length'] = $size;
            } else {
                $modify['set_headers']['Transfer-Encoding'] = 'chunked';
            }
        }

        // Add the expect header if needed.
        $this->addExpectHeader($request, $options, $modify);

        return $fn(Psr7\modify_request($request, $modify), $options);
    }

    private function addExpectHeader(
        RequestInterface $request,
        array $options,
        array &$modify
    ) {
        // Determine if the Expect header should be used
        if ($request->hasHeader('Expect')) {
            return;
        }

        $expect = isset($options['expect']) ? $options['expect'] : null;

        // Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0
        if ($expect === false || $request->getProtocolVersion() < 1.1) {
            return;
        }

        // The expect header is unconditionally enabled
        if ($expect === true) {
            $modify['set_headers']['Expect'] = '100-Continue';
            return;
        }

        // By default, send the expect header when the payload is > 1mb
        if ($expect === null) {
            $expect = 1048576;
        }

        // Always add if the body cannot be rewound, the size cannot be
        // determined, or the size is greater than the cutoff threshold
        $body = $request->getBody();
        $size = $body->getSize();

        if ($size === null || $size >= (int) $expect || !$body->isSeekable()) {
            $modify['set_headers']['Expect'] = '100-Continue';
        }
    }
}
<?php
namespace GuzzleHttp;

use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Exception\TooManyRedirectsException;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;

/**
 * Request redirect middleware.
 *
 * Apply this middleware like other middleware using
 * {@see GuzzleHttp\Middleware::redirect()}.
 */
class RedirectMiddleware
{
    const HISTORY_HEADER = 'X-Guzzle-Redirect-History';

    public static $defaultSettings = [
        'max'             => 5,
        'protocols'       => ['http', 'https'],
        'strict'          => false,
        'referer'         => false,
        'track_redirects' => false,
    ];

    /** @var callable  */
    private $nextHandler;

    /**
     * @param callable $nextHandler Next handler to invoke.
     */
    public function __construct(callable $nextHandler)
    {
        $this->nextHandler = $nextHandler;
    }

    /**
     * @param RequestInterface $request
     * @param array            $options
     *
     * @return PromiseInterface
     */
    public function __invoke(RequestInterface $request, array $options)
    {
        $fn = $this->nextHandler;

        if (empty($options['allow_redirects'])) {
            return $fn($request, $options);
        }

        if ($options['allow_redirects'] === true) {
            $options['allow_redirects'] = self::$defaultSettings;
        } elseif (!is_array($options['allow_redirects'])) {
            throw new \InvalidArgumentException('allow_redirects must be true, false, or array');
        } else {
            // Merge the default settings with the provided settings
            $options['allow_redirects'] += self::$defaultSettings;
        }

        if (empty($options['allow_redirects']['max'])) {
            return $fn($request, $options);
        }

        return $fn($request, $options)
            ->then(function (ResponseInterface $response) use ($request, $options) {
                return $this->checkRedirect($request, $options, $response);
            });
    }

    /**
     * @param RequestInterface  $request
     * @param array             $options
     * @param ResponseInterface|PromiseInterface $response
     *
     * @return ResponseInterface|PromiseInterface
     */
    public function checkRedirect(
        RequestInterface $request,
        array $options,
        ResponseInterface $response
    ) {
        if (substr($response->getStatusCode(), 0, 1) != '3'
            || !$response->hasHeader('Location')
        ) {
            return $response;
        }

        $this->guardMax($request, $options);
        $nextRequest = $this->modifyRequest($request, $options, $response);

        if (isset($options['allow_redirects']['on_redirect'])) {
            call_user_func(
                $options['allow_redirects']['on_redirect'],
                $request,
                $response,
                $nextRequest->getUri()
            );
        }

        /** @var PromiseInterface|ResponseInterface $promise */
        $promise = $this($nextRequest, $options);

        // Add headers to be able to track history of redirects.
        if (!empty($options['allow_redirects']['track_redirects'])) {
            return $this->withTracking(
                $promise,
                (string) $nextRequest->getUri()
            );
        }

        return $promise;
    }

    private function withTracking(PromiseInterface $promise, $uri)
    {
        return $promise->then(
            function (ResponseInterface $response) use ($uri) {
                // Note that we are pushing to the front of the list as this
                // would be an earlier response than what is currently present
                // in the history header.
                $header = $response->getHeader(self::HISTORY_HEADER);
                array_unshift($header, $uri);
                return $response->withHeader(self::HISTORY_HEADER, $header);
            }
        );
    }

    private function guardMax(RequestInterface $request, array &$options)
    {
        $current = isset($options['__redirect_count'])
            ? $options['__redirect_count']
            : 0;
        $options['__redirect_count'] = $current + 1;
        $max = $options['allow_redirects']['max'];

        if ($options['__redirect_count'] > $max) {
            throw new TooManyRedirectsException(
                "Will not follow more than {$max} redirects",
                $request
            );
        }
    }

    /**
     * @param RequestInterface  $request
     * @param array             $options
     * @param ResponseInterface $response
     *
     * @return RequestInterface
     */
    public function modifyRequest(
        RequestInterface $request,
        array $options,
        ResponseInterface $response
    ) {
        // Request modifications to apply.
        $modify = [];
        $protocols = $options['allow_redirects']['protocols'];

        // Use a GET request if this is an entity enclosing request and we are
        // not forcing RFC compliance, but rather emulating what all browsers
        // would do.
        $statusCode = $response->getStatusCode();
        if ($statusCode == 303 ||
            ($statusCode <= 302 && $request->getBody() && !$options['allow_redirects']['strict'])
        ) {
            $modify['method'] = 'GET';
            $modify['body'] = '';
        }

        $modify['uri'] = $this->redirectUri($request, $response, $protocols);
        Psr7\rewind_body($request);

        // Add the Referer header if it is told to do so and only
        // add the header if we are not redirecting from https to http.
        if ($options['allow_redirects']['referer']
            && $modify['uri']->getScheme() === $request->getUri()->getScheme()
        ) {
            $uri = $request->getUri()->withUserInfo('', '');
            $modify['set_headers']['Referer'] = (string) $uri;
        } else {
            $modify['remove_headers'][] = 'Referer';
        }

        // Remove Authorization header if host is different.
        if ($request->getUri()->getHost() !== $modify['uri']->getHost()) {
            $modify['remove_headers'][] = 'Authorization';
        }

        return Psr7\modify_request($request, $modify);
    }

    /**
     * Set the appropriate URL on the request based on the location header
     *
     * @param RequestInterface  $request
     * @param ResponseInterface $response
     * @param array             $protocols
     *
     * @return UriInterface
     */
    private function redirectUri(
        RequestInterface $request,
        ResponseInterface $response,
        array $protocols
    ) {
        $location = Psr7\Uri::resolve(
            $request->getUri(),
            $response->getHeaderLine('Location')
        );

        // Ensure that the redirect URI is allowed based on the protocols.
        if (!in_array($location->getScheme(), $protocols)) {
            throw new BadResponseException(
                sprintf(
                    'Redirect URI, %s, does not use one of the allowed redirect protocols: %s',
                    $location,
                    implode(', ', $protocols)
                ),
                $request,
                $response
            );
        }

        return $location;
    }
}
<?php
namespace GuzzleHttp;

/**
 * This class contains a list of built-in Guzzle request options.
 *
 * More documentation for each option can be found at http://guzzlephp.org/.
 *
 * @link http://docs.guzzlephp.org/en/v6/request-options.html
 */
final class RequestOptions
{
    /**
     * allow_redirects: (bool|array) Controls redirect behavior. Pass false
     * to disable redirects, pass true to enable redirects, pass an
     * associative to provide custom redirect settings. Defaults to "false".
     * This option only works if your handler has the RedirectMiddleware. When
     * passing an associative array, you can provide the following key value
     * pairs:
     *
     * - max: (int, default=5) maximum number of allowed redirects.
     * - strict: (bool, default=false) Set to true to use strict redirects
     *   meaning redirect POST requests with POST requests vs. doing what most
     *   browsers do which is redirect POST requests with GET requests
     * - referer: (bool, default=true) Set to false to disable the Referer
     *   header.
     * - protocols: (array, default=['http', 'https']) Allowed redirect
     *   protocols.
     * - on_redirect: (callable) PHP callable that is invoked when a redirect
     *   is encountered. The callable is invoked with the request, the redirect
     *   response that was received, and the effective URI. Any return value
     *   from the on_redirect function is ignored.
     */
    const ALLOW_REDIRECTS = 'allow_redirects';

    /**
     * auth: (array) Pass an array of HTTP authentication parameters to use
     * with the request. The array must contain the username in index [0],
     * the password in index [1], and you can optionally provide a built-in
     * authentication type in index [2]. Pass null to disable authentication
     * for a request.
     */
    const AUTH = 'auth';

    /**
     * body: (string|null|callable|iterator|object) Body to send in the
     * request.
     */
    const BODY = 'body';

    /**
     * cert: (string|array) Set to a string to specify the path to a file
     * containing a PEM formatted SSL client side certificate. If a password
     * is required, then set cert to an array containing the path to the PEM
     * file in the first array element followed by the certificate password
     * in the second array element.
     */
    const CERT = 'cert';

    /**
     * cookies: (bool|GuzzleHttp\Cookie\CookieJarInterface, default=false)
     * Specifies whether or not cookies are used in a request or what cookie
     * jar to use or what cookies to send. This option only works if your
     * handler has the `cookie` middleware. Valid values are `false` and
     * an instance of {@see GuzzleHttp\Cookie\CookieJarInterface}.
     */
    const COOKIES = 'cookies';

    /**
     * connect_timeout: (float, default=0) Float describing the number of
     * seconds to wait while trying to connect to a server. Use 0 to wait
     * indefinitely (the default behavior).
     */
    const CONNECT_TIMEOUT = 'connect_timeout';

    /**
     * debug: (bool|resource) Set to true or set to a PHP stream returned by
     * fopen()  enable debug output with the HTTP handler used to send a
     * request.
     */
    const DEBUG = 'debug';

    /**
     * decode_content: (bool, default=true) Specify whether or not
     * Content-Encoding responses (gzip, deflate, etc.) are automatically
     * decoded.
     */
    const DECODE_CONTENT = 'decode_content';

    /**
     * delay: (int) The amount of time to delay before sending in milliseconds.
     */
    const DELAY = 'delay';

    /**
     * expect: (bool|integer) Controls the behavior of the
     * "Expect: 100-Continue" header.
     *
     * Set to `true` to enable the "Expect: 100-Continue" header for all
     * requests that sends a body. Set to `false` to disable the
     * "Expect: 100-Continue" header for all requests. Set to a number so that
     * the size of the payload must be greater than the number in order to send
     * the Expect header. Setting to a number will send the Expect header for
     * all requests in which the size of the payload cannot be determined or
     * where the body is not rewindable.
     *
     * By default, Guzzle will add the "Expect: 100-Continue" header when the
     * size of the body of a request is greater than 1 MB and a request is
     * using HTTP/1.1.
     */
    const EXPECT = 'expect';

    /**
     * form_params: (array) Associative array of form field names to values
     * where each value is a string or array of strings. Sets the Content-Type
     * header to application/x-www-form-urlencoded when no Content-Type header
     * is already present.
     */
    const FORM_PARAMS = 'form_params';

    /**
     * headers: (array) Associative array of HTTP headers. Each value MUST be
     * a string or array of strings.
     */
    const HEADERS = 'headers';

    /**
     * http_errors: (bool, default=true) Set to false to disable exceptions
     * when a non- successful HTTP response is received. By default,
     * exceptions will be thrown for 4xx and 5xx responses. This option only
     * works if your handler has the `httpErrors` middleware.
     */
    const HTTP_ERRORS = 'http_errors';

    /**
     * json: (mixed) Adds JSON data to a request. The provided value is JSON
     * encoded and a Content-Type header of application/json will be added to
     * the request if no Content-Type header is already present.
     */
    const JSON = 'json';

    /**
     * multipart: (array) Array of associative arrays, each containing a
     * required "name" key mapping to the form field, name, a required
     * "contents" key mapping to a StreamInterface|resource|string, an
     * optional "headers" associative array of custom headers, and an
     * optional "filename" key mapping to a string to send as the filename in
     * the part. If no "filename" key is present, then no "filename" attribute
     * will be added to the part.
     */
    const MULTIPART = 'multipart';

    /**
     * on_headers: (callable) A callable that is invoked when the HTTP headers
     * of the response have been received but the body has not yet begun to
     * download.
     */
    const ON_HEADERS = 'on_headers';

    /**
     * on_stats: (callable) allows you to get access to transfer statistics of
     * a request and access the lower level transfer details of the handler
     * associated with your client. ``on_stats`` is a callable that is invoked
     * when a handler has finished sending a request. The callback is invoked
     * with transfer statistics about the request, the response received, or
     * the error encountered. Included in the data is the total amount of time
     * taken to send the request.
     */
    const ON_STATS = 'on_stats';

    /**
     * progress: (callable) Defines a function to invoke when transfer
     * progress is made. The function accepts the following positional
     * arguments: the total number of bytes expected to be downloaded, the
     * number of bytes downloaded so far, the number of bytes expected to be
     * uploaded, the number of bytes uploaded so far.
     */
    const PROGRESS = 'progress';

    /**
     * proxy: (string|array) Pass a string to specify an HTTP proxy, or an
     * array to specify different proxies for different protocols (where the
     * key is the protocol and the value is a proxy string).
     */
    const PROXY = 'proxy';

    /**
     * query: (array|string) Associative array of query string values to add
     * to the request. This option uses PHP's http_build_query() to create
     * the string representation. Pass a string value if you need more
     * control than what this method provides
     */
    const QUERY = 'query';

    /**
     * sink: (resource|string|StreamInterface) Where the data of the
     * response is written to. Defaults to a PHP temp stream. Providing a
     * string will write data to a file by the given name.
     */
    const SINK = 'sink';

    /**
     * synchronous: (bool) Set to true to inform HTTP handlers that you intend
     * on waiting on the response. This can be useful for optimizations. Note
     * that a promise is still returned if you are using one of the async
     * client methods.
     */
    const SYNCHRONOUS = 'synchronous';

    /**
     * ssl_key: (array|string) Specify the path to a file containing a private
     * SSL key in PEM format. If a password is required, then set to an array
     * containing the path to the SSL key in the first array element followed
     * by the password required for the certificate in the second element.
     */
    const SSL_KEY = 'ssl_key';

    /**
     * stream: Set to true to attempt to stream a response rather than
     * download it all up-front.
     */
    const STREAM = 'stream';

    /**
     * verify: (bool|string, default=true) Describes the SSL certificate
     * verification behavior of a request. Set to true to enable SSL
     * certificate verification using the system CA bundle when available
     * (the default). Set to false to disable certificate verification (this
     * is insecure!). Set to a string to provide the path to a CA bundle on
     * disk to enable verification using a custom certificate.
     */
    const VERIFY = 'verify';

    /**
     * timeout: (float, default=0) Float describing the timeout of the
     * request in seconds. Use 0 to wait indefinitely (the default behavior).
     */
    const TIMEOUT = 'timeout';

    /**
     * version: (float) Specifies the HTTP protocol version to attempt to use.
     */
    const VERSION = 'version';
}
<?php
namespace GuzzleHttp;

use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;

/**
 * Middleware that retries requests based on the boolean result of
 * invoking the provided "decider" function.
 */
class RetryMiddleware
{
    /** @var callable  */
    private $nextHandler;

    /** @var callable */
    private $decider;

    /**
     * @param callable $decider     Function that accepts the number of retries,
     *                              a request, [response], and [exception] and
     *                              returns true if the request is to be
     *                              retried.
     * @param callable $nextHandler Next handler to invoke.
     * @param callable $delay       Function that accepts the number of retries
     *                              and returns the number of milliseconds to
     *                              delay.
     */
    public function __construct(
        callable $decider,
        callable $nextHandler,
        callable $delay = null
    ) {
        $this->decider = $decider;
        $this->nextHandler = $nextHandler;
        $this->delay = $delay ?: __CLASS__ . '::exponentialDelay';
    }

    /**
     * Default exponential backoff delay function.
     *
     * @param $retries
     *
     * @return int
     */
    public static function exponentialDelay($retries)
    {
        return (int) pow(2, $retries - 1);
    }

    /**
     * @param RequestInterface $request
     * @param array            $options
     *
     * @return PromiseInterface
     */
    public function __invoke(RequestInterface $request, array $options)
    {
        if (!isset($options['retries'])) {
            $options['retries'] = 0;
        }

        $fn = $this->nextHandler;
        return $fn($request, $options)
            ->then(
                $this->onFulfilled($request, $options),
                $this->onRejected($request, $options)
            );
    }

    private function onFulfilled(RequestInterface $req, array $options)
    {
        return function ($value) use ($req, $options) {
            if (!call_user_func(
                $this->decider,
                $options['retries'],
                $req,
                $value,
                null
            )) {
                return $value;
            }
            return $this->doRetry($req, $options);
        };
    }

    private function onRejected(RequestInterface $req, array $options)
    {
        return function ($reason) use ($req, $options) {
            if (!call_user_func(
                $this->decider,
                $options['retries'],
                $req,
                null,
                $reason
            )) {
                return new RejectedPromise($reason);
            }
            return $this->doRetry($req, $options);
        };
    }

    private function doRetry(RequestInterface $request, array $options)
    {
        $options['delay'] = call_user_func($this->delay, ++$options['retries']);

        return $this($request, $options);
    }
}
<?php
namespace GuzzleHttp;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;

/**
 * Represents data at the point after it was transferred either successfully
 * or after a network error.
 */
final class TransferStats
{
    private $request;
    private $response;
    private $transferTime;
    private $handlerStats;
    private $handlerErrorData;

    /**
     * @param RequestInterface  $request          Request that was sent.
     * @param ResponseInterface $response         Response received (if any)
     * @param null              $transferTime     Total handler transfer time.
     * @param mixed             $handlerErrorData Handler error data.
     * @param array             $handlerStats     Handler specific stats.
     */
    public function __construct(
        RequestInterface $request,
        ResponseInterface $response = null,
        $transferTime = null,
        $handlerErrorData = null,
        $handlerStats = []
    ) {
        $this->request = $request;
        $this->response = $response;
        $this->transferTime = $transferTime;
        $this->handlerErrorData = $handlerErrorData;
        $this->handlerStats = $handlerStats;
    }

    /**
     * @return RequestInterface
     */
    public function getRequest()
    {
        return $this->request;
    }

    /**
     * Returns the response that was received (if any).
     *
     * @return ResponseInterface|null
     */
    public function getResponse()
    {
        return $this->response;
    }

    /**
     * Returns true if a response was received.
     *
     * @return bool
     */
    public function hasResponse()
    {
        return $this->response !== null;
    }

    /**
     * Gets handler specific error data.
     *
     * This might be an exception, a integer representing an error code, or
     * anything else. Relying on this value assumes that you know what handler
     * you are using.
     *
     * @return mixed
     */
    public function getHandlerErrorData()
    {
        return $this->handlerErrorData;
    }

    /**
     * Get the effective URI the request was sent to.
     *
     * @return UriInterface
     */
    public function getEffectiveUri()
    {
        return $this->request->getUri();
    }

    /**
     * Get the estimated time the request was being transferred by the handler.
     *
     * @return float Time in seconds.
     */
    public function getTransferTime()
    {
        return $this->transferTime;
    }

    /**
     * Gets an array of all of the handler specific transfer data.
     *
     * @return array
     */
    public function getHandlerStats()
    {
        return $this->handlerStats;
    }

    /**
     * Get a specific handler statistic from the handler by name.
     *
     * @param string $stat Handler specific transfer stat to retrieve.
     *
     * @return mixed|null
     */
    public function getHandlerStat($stat)
    {
        return isset($this->handlerStats[$stat])
            ? $this->handlerStats[$stat]
            : null;
    }
}
<?php
namespace GuzzleHttp;

/**
 * Expands URI templates. Userland implementation of PECL uri_template.
 *
 * @link http://tools.ietf.org/html/rfc6570
 */
class UriTemplate
{
    /** @var string URI template */
    private $template;

    /** @var array Variables to use in the template expansion */
    private $variables;

    /** @var array Hash for quick operator lookups */
    private static $operatorHash = array(
        ''  => array('prefix' => '',  'joiner' => ',', 'query' => false),
        '+' => array('prefix' => '',  'joiner' => ',', 'query' => false),
        '#' => array('prefix' => '#', 'joiner' => ',', 'query' => false),
        '.' => array('prefix' => '.', 'joiner' => '.', 'query' => false),
        '/' => array('prefix' => '/', 'joiner' => '/', 'query' => false),
        ';' => array('prefix' => ';', 'joiner' => ';', 'query' => true),
        '?' => array('prefix' => '?', 'joiner' => '&', 'query' => true),
        '&' => array('prefix' => '&', 'joiner' => '&', 'query' => true)
    );

    /** @var array Delimiters */
    private static $delims = array(':', '/', '?', '#', '[', ']', '@', '!', '$',
        '&', '\'', '(', ')', '*', '+', ',', ';', '=');

    /** @var array Percent encoded delimiters */
    private static $delimsPct = array('%3A', '%2F', '%3F', '%23', '%5B', '%5D',
        '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C',
        '%3B', '%3D');

    public function expand($template, array $variables)
    {
        if (false === strpos($template, '{')) {
            return $template;
        }

        $this->template = $template;
        $this->variables = $variables;

        return preg_replace_callback(
            '/\{([^\}]+)\}/',
            [$this, 'expandMatch'],
            $this->template
        );
    }

    /**
     * Parse an expression into parts
     *
     * @param string $expression Expression to parse
     *
     * @return array Returns an associative array of parts
     */
    private function parseExpression($expression)
    {
        $result = array();

        if (isset(self::$operatorHash[$expression[0]])) {
            $result['operator'] = $expression[0];
            $expression = substr($expression, 1);
        } else {
            $result['operator'] = '';
        }

        foreach (explode(',', $expression) as $value) {
            $value = trim($value);
            $varspec = array();
            if ($colonPos = strpos($value, ':')) {
                $varspec['value'] = substr($value, 0, $colonPos);
                $varspec['modifier'] = ':';
                $varspec['position'] = (int) substr($value, $colonPos + 1);
            } elseif (substr($value, -1) == '*') {
                $varspec['modifier'] = '*';
                $varspec['value'] = substr($value, 0, -1);
            } else {
                $varspec['value'] = (string) $value;
                $varspec['modifier'] = '';
            }
            $result['values'][] = $varspec;
        }

        return $result;
    }

    /**
     * Process an expansion
     *
     * @param array $matches Matches met in the preg_replace_callback
     *
     * @return string Returns the replacement string
     */
    private function expandMatch(array $matches)
    {
        static $rfc1738to3986 = array('+' => '%20', '%7e' => '~');

        $replacements = array();
        $parsed = self::parseExpression($matches[1]);
        $prefix = self::$operatorHash[$parsed['operator']]['prefix'];
        $joiner = self::$operatorHash[$parsed['operator']]['joiner'];
        $useQuery = self::$operatorHash[$parsed['operator']]['query'];

        foreach ($parsed['values'] as $value) {

            if (!isset($this->variables[$value['value']])) {
                continue;
            }

            $variable = $this->variables[$value['value']];
            $actuallyUseQuery = $useQuery;
            $expanded = '';

            if (is_array($variable)) {

                $isAssoc = $this->isAssoc($variable);
                $kvp = array();
                foreach ($variable as $key => $var) {

                    if ($isAssoc) {
                        $key = rawurlencode($key);
                        $isNestedArray = is_array($var);
                    } else {
                        $isNestedArray = false;
                    }

                    if (!$isNestedArray) {
                        $var = rawurlencode($var);
                        if ($parsed['operator'] == '+' ||
                            $parsed['operator'] == '#'
                        ) {
                            $var = $this->decodeReserved($var);
                        }
                    }

                    if ($value['modifier'] == '*') {
                        if ($isAssoc) {
                            if ($isNestedArray) {
                                // Nested arrays must allow for deeply nested
                                // structures.
                                $var = strtr(
                                    http_build_query([$key => $var]),
                                    $rfc1738to3986
                                );
                            } else {
                                $var = $key . '=' . $var;
                            }
                        } elseif ($key > 0 && $actuallyUseQuery) {
                            $var = $value['value'] . '=' . $var;
                        }
                    }

                    $kvp[$key] = $var;
                }

                if (empty($variable)) {
                    $actuallyUseQuery = false;
                } elseif ($value['modifier'] == '*') {
                    $expanded = implode($joiner, $kvp);
                    if ($isAssoc) {
                        // Don't prepend the value name when using the explode
                        // modifier with an associative array.
                        $actuallyUseQuery = false;
                    }
                } else {
                    if ($isAssoc) {
                        // When an associative array is encountered and the
                        // explode modifier is not set, then the result must be
                        // a comma separated list of keys followed by their
                        // respective values.
                        foreach ($kvp as $k => &$v) {
                            $v = $k . ',' . $v;
                        }
                    }
                    $expanded = implode(',', $kvp);
                }

            } else {
                if ($value['modifier'] == ':') {
                    $variable = substr($variable, 0, $value['position']);
                }
                $expanded = rawurlencode($variable);
                if ($parsed['operator'] == '+' || $parsed['operator'] == '#') {
                    $expanded = $this->decodeReserved($expanded);
                }
            }

            if ($actuallyUseQuery) {
                if (!$expanded && $joiner != '&') {
                    $expanded = $value['value'];
                } else {
                    $expanded = $value['value'] . '=' . $expanded;
                }
            }

            $replacements[] = $expanded;
        }

        $ret = implode($joiner, $replacements);
        if ($ret && $prefix) {
            return $prefix . $ret;
        }

        return $ret;
    }

    /**
     * Determines if an array is associative.
     *
     * This makes the assumption that input arrays are sequences or hashes.
     * This assumption is a tradeoff for accuracy in favor of speed, but it
     * should work in almost every case where input is supplied for a URI
     * template.
     *
     * @param array $array Array to check
     *
     * @return bool
     */
    private function isAssoc(array $array)
    {
        return $array && array_keys($array)[0] !== 0;
    }

    /**
     * Removes percent encoding on reserved characters (used with + and #
     * modifiers).
     *
     * @param string $string String to fix
     *
     * @return string
     */
    private function decodeReserved($string)
    {
        return str_replace(self::$delimsPct, self::$delims, $string);
    }
}
<?php
namespace GuzzleHttp\Promise;

/**
 * Exception thrown when too many errors occur in the some() or any() methods.
 */
class AggregateException extends RejectionException
{
    public function __construct($msg, array $reasons)
    {
        parent::__construct(
            $reasons,
            sprintf('%s; %d rejected promises', $msg, count($reasons))
        );
    }
}
<?php
namespace GuzzleHttp\Promise;

/**
 * Exception that is set as the reason for a promise that has been cancelled.
 */
class CancellationException extends RejectionException
{
}
<?php
namespace GuzzleHttp\Promise;

/**
 * Represents a promise that iterates over many promises and invokes
 * side-effect functions in the process.
 */
class EachPromise implements PromisorInterface
{
    private $pending = [];

    /** @var \Iterator */
    private $iterable;

    /** @var callable|int */
    private $concurrency;

    /** @var callable */
    private $onFulfilled;

    /** @var callable */
    private $onRejected;

    /** @var Promise */
    private $aggregate;

    /** @var bool */
    private $mutex;

    /**
     * Configuration hash can include the following key value pairs:
     *
     * - fulfilled: (callable) Invoked when a promise fulfills. The function
     *   is invoked with three arguments: the fulfillment value, the index
     *   position from the iterable list of the promise, and the aggregate
     *   promise that manages all of the promises. The aggregate promise may
     *   be resolved from within the callback to short-circuit the promise.
     * - rejected: (callable) Invoked when a promise is rejected. The
     *   function is invoked with three arguments: the rejection reason, the
     *   index position from the iterable list of the promise, and the
     *   aggregate promise that manages all of the promises. The aggregate
     *   promise may be resolved from within the callback to short-circuit
     *   the promise.
     * - concurrency: (integer) Pass this configuration option to limit the
     *   allowed number of outstanding concurrently executing promises,
     *   creating a capped pool of promises. There is no limit by default.
     *
     * @param mixed    $iterable Promises or values to iterate.
     * @param array    $config   Configuration options
     */
    public function __construct($iterable, array $config = [])
    {
        $this->iterable = iter_for($iterable);

        if (isset($config['concurrency'])) {
            $this->concurrency = $config['concurrency'];
        }

        if (isset($config['fulfilled'])) {
            $this->onFulfilled = $config['fulfilled'];
        }

        if (isset($config['rejected'])) {
            $this->onRejected = $config['rejected'];
        }
    }

    public function promise()
    {
        if ($this->aggregate) {
            return $this->aggregate;
        }

        try {
            $this->createPromise();
            $this->iterable->rewind();
            $this->refillPending();
        } catch (\Throwable $e) {
            $this->aggregate->reject($e);
        } catch (\Exception $e) {
            $this->aggregate->reject($e);
        }

        return $this->aggregate;
    }

    private function createPromise()
    {
        $this->mutex = false;
        $this->aggregate = new Promise(function () {
            reset($this->pending);
            if (empty($this->pending) && !$this->iterable->valid()) {
                $this->aggregate->resolve(null);
                return;
            }

            // Consume a potentially fluctuating list of promises while
            // ensuring that indexes are maintained (precluding array_shift).
            while ($promise = current($this->pending)) {
                next($this->pending);
                $promise->wait();
                if ($this->aggregate->getState() !== PromiseInterface::PENDING) {
                    return;
                }
            }
        });

        // Clear the references when the promise is resolved.
        $clearFn = function () {
            $this->iterable = $this->concurrency = $this->pending = null;
            $this->onFulfilled = $this->onRejected = null;
        };

        $this->aggregate->then($clearFn, $clearFn);
    }

    private function refillPending()
    {
        if (!$this->concurrency) {
            // Add all pending promises.
            while ($this->addPending() && $this->advanceIterator());
            return;
        }

        // Add only up to N pending promises.
        $concurrency = is_callable($this->concurrency)
            ? call_user_func($this->concurrency, count($this->pending))
            : $this->concurrency;
        $concurrency = max($concurrency - count($this->pending), 0);
        // Concurrency may be set to 0 to disallow new promises.
        if (!$concurrency) {
            return;
        }
        // Add the first pending promise.
        $this->addPending();
        // Note this is special handling for concurrency=1 so that we do
        // not advance the iterator after adding the first promise. This
        // helps work around issues with generators that might not have the
        // next value to yield until promise callbacks are called.
        while (--$concurrency
            && $this->advanceIterator()
            && $this->addPending());
    }

    private function addPending()
    {
        if (!$this->iterable || !$this->iterable->valid()) {
            return false;
        }

        $promise = promise_for($this->iterable->current());
        $idx = $this->iterable->key();

        $this->pending[$idx] = $promise->then(
            function ($value) use ($idx) {
                if ($this->onFulfilled) {
                    call_user_func(
                        $this->onFulfilled, $value, $idx, $this->aggregate
                    );
                }
                $this->step($idx);
            },
            function ($reason) use ($idx) {
                if ($this->onRejected) {
                    call_user_func(
                        $this->onRejected, $reason, $idx, $this->aggregate
                    );
                }
                $this->step($idx);
            }
        );

        return true;
    }

    private function advanceIterator()
    {
        // Place a lock on the iterator so that we ensure to not recurse,
        // preventing fatal generator errors.
        if ($this->mutex) {
            return false;
        }

        $this->mutex = true;

        try {
            $this->iterable->next();
            $this->mutex = false;
            return true;
        } catch (\Throwable $e) {
            $this->aggregate->reject($e);
            $this->mutex = false;
            return false;
        } catch (\Exception $e) {
            $this->aggregate->reject($e);
            $this->mutex = false;
            return false;
        }
    }

    private function step($idx)
    {
        // If the promise was already resolved, then ignore this step.
        if ($this->aggregate->getState() !== PromiseInterface::PENDING) {
            return;
        }

        unset($this->pending[$idx]);

        // Only refill pending promises if we are not locked, preventing the
        // EachPromise to recursively invoke the provided iterator, which
        // cause a fatal error: "Cannot resume an already running generator"
        if ($this->advanceIterator() && !$this->checkIfFinished()) {
            // Add more pending promises if possible.
            $this->refillPending();
        }
    }

    private function checkIfFinished()
    {
        if (!$this->pending && !$this->iterable->valid()) {
            // Resolve the promise if there's nothing left to do.
            $this->aggregate->resolve(null);
            return true;
        }

        return false;
    }
}
<?php
namespace GuzzleHttp\Promise;

/**
 * A promise that has been fulfilled.
 *
 * Thenning off of this promise will invoke the onFulfilled callback
 * immediately and ignore other callbacks.
 */
class FulfilledPromise implements PromiseInterface
{
    private $value;

    public function __construct($value)
    {
        if (method_exists($value, 'then')) {
            throw new \InvalidArgumentException(
                'You cannot create a FulfilledPromise with a promise.');
        }

        $this->value = $value;
    }

    public function then(
        callable $onFulfilled = null,
        callable $onRejected = null
    ) {
        // Return itself if there is no onFulfilled function.
        if (!$onFulfilled) {
            return $this;
        }

        $queue = queue();
        $p = new Promise([$queue, 'run']);
        $value = $this->value;
        $queue->add(static function () use ($p, $value, $onFulfilled) {
            if ($p->getState() === self::PENDING) {
                try {
                    $p->resolve($onFulfilled($value));
                } catch (\Throwable $e) {
                    $p->reject($e);
                } catch (\Exception $e) {
                    $p->reject($e);
                }
            }
        });

        return $p;
    }

    public function otherwise(callable $onRejected)
    {
        return $this->then(null, $onRejected);
    }

    public function wait($unwrap = true, $defaultDelivery = null)
    {
        return $unwrap ? $this->value : null;
    }

    public function getState()
    {
        return self::FULFILLED;
    }

    public function resolve($value)
    {
        if ($value !== $this->value) {
            throw new \LogicException("Cannot resolve a fulfilled promise");
        }
    }

    public function reject($reason)
    {
        throw new \LogicException("Cannot reject a fulfilled promise");
    }

    public function cancel()
    {
        // pass
    }
}
<?php
namespace GuzzleHttp\Promise;

/**
 * Get the global task queue used for promise resolution.
 *
 * This task queue MUST be run in an event loop in order for promises to be
 * settled asynchronously. It will be automatically run when synchronously
 * waiting on a promise.
 *
 * <code>
 * while ($eventLoop->isRunning()) {
 *     GuzzleHttp\Promise\queue()->run();
 * }
 * </code>
 *
 * @return TaskQueue
 */
function queue()
{
    static $queue;

    if (!$queue) {
        $queue = new TaskQueue();
    }

    return $queue;
}

/**
 * Adds a function to run in the task queue when it is next `run()` and returns
 * a promise that is fulfilled or rejected with the result.
 *
 * @param callable $task Task function to run.
 *
 * @return PromiseInterface
 */
function task(callable $task)
{
    $queue = queue();
    $promise = new Promise([$queue, 'run']);
    $queue->add(function () use ($task, $promise) {
        try {
            $promise->resolve($task());
        } catch (\Throwable $e) {
            $promise->reject($e);
        } catch (\Exception $e) {
            $promise->reject($e);
        }
    });

    return $promise;
}

/**
 * Creates a promise for a value if the value is not a promise.
 *
 * @param mixed $value Promise or value.
 *
 * @return PromiseInterface
 */
function promise_for($value)
{
    if ($value instanceof PromiseInterface) {
        return $value;
    }

    // Return a Guzzle promise that shadows the given promise.
    if (method_exists($value, 'then')) {
        $wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null;
        $cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null;
        $promise = new Promise($wfn, $cfn);
        $value->then([$promise, 'resolve'], [$promise, 'reject']);
        return $promise;
    }

    return new FulfilledPromise($value);
}

/**
 * Creates a rejected promise for a reason if the reason is not a promise. If
 * the provided reason is a promise, then it is returned as-is.
 *
 * @param mixed $reason Promise or reason.
 *
 * @return PromiseInterface
 */
function rejection_for($reason)
{
    if ($reason instanceof PromiseInterface) {
        return $reason;
    }

    return new RejectedPromise($reason);
}

/**
 * Create an exception for a rejected promise value.
 *
 * @param mixed $reason
 *
 * @return \Exception|\Throwable
 */
function exception_for($reason)
{
    return $reason instanceof \Exception || $reason instanceof \Throwable
        ? $reason
        : new RejectionException($reason);
}

/**
 * Returns an iterator for the given value.
 *
 * @param mixed $value
 *
 * @return \Iterator
 */
function iter_for($value)
{
    if ($value instanceof \Iterator) {
        return $value;
    } elseif (is_array($value)) {
        return new \ArrayIterator($value);
    } else {
        return new \ArrayIterator([$value]);
    }
}

/**
 * Synchronously waits on a promise to resolve and returns an inspection state
 * array.
 *
 * Returns a state associative array containing a "state" key mapping to a
 * valid promise state. If the state of the promise is "fulfilled", the array
 * will contain a "value" key mapping to the fulfilled value of the promise. If
 * the promise is rejected, the array will contain a "reason" key mapping to
 * the rejection reason of the promise.
 *
 * @param PromiseInterface $promise Promise or value.
 *
 * @return array
 */
function inspect(PromiseInterface $promise)
{
    try {
        return [
            'state' => PromiseInterface::FULFILLED,
            'value' => $promise->wait()
        ];
    } catch (RejectionException $e) {
        return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()];
    } catch (\Throwable $e) {
        return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
    } catch (\Exception $e) {
        return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
    }
}

/**
 * Waits on all of the provided promises, but does not unwrap rejected promises
 * as thrown exception.
 *
 * Returns an array of inspection state arrays.
 *
 * @param PromiseInterface[] $promises Traversable of promises to wait upon.
 *
 * @return array
 * @see GuzzleHttp\Promise\inspect for the inspection state array format.
 */
function inspect_all($promises)
{
    $results = [];
    foreach ($promises as $key => $promise) {
        $results[$key] = inspect($promise);
    }

    return $results;
}

/**
 * Waits on all of the provided promises and returns the fulfilled values.
 *
 * Returns an array that contains the value of each promise (in the same order
 * the promises were provided). An exception is thrown if any of the promises
 * are rejected.
 *
 * @param mixed $promises Iterable of PromiseInterface objects to wait on.
 *
 * @return array
 * @throws \Exception on error
 * @throws \Throwable on error in PHP >=7
 */
function unwrap($promises)
{
    $results = [];
    foreach ($promises as $key => $promise) {
        $results[$key] = $promise->wait();
    }

    return $results;
}

/**
 * Given an array of promises, return a promise that is fulfilled when all the
 * items in the array are fulfilled.
 *
 * The promise's fulfillment value is an array with fulfillment values at
 * respective positions to the original array. If any promise in the array
 * rejects, the returned promise is rejected with the rejection reason.
 *
 * @param mixed $promises Promises or values.
 *
 * @return Promise
 */
function all($promises)
{
    $results = [];
    return each(
        $promises,
        function ($value, $idx) use (&$results) {
            $results[$idx] = $value;
        },
        function ($reason, $idx, Promise $aggregate) {
            $aggregate->reject($reason);
        }
    )->then(function () use (&$results) {
        ksort($results);
        return $results;
    });
}

/**
 * Initiate a competitive race between multiple promises or values (values will
 * become immediately fulfilled promises).
 *
 * When count amount of promises have been fulfilled, the returned promise is
 * fulfilled with an array that contains the fulfillment values of the winners
 * in order of resolution.
 *
 * This prommise is rejected with a {@see GuzzleHttp\Promise\AggregateException}
 * if the number of fulfilled promises is less than the desired $count.
 *
 * @param int   $count    Total number of promises.
 * @param mixed $promises Promises or values.
 *
 * @return Promise
 */
function some($count, $promises)
{
    $results = [];
    $rejections = [];

    return each(
        $promises,
        function ($value, $idx, PromiseInterface $p) use (&$results, $count) {
            if ($p->getState() !== PromiseInterface::PENDING) {
                return;
            }
            $results[$idx] = $value;
            if (count($results) >= $count) {
                $p->resolve(null);
            }
        },
        function ($reason) use (&$rejections) {
            $rejections[] = $reason;
        }
    )->then(
        function () use (&$results, &$rejections, $count) {
            if (count($results) !== $count) {
                throw new AggregateException(
                    'Not enough promises to fulfill count',
                    $rejections
                );
            }
            ksort($results);
            return array_values($results);
        }
    );
}

/**
 * Like some(), with 1 as count. However, if the promise fulfills, the
 * fulfillment value is not an array of 1 but the value directly.
 *
 * @param mixed $promises Promises or values.
 *
 * @return PromiseInterface
 */
function any($promises)
{
    return some(1, $promises)->then(function ($values) { return $values[0]; });
}

/**
 * Returns a promise that is fulfilled when all of the provided promises have
 * been fulfilled or rejected.
 *
 * The returned promise is fulfilled with an array of inspection state arrays.
 *
 * @param mixed $promises Promises or values.
 *
 * @return Promise
 * @see GuzzleHttp\Promise\inspect for the inspection state array format.
 */
function settle($promises)
{
    $results = [];

    return each(
        $promises,
        function ($value, $idx) use (&$results) {
            $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value];
        },
        function ($reason, $idx) use (&$results) {
            $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason];
        }
    )->then(function () use (&$results) {
        ksort($results);
        return $results;
    });
}

/**
 * Given an iterator that yields promises or values, returns a promise that is
 * fulfilled with a null value when the iterator has been consumed or the
 * aggregate promise has been fulfilled or rejected.
 *
 * $onFulfilled is a function that accepts the fulfilled value, iterator
 * index, and the aggregate promise. The callback can invoke any necessary side
 * effects and choose to resolve or reject the aggregate promise if needed.
 *
 * $onRejected is a function that accepts the rejection reason, iterator
 * index, and the aggregate promise. The callback can invoke any necessary side
 * effects and choose to resolve or reject the aggregate promise if needed.
 *
 * @param mixed    $iterable    Iterator or array to iterate over.
 * @param callable $onFulfilled
 * @param callable $onRejected
 *
 * @return Promise
 */
function each(
    $iterable,
    callable $onFulfilled = null,
    callable $onRejected = null
) {
    return (new EachPromise($iterable, [
        'fulfilled' => $onFulfilled,
        'rejected'  => $onRejected
    ]))->promise();
}

/**
 * Like each, but only allows a certain number of outstanding promises at any
 * given time.
 *
 * $concurrency may be an integer or a function that accepts the number of
 * pending promises and returns a numeric concurrency limit value to allow for
 * dynamic a concurrency size.
 *
 * @param mixed        $iterable
 * @param int|callable $concurrency
 * @param callable     $onFulfilled
 * @param callable     $onRejected
 *
 * @return mixed
 */
function each_limit(
    $iterable,
    $concurrency,
    callable $onFulfilled = null,
    callable $onRejected = null
) {
    return (new EachPromise($iterable, [
        'fulfilled'   => $onFulfilled,
        'rejected'    => $onRejected,
        'concurrency' => $concurrency
    ]))->promise();
}

/**
 * Like each_limit, but ensures that no promise in the given $iterable argument
 * is rejected. If any promise is rejected, then the aggregate promise is
 * rejected with the encountered rejection.
 *
 * @param mixed        $iterable
 * @param int|callable $concurrency
 * @param callable     $onFulfilled
 *
 * @return mixed
 */
function each_limit_all(
    $iterable,
    $concurrency,
    callable $onFulfilled = null
) {
    return each_limit(
        $iterable,
        $concurrency,
        $onFulfilled,
        function ($reason, $idx, PromiseInterface $aggregate) {
            $aggregate->reject($reason);
        }
    );
}

/**
 * Returns true if a promise is fulfilled.
 *
 * @param PromiseInterface $promise
 *
 * @return bool
 */
function is_fulfilled(PromiseInterface $promise)
{
    return $promise->getState() === PromiseInterface::FULFILLED;
}

/**
 * Returns true if a promise is rejected.
 *
 * @param PromiseInterface $promise
 *
 * @return bool
 */
function is_rejected(PromiseInterface $promise)
{
    return $promise->getState() === PromiseInterface::REJECTED;
}

/**
 * Returns true if a promise is fulfilled or rejected.
 *
 * @param PromiseInterface $promise
 *
 * @return bool
 */
function is_settled(PromiseInterface $promise)
{
    return $promise->getState() !== PromiseInterface::PENDING;
}

/**
 * Creates a promise that is resolved using a generator that yields values or
 * promises (somewhat similar to C#'s async keyword).
 *
 * When called, the coroutine function will start an instance of the generator
 * and returns a promise that is fulfilled with its final yielded value.
 *
 * Control is returned back to the generator when the yielded promise settles.
 * This can lead to less verbose code when doing lots of sequential async calls
 * with minimal processing in between.
 *
 *     use GuzzleHttp\Promise;
 *
 *     function createPromise($value) {
 *         return new Promise\FulfilledPromise($value);
 *     }
 *
 *     $promise = Promise\coroutine(function () {
 *         $value = (yield createPromise('a'));
 *         try {
 *             $value = (yield createPromise($value . 'b'));
 *         } catch (\Exception $e) {
 *             // The promise was rejected.
 *         }
 *         yield $value . 'c';
 *     });
 *
 *     // Outputs "abc"
 *     $promise->then(function ($v) { echo $v; });
 *
 * @param callable $generatorFn Generator function to wrap into a promise.
 *
 * @return Promise
 * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
 */
function coroutine(callable $generatorFn)
{
    $generator = $generatorFn();
    return __next_coroutine($generator->current(), $generator)->then();
}

/** @internal */
function __next_coroutine($yielded, \Generator $generator)
{
    return promise_for($yielded)->then(
        function ($value) use ($generator) {
            $nextYield = $generator->send($value);
            return $generator->valid()
                ? __next_coroutine($nextYield, $generator)
                : $value;
        },
        function ($reason) use ($generator) {
            $nextYield = $generator->throw(exception_for($reason));
            // The throw was caught, so keep iterating on the coroutine
            return __next_coroutine($nextYield, $generator);
        }
    );
}
<?php

// Don't redefine the functions if included multiple times.
if (!function_exists('GuzzleHttp\Promise\promise_for')) {
    require __DIR__ . '/functions.php';
}
<?php
namespace GuzzleHttp\Promise;

/**
 * Promises/A+ implementation that avoids recursion when possible.
 *
 * @link https://promisesaplus.com/
 */
class Promise implements PromiseInterface
{
    private $state = self::PENDING;
    private $result;
    private $cancelFn;
    private $waitFn;
    private $waitList;
    private $handlers = [];

    /**
     * @param callable $waitFn   Fn that when invoked resolves the promise.
     * @param callable $cancelFn Fn that when invoked cancels the promise.
     */
    public function __construct(
        callable $waitFn = null,
        callable $cancelFn = null
    ) {
        $this->waitFn = $waitFn;
        $this->cancelFn = $cancelFn;
    }

    public function then(
        callable $onFulfilled = null,
        callable $onRejected = null
    ) {
        if ($this->state === self::PENDING) {
            $p = new Promise(null, [$this, 'cancel']);
            $this->handlers[] = [$p, $onFulfilled, $onRejected];
            $p->waitList = $this->waitList;
            $p->waitList[] = $this;
            return $p;
        }

        // Return a fulfilled promise and immediately invoke any callbacks.
        if ($this->state === self::FULFILLED) {
            return $onFulfilled
                ? promise_for($this->result)->then($onFulfilled)
                : promise_for($this->result);
        }

        // It's either cancelled or rejected, so return a rejected promise
        // and immediately invoke any callbacks.
        $rejection = rejection_for($this->result);
        return $onRejected ? $rejection->then(null, $onRejected) : $rejection;
    }

    public function otherwise(callable $onRejected)
    {
        return $this->then(null, $onRejected);
    }

    public function wait($unwrap = true)
    {
        $this->waitIfPending();

        $inner = $this->result instanceof PromiseInterface
            ? $this->result->wait($unwrap)
            : $this->result;

        if ($unwrap) {
            if ($this->result instanceof PromiseInterface
                || $this->state === self::FULFILLED
            ) {
                return $inner;
            } else {
                // It's rejected so "unwrap" and throw an exception.
                throw exception_for($inner);
            }
        }
    }

    public function getState()
    {
        return $this->state;
    }

    public function cancel()
    {
        if ($this->state !== self::PENDING) {
            return;
        }

        $this->waitFn = $this->waitList = null;

        if ($this->cancelFn) {
            $fn = $this->cancelFn;
            $this->cancelFn = null;
            try {
                $fn();
            } catch (\Throwable $e) {
                $this->reject($e);
            } catch (\Exception $e) {
                $this->reject($e);
            }
        }

        // Reject the promise only if it wasn't rejected in a then callback.
        if ($this->state === self::PENDING) {
            $this->reject(new CancellationException('Promise has been cancelled'));
        }
    }

    public function resolve($value)
    {
        $this->settle(self::FULFILLED, $value);
    }

    public function reject($reason)
    {
        $this->settle(self::REJECTED, $reason);
    }

    private function settle($state, $value)
    {
        if ($this->state !== self::PENDING) {
            // Ignore calls with the same resolution.
            if ($state === $this->state && $value === $this->result) {
                return;
            }
            throw $this->state === $state
                ? new \LogicException("The promise is already {$state}.")
                : new \LogicException("Cannot change a {$this->state} promise to {$state}");
        }

        if ($value === $this) {
            throw new \LogicException('Cannot fulfill or reject a promise with itself');
        }

        // Clear out the state of the promise but stash the handlers.
        $this->state = $state;
        $this->result = $value;
        $handlers = $this->handlers;
        $this->handlers = null;
        $this->waitList = $this->waitFn = null;
        $this->cancelFn = null;

        if (!$handlers) {
            return;
        }

        // If the value was not a settled promise or a thenable, then resolve
        // it in the task queue using the correct ID.
        if (!method_exists($value, 'then')) {
            $id = $state === self::FULFILLED ? 1 : 2;
            // It's a success, so resolve the handlers in the queue.
            queue()->add(static function () use ($id, $value, $handlers) {
                foreach ($handlers as $handler) {
                    self::callHandler($id, $value, $handler);
                }
            });
        } elseif ($value instanceof Promise
            && $value->getState() === self::PENDING
        ) {
            // We can just merge our handlers onto the next promise.
            $value->handlers = array_merge($value->handlers, $handlers);
        } else {
            // Resolve the handlers when the forwarded promise is resolved.
            $value->then(
                static function ($value) use ($handlers) {
                    foreach ($handlers as $handler) {
                        self::callHandler(1, $value, $handler);
                    }
                },
                static function ($reason) use ($handlers) {
                    foreach ($handlers as $handler) {
                        self::callHandler(2, $reason, $handler);
                    }
                }
            );
        }
    }

    /**
     * Call a stack of handlers using a specific callback index and value.
     *
     * @param int   $index   1 (resolve) or 2 (reject).
     * @param mixed $value   Value to pass to the callback.
     * @param array $handler Array of handler data (promise and callbacks).
     *
     * @return array Returns the next group to resolve.
     */
    private static function callHandler($index, $value, array $handler)
    {
        /** @var PromiseInterface $promise */
        $promise = $handler[0];

        // The promise may have been cancelled or resolved before placing
        // this thunk in the queue.
        if ($promise->getState() !== self::PENDING) {
            return;
        }

        try {
            if (isset($handler[$index])) {
                $promise->resolve($handler[$index]($value));
            } elseif ($index === 1) {
                // Forward resolution values as-is.
                $promise->resolve($value);
            } else {
                // Forward rejections down the chain.
                $promise->reject($value);
            }
        } catch (\Throwable $reason) {
            $promise->reject($reason);
        } catch (\Exception $reason) {
            $promise->reject($reason);
        }
    }

    private function waitIfPending()
    {
        if ($this->state !== self::PENDING) {
            return;
        } elseif ($this->waitFn) {
            $this->invokeWaitFn();
        } elseif ($this->waitList) {
            $this->invokeWaitList();
        } else {
            // If there's not wait function, then reject the promise.
            $this->reject('Cannot wait on a promise that has '
                . 'no internal wait function. You must provide a wait '
                . 'function when constructing the promise to be able to '
                . 'wait on a promise.');
        }

        queue()->run();

        if ($this->state === self::PENDING) {
            $this->reject('Invoking the wait callback did not resolve the promise');
        }
    }

    private function invokeWaitFn()
    {
        try {
            $wfn = $this->waitFn;
            $this->waitFn = null;
            $wfn(true);
        } catch (\Exception $reason) {
            if ($this->state === self::PENDING) {
                // The promise has not been resolved yet, so reject the promise
                // with the exception.
                $this->reject($reason);
            } else {
                // The promise was already resolved, so there's a problem in
                // the application.
                throw $reason;
            }
        }
    }

    private function invokeWaitList()
    {
        $waitList = $this->waitList;
        $this->waitList = null;

        foreach ($waitList as $result) {
            $result->waitIfPending();
            while ($result->result instanceof Promise) {
                $result = $result->result;
                $result->waitIfPending();
            }
        }
    }
}
<?php
namespace GuzzleHttp\Promise;

/**
 * A promise represents the eventual result of an asynchronous operation.
 *
 * The primary way of interacting with a promise is through its then method,
 * which registers callbacks to receive either a promise’s eventual value or
 * the reason why the promise cannot be fulfilled.
 *
 * @link https://promisesaplus.com/
 */
interface PromiseInterface
{
    const PENDING = 'pending';
    const FULFILLED = 'fulfilled';
    const REJECTED = 'rejected';

    /**
     * Appends fulfillment and rejection handlers to the promise, and returns
     * a new promise resolving to the return value of the called handler.
     *
     * @param callable $onFulfilled Invoked when the promise fulfills.
     * @param callable $onRejected  Invoked when the promise is rejected.
     *
     * @return PromiseInterface
     */
    public function then(
        callable $onFulfilled = null,
        callable $onRejected = null
    );

    /**
     * Appends a rejection handler callback to the promise, and returns a new
     * promise resolving to the return value of the callback if it is called,
     * or to its original fulfillment value if the promise is instead
     * fulfilled.
     *
     * @param callable $onRejected Invoked when the promise is rejected.
     *
     * @return PromiseInterface
     */
    public function otherwise(callable $onRejected);

    /**
     * Get the state of the promise ("pending", "rejected", or "fulfilled").
     *
     * The three states can be checked against the constants defined on
     * PromiseInterface: PENDING, FULFILLED, and REJECTED.
     *
     * @return string
     */
    public function getState();

    /**
     * Resolve the promise with the given value.
     *
     * @param mixed $value
     * @throws \RuntimeException if the promise is already resolved.
     */
    public function resolve($value);

    /**
     * Reject the promise with the given reason.
     *
     * @param mixed $reason
     * @throws \RuntimeException if the promise is already resolved.
     */
    public function reject($reason);

    /**
     * Cancels the promise if possible.
     *
     * @link https://github.com/promises-aplus/cancellation-spec/issues/7
     */
    public function cancel();

    /**
     * Waits until the promise completes if possible.
     *
     * Pass $unwrap as true to unwrap the result of the promise, either
     * returning the resolved value or throwing the rejected exception.
     *
     * If the promise cannot be waited on, then the promise will be rejected.
     *
     * @param bool $unwrap
     *
     * @return mixed
     * @throws \LogicException if the promise has no wait function or if the
     *                         promise does not settle after waiting.
     */
    public function wait($unwrap = true);
}
<?php
namespace GuzzleHttp\Promise;

/**
 * Interface used with classes that return a promise.
 */
interface PromisorInterface
{
    /**
     * Returns a promise.
     *
     * @return PromiseInterface
     */
    public function promise();
}
<?php
namespace GuzzleHttp\Promise;

/**
 * A promise that has been rejected.
 *
 * Thenning off of this promise will invoke the onRejected callback
 * immediately and ignore other callbacks.
 */
class RejectedPromise implements PromiseInterface
{
    private $reason;

    public function __construct($reason)
    {
        if (method_exists($reason, 'then')) {
            throw new \InvalidArgumentException(
                'You cannot create a RejectedPromise with a promise.');
        }

        $this->reason = $reason;
    }

    public function then(
        callable $onFulfilled = null,
        callable $onRejected = null
    ) {
        // If there's no onRejected callback then just return self.
        if (!$onRejected) {
            return $this;
        }

        $queue = queue();
        $reason = $this->reason;
        $p = new Promise([$queue, 'run']);
        $queue->add(static function () use ($p, $reason, $onRejected) {
            if ($p->getState() === self::PENDING) {
                try {
                    // Return a resolved promise if onRejected does not throw.
                    $p->resolve($onRejected($reason));
                } catch (\Throwable $e) {
                    // onRejected threw, so return a rejected promise.
                    $p->reject($e);
                } catch (\Exception $e) {
                    // onRejected threw, so return a rejected promise.
                    $p->reject($e);
                }
            }
        });

        return $p;
    }

    public function otherwise(callable $onRejected)
    {
        return $this->then(null, $onRejected);
    }

    public function wait($unwrap = true, $defaultDelivery = null)
    {
        if ($unwrap) {
            throw exception_for($this->reason);
        }
    }

    public function getState()
    {
        return self::REJECTED;
    }

    public function resolve($value)
    {
        throw new \LogicException("Cannot resolve a rejected promise");
    }

    public function reject($reason)
    {
        if ($reason !== $this->reason) {
            throw new \LogicException("Cannot reject a rejected promise");
        }
    }

    public function cancel()
    {
        // pass
    }
}
<?php
namespace GuzzleHttp\Promise;

/**
 * A special exception that is thrown when waiting on a rejected promise.
 *
 * The reason value is available via the getReason() method.
 */
class RejectionException extends \RuntimeException
{
    /** @var mixed Rejection reason. */
    private $reason;

    /**
     * @param mixed $reason       Rejection reason.
     * @param string $description Optional description
     */
    public function __construct($reason, $description = null)
    {
        $this->reason = $reason;

        $message = 'The promise was rejected';

        if ($description) {
            $message .= ' with reason: ' . $description;
        } elseif (is_string($reason)
            || (is_object($reason) && method_exists($reason, '__toString'))
        ) {
            $message .= ' with reason: ' . $this->reason;
        } elseif ($reason instanceof \JsonSerializable) {
            $message .= ' with reason: '
                . json_encode($this->reason, JSON_PRETTY_PRINT);
        }

        parent::__construct($message);
    }

    /**
     * Returns the rejection reason.
     *
     * @return mixed
     */
    public function getReason()
    {
        return $this->reason;
    }
}
<?php
namespace GuzzleHttp\Promise;

/**
 * A task queue that executes tasks in a FIFO order.
 *
 * This task queue class is used to settle promises asynchronously and
 * maintains a constant stack size. You can use the task queue asynchronously
 * by calling the `run()` function of the global task queue in an event loop.
 *
 *     GuzzleHttp\Promise\queue()->run();
 */
class TaskQueue
{
    private $enableShutdown = true;
    private $queue = [];

    public function __construct($withShutdown = true)
    {
        if ($withShutdown) {
            register_shutdown_function(function () {
                if ($this->enableShutdown) {
                    // Only run the tasks if an E_ERROR didn't occur.
                    $err = error_get_last();
                    if (!$err || ($err['type'] ^ E_ERROR)) {
                        $this->run();
                    }
                }
            });
        }
    }

    /**
     * Returns true if the queue is empty.
     *
     * @return bool
     */
    public function isEmpty()
    {
        return !$this->queue;
    }

    /**
     * Adds a task to the queue that will be executed the next time run is
     * called.
     *
     * @param callable $task
     */
    public function add(callable $task)
    {
        $this->queue[] = $task;
    }

    /**
     * Execute all of the pending task in the queue.
     */
    public function run()
    {
        /** @var callable $task */
        while ($task = array_shift($this->queue)) {
            $task();
        }
    }

    /**
     * The task queue will be run and exhausted by default when the process
     * exits IFF the exit is not the result of a PHP E_ERROR error.
     *
     * You can disable running the automatic shutdown of the queue by calling
     * this function. If you disable the task queue shutdown process, then you
     * MUST either run the task queue (as a result of running your event loop
     * or manually using the run() method) or wait on each outstanding promise.
     *
     * Note: This shutdown will occur before any destructors are triggered.
     */
    public function disableShutdown()
    {
        $this->enableShutdown = false;
    }
}
<?php
namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise\AggregateException;

class AggregateExceptionTest extends \PHPUnit_Framework_TestCase
{
    public function testHasReason()
    {
        $e = new AggregateException('foo', ['baz', 'bar']);
        $this->assertContains('foo', $e->getMessage());
        $this->assertEquals(['baz', 'bar'], $e->getReason());
    }
}
<?php
require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/Thennable.php';
require __DIR__ . '/NotPromiseInstance.php';
<?php
namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\EachPromise;
use GuzzleHttp\Promise as P;

/**
 * @covers GuzzleHttp\Promise\EachPromise
 */
class EachPromiseTest extends \PHPUnit_Framework_TestCase
{
    public function testReturnsSameInstance()
    {
        $each = new EachPromise([], ['concurrency' => 100]);
        $this->assertSame($each->promise(), $each->promise());
    }

    public function testInvokesAllPromises()
    {
        $promises = [new Promise(), new Promise(), new Promise()];
        $called = [];
        $each = new EachPromise($promises, [
            'fulfilled' => function ($value) use (&$called) {
                $called[] = $value;
            }
        ]);
        $p = $each->promise();
        $promises[0]->resolve('a');
        $promises[1]->resolve('c');
        $promises[2]->resolve('b');
        P\queue()->run();
        $this->assertEquals(['a', 'c', 'b'], $called);
        $this->assertEquals(PromiseInterface::FULFILLED, $p->getState());
    }

    public function testIsWaitable()
    {
        $a = $this->createSelfResolvingPromise('a');
        $b = $this->createSelfResolvingPromise('b');
        $called = [];
        $each = new EachPromise([$a, $b], [
            'fulfilled' => function ($value) use (&$called) { $called[] = $value; }
        ]);
        $p = $each->promise();
        $this->assertNull($p->wait());
        $this->assertEquals(PromiseInterface::FULFILLED, $p->getState());
        $this->assertEquals(['a', 'b'], $called);
    }

    public function testCanResolveBeforeConsumingAll()
    {
        $called = 0;
        $a = $this->createSelfResolvingPromise('a');
        $b = new Promise(function () { $this->fail(); });
        $each = new EachPromise([$a, $b], [
            'fulfilled' => function ($value, $idx, Promise $aggregate) use (&$called) {
                $this->assertSame($idx, 0);
                $this->assertEquals('a', $value);
                $aggregate->resolve(null);
                $called++;
            },
            'rejected' => function (\Exception $reason) {
                $this->fail($reason->getMessage());
            }
        ]);
        $p = $each->promise();
        $p->wait();
        $this->assertNull($p->wait());
        $this->assertEquals(1, $called);
        $this->assertEquals(PromiseInterface::FULFILLED, $a->getState());
        $this->assertEquals(PromiseInterface::PENDING, $b->getState());
        // Resolving $b has no effect on the aggregate promise.
        $b->resolve('foo');
        $this->assertEquals(1, $called);
    }

    public function testLimitsPendingPromises()
    {
        $pending = [new Promise(), new Promise(), new Promise(), new Promise()];
        $promises = new \ArrayIterator($pending);
        $each = new EachPromise($promises, ['concurrency' => 2]);
        $p = $each->promise();
        $this->assertCount(2, $this->readAttribute($each, 'pending'));
        $pending[0]->resolve('a');
        $this->assertCount(2, $this->readAttribute($each, 'pending'));
        $this->assertTrue($promises->valid());
        $pending[1]->resolve('b');
        P\queue()->run();
        $this->assertCount(2, $this->readAttribute($each, 'pending'));
        $this->assertTrue($promises->valid());
        $promises[2]->resolve('c');
        P\queue()->run();
        $this->assertCount(1, $this->readAttribute($each, 'pending'));
        $this->assertEquals(PromiseInterface::PENDING, $p->getState());
        $promises[3]->resolve('d');
        P\queue()->run();
        $this->assertNull($this->readAttribute($each, 'pending'));
        $this->assertEquals(PromiseInterface::FULFILLED, $p->getState());
        $this->assertFalse($promises->valid());
    }

    public function testDynamicallyLimitsPendingPromises()
    {
        $calls = [];
        $pendingFn = function ($count) use (&$calls) {
            $calls[] = $count;
            return 2;
        };
        $pending = [new Promise(), new Promise(), new Promise(), new Promise()];
        $promises = new \ArrayIterator($pending);
        $each = new EachPromise($promises, ['concurrency' => $pendingFn]);
        $p = $each->promise();
        $this->assertCount(2, $this->readAttribute($each, 'pending'));
        $pending[0]->resolve('a');
        $this->assertCount(2, $this->readAttribute($each, 'pending'));
        $this->assertTrue($promises->valid());
        $pending[1]->resolve('b');
        $this->assertCount(2, $this->readAttribute($each, 'pending'));
        P\queue()->run();
        $this->assertTrue($promises->valid());
        $promises[2]->resolve('c');
        P\queue()->run();
        $this->assertCount(1, $this->readAttribute($each, 'pending'));
        $this->assertEquals(PromiseInterface::PENDING, $p->getState());
        $promises[3]->resolve('d');
        P\queue()->run();
        $this->assertNull($this->readAttribute($each, 'pending'));
        $this->assertEquals(PromiseInterface::FULFILLED, $p->getState());
        $this->assertEquals([0, 1, 1, 1], $calls);
        $this->assertFalse($promises->valid());
    }

    public function testClearsReferencesWhenResolved()
    {
        $called = false;
        $a = new Promise(function () use (&$a, &$called) {
            $a->resolve('a');
            $called = true;
        });
        $each = new EachPromise([$a], [
            'concurrency'       => function () { return 1; },
            'fulfilled' => function () {},
            'rejected'  => function () {}
        ]);
        $each->promise()->wait();
        $this->assertNull($this->readAttribute($each, 'onFulfilled'));
        $this->assertNull($this->readAttribute($each, 'onRejected'));
        $this->assertNull($this->readAttribute($each, 'iterable'));
        $this->assertNull($this->readAttribute($each, 'pending'));
        $this->assertNull($this->readAttribute($each, 'concurrency'));
        $this->assertTrue($called);
    }

    public function testCanBeCancelled()
    {
        $this->markTestIncomplete();
    }

    public function testFulfillsImmediatelyWhenGivenAnEmptyIterator()
    {
        $each = new EachPromise(new \ArrayIterator([]));
        $result = $each->promise()->wait();
    }

    public function testDoesNotBlowStackWithFulfilledPromises()
    {
        $pending = [];
        for ($i = 0; $i < 100; $i++) {
            $pending[] = new FulfilledPromise($i);
        }
        $values = [];
        $each = new EachPromise($pending, [
            'fulfilled' => function ($value) use (&$values) {
                $values[] = $value;
            }
        ]);
        $called = false;
        $each->promise()->then(function () use (&$called) {
            $called = true;
        });
        $this->assertFalse($called);
        P\queue()->run();
        $this->assertTrue($called);
        $this->assertEquals(range(0, 99), $values);
    }

    public function testDoesNotBlowStackWithRejectedPromises()
    {
        $pending = [];
        for ($i = 0; $i < 100; $i++) {
            $pending[] = new RejectedPromise($i);
        }
        $values = [];
        $each = new EachPromise($pending, [
            'rejected' => function ($value) use (&$values) {
                $values[] = $value;
            }
        ]);
        $called = false;
        $each->promise()->then(
            function () use (&$called) { $called = true; },
            function () { $this->fail('Should not have rejected.'); }
        );
        $this->assertFalse($called);
        P\queue()->run();
        $this->assertTrue($called);
        $this->assertEquals(range(0, 99), $values);
    }

    public function testReturnsPromiseForWhatever()
    {
        $called = [];
        $arr = ['a', 'b'];
        $each = new EachPromise($arr, [
            'fulfilled' => function ($v) use (&$called) { $called[] = $v; }
        ]);
        $p = $each->promise();
        $this->assertNull($p->wait());
        $this->assertEquals(['a', 'b'], $called);
    }

    public function testRejectsAggregateWhenNextThrows()
    {
        $iter = function () {
            yield 'a';
            throw new \Exception('Failure');
        };
        $each = new EachPromise($iter());
        $p = $each->promise();
        $e = null;
        $received = null;
        $p->then(null, function ($reason) use (&$e) { $e = $reason; });
        P\queue()->run();
        $this->assertInstanceOf('Exception', $e);
        $this->assertEquals('Failure', $e->getMessage());
    }

    public function testDoesNotCallNextOnIteratorUntilNeededWhenWaiting()
    {
        $results = [];
        $values = [10];
        $remaining = 9;
        $iter = function () use (&$values) {
            while ($value = array_pop($values)) {
                yield $value;
            }
        };
        $each = new EachPromise($iter(), [
            'concurrency' => 1,
            'fulfilled' => function ($r) use (&$results, &$values, &$remaining) {
                $results[] = $r;
                if ($remaining > 0) {
                    $values[] = $remaining--;
                }
            }
        ]);
        $each->promise()->wait();
        $this->assertEquals(range(10, 1), $results);
    }

    public function testDoesNotCallNextOnIteratorUntilNeededWhenAsync()
    {
        $firstPromise = new Promise();
        $pending = [$firstPromise];
        $values = [$firstPromise];
        $results = [];
        $remaining = 9;
        $iter = function () use (&$values) {
            while ($value = array_pop($values)) {
                yield $value;
            }
        };
        $each = new EachPromise($iter(), [
            'concurrency' => 1,
            'fulfilled' => function ($r) use (&$results, &$values, &$remaining, &$pending) {
                $results[] = $r;
                if ($remaining-- > 0) {
                    $pending[] = $values[] = new Promise();
                }
            }
        ]);
        $i = 0;
        $each->promise();
        while ($promise = array_pop($pending)) {
            $promise->resolve($i++);
            P\queue()->run();
        }
        $this->assertEquals(range(0, 9), $results);
    }

    private function createSelfResolvingPromise($value)
    {
        $p = new Promise(function () use (&$p, $value) {
            $p->resolve($value);
        });

        return $p;
    }

    public function testMutexPreventsGeneratorRecursion()
    {
        $results = $promises = [];
        for ($i = 0; $i < 20; $i++) {
            $p = $this->createSelfResolvingPromise($i);
            $pending[] = $p;
            $promises[] = $p;
        }

        $iter = function () use (&$promises, &$pending) {
            foreach ($promises as $promise) {
                // Resolve a promises, which will trigger the then() function,
                // which would cause the EachPromise to try to add more
                // promises to the queue. Without a lock, this would trigger
                // a "Cannot resume an already running generator" fatal error.
                if ($p = array_pop($pending)) {
                    $p->wait();
                }
                yield $promise;
            }
        };

        $each = new EachPromise($iter(), [
            'concurrency' => 5,
            'fulfilled' => function ($r) use (&$results, &$pending) {
                $results[] = $r;
            }
        ]);

        $each->promise()->wait();
        $this->assertCount(20, $results);
    }
}
<?php
namespace GuzzleHttp\Tests\Promise;

use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\FulfilledPromise;

/**
 * @covers GuzzleHttp\Promise\FulfilledPromise
 */
class FulfilledPromiseTest extends \PHPUnit_Framework_TestCase
{
    public function testReturnsValueWhenWaitedUpon()
    {
        $p = new FulfilledPromise('foo');
        $this->assertEquals('fulfilled', $p->getState());
        $this->assertEquals('foo', $p->wait(true));
    }

    public function testCannotCancel()
    {
        $p = new FulfilledPromise('foo');
        $this->assertEquals('fulfilled', $p->getState());
        $p->cancel();
        $this->assertEquals('foo', $p->wait());
    }

    /**
     * @expectedException \LogicException
     * @exepctedExceptionMessage Cannot resolve a fulfilled promise
     */
    public function testCannotResolve()
    {
        $p = new FulfilledPromise('foo');
        $p->resolve('bar');
    }

    /**
     * @expectedException \LogicException
     * @exepctedExceptionMessage Cannot reject a fulfilled promise
     */
    public function testCannotReject()
    {
        $p = new FulfilledPromise('foo');
        $p->reject('bar');
    }

    public function testCanResolveWithSameValue()
    {
        $p = new FulfilledPromise('foo');
        $p->resolve('foo');
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testCannotResolveWithPromise()
    {
        new FulfilledPromise(new Promise());
    }

    public function testReturnsSelfWhenNoOnFulfilled()
    {
        $p = new FulfilledPromise('a');
        $this->assertSame($p, $p->then());
    }

    public function testAsynchronouslyInvokesOnFulfilled()
    {
        $p = new FulfilledPromise('a');
        $r = null;
        $f = function ($d) use (&$r) { $r = $d; };
        $p2 = $p->then($f);
        $this->assertNotSame($p, $p2);
        $this->assertNull($r);
        \GuzzleHttp\Promise\queue()->run();
        $this->assertEquals('a', $r);
    }

    public function testReturnsNewRejectedWhenOnFulfilledFails()
    {
        $p = new FulfilledPromise('a');
        $f = function () { throw new \Exception('b'); };
        $p2 = $p->then($f);
        $this->assertNotSame($p, $p2);
        try {
            $p2->wait();
            $this->fail();
        } catch (\Exception $e) {
            $this->assertEquals('b', $e->getMessage());
        }
    }

    public function testOtherwiseIsSugarForRejections()
    {
        $c = null;
        $p = new FulfilledPromise('foo');
        $p->otherwise(function ($v) use (&$c) { $c = $v; });
        $this->assertNull($c);
    }

    public function testDoesNotTryToFulfillTwiceDuringTrampoline()
    {
        $fp = new FulfilledPromise('a');
        $t1 = $fp->then(function ($v) { return $v . ' b'; });
        $t1->resolve('why!');
        $this->assertEquals('why!', $t1->wait());
    }
}
<?php
namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;

class FunctionsTest extends \PHPUnit_Framework_TestCase
{
    public function testCreatesPromiseForValue()
    {
        $p = \GuzzleHttp\Promise\promise_for('foo');
        $this->assertInstanceOf('GuzzleHttp\Promise\FulfilledPromise', $p);
    }

    public function testReturnsPromiseForPromise()
    {
        $p = new Promise();
        $this->assertSame($p, \GuzzleHttp\Promise\promise_for($p));
    }

    public function testReturnsPromiseForThennable()
    {
        $p = new Thennable();
        $wrapped = \GuzzleHttp\Promise\promise_for($p);
        $this->assertNotSame($p, $wrapped);
        $this->assertInstanceOf('GuzzleHttp\Promise\PromiseInterface', $wrapped);
        $p->resolve('foo');
        P\queue()->run();
        $this->assertEquals('foo', $wrapped->wait());
    }

    public function testReturnsRejection()
    {
        $p = \GuzzleHttp\Promise\rejection_for('fail');
        $this->assertInstanceOf('GuzzleHttp\Promise\RejectedPromise', $p);
        $this->assertEquals('fail', $this->readAttribute($p, 'reason'));
    }

    public function testReturnsPromisesAsIsInRejectionFor()
    {
        $a = new Promise();
        $b = \GuzzleHttp\Promise\rejection_for($a);
        $this->assertSame($a, $b);
    }

    public function testWaitsOnAllPromisesIntoArray()
    {
        $e = new \Exception();
        $a = new Promise(function () use (&$a) { $a->resolve('a'); });
        $b = new Promise(function () use (&$b) { $b->reject('b'); });
        $c = new Promise(function () use (&$c, $e) { $c->reject($e); });
        $results = \GuzzleHttp\Promise\inspect_all([$a, $b, $c]);
        $this->assertEquals([
            ['state' => 'fulfilled', 'value' => 'a'],
            ['state' => 'rejected', 'reason' => 'b'],
            ['state' => 'rejected', 'reason' => $e]
        ], $results);
    }

    /**
     * @expectedException \GuzzleHttp\Promise\RejectionException
     */
    public function testUnwrapsPromisesWithNoDefaultAndFailure()
    {
        $promises = [new FulfilledPromise('a'), new Promise()];
        \GuzzleHttp\Promise\unwrap($promises);
    }

    public function testUnwrapsPromisesWithNoDefault()
    {
        $promises = [new FulfilledPromise('a')];
        $this->assertEquals(['a'], \GuzzleHttp\Promise\unwrap($promises));
    }

    public function testUnwrapsPromisesWithKeys()
    {
        $promises = [
            'foo' => new FulfilledPromise('a'),
            'bar' => new FulfilledPromise('b'),
        ];
        $this->assertEquals([
            'foo' => 'a',
            'bar' => 'b'
        ], \GuzzleHttp\Promise\unwrap($promises));
    }

    public function testAllAggregatesSortedArray()
    {
        $a = new Promise();
        $b = new Promise();
        $c = new Promise();
        $d = \GuzzleHttp\Promise\all([$a, $b, $c]);
        $b->resolve('b');
        $a->resolve('a');
        $c->resolve('c');
        $d->then(
            function ($value) use (&$result) { $result = $value; },
            function ($reason) use (&$result) { $result = $reason; }
        );
        P\queue()->run();
        $this->assertEquals(['a', 'b', 'c'], $result);
    }

    public function testAllThrowsWhenAnyRejected()
    {
        $a = new Promise();
        $b = new Promise();
        $c = new Promise();
        $d = \GuzzleHttp\Promise\all([$a, $b, $c]);
        $b->resolve('b');
        $a->reject('fail');
        $c->resolve('c');
        $d->then(
            function ($value) use (&$result) { $result = $value; },
            function ($reason) use (&$result) { $result = $reason; }
        );
        P\queue()->run();
        $this->assertEquals('fail', $result);
    }

    public function testSomeAggregatesSortedArrayWithMax()
    {
        $a = new Promise();
        $b = new Promise();
        $c = new Promise();
        $d = \GuzzleHttp\Promise\some(2, [$a, $b, $c]);
        $b->resolve('b');
        $c->resolve('c');
        $a->resolve('a');
        $d->then(function ($value) use (&$result) { $result = $value; });
        P\queue()->run();
        $this->assertEquals(['b', 'c'], $result);
    }

    public function testSomeRejectsWhenTooManyRejections()
    {
        $a = new Promise();
        $b = new Promise();
        $d = \GuzzleHttp\Promise\some(2, [$a, $b]);
        $a->reject('bad');
        $b->resolve('good');
        P\queue()->run();
        $this->assertEquals($a::REJECTED, $d->getState());
        $d->then(null, function ($reason) use (&$called) {
            $called = $reason;
        });
        P\queue()->run();
        $this->assertInstanceOf('GuzzleHttp\Promise\AggregateException', $called);
        $this->assertContains('bad', $called->getReason());
    }

    public function testCanWaitUntilSomeCountIsSatisfied()
    {
        $a = new Promise(function () use (&$a) { $a->resolve('a'); });
        $b = new Promise(function () use (&$b) { $b->resolve('b'); });
        $c = new Promise(function () use (&$c) { $c->resolve('c'); });
        $d = \GuzzleHttp\Promise\some(2, [$a, $b, $c]);
        $this->assertEquals(['a', 'b'], $d->wait());
    }

    /**
     * @expectedException \GuzzleHttp\Promise\AggregateException
     * @expectedExceptionMessage Not enough promises to fulfill count
     */
    public function testThrowsIfImpossibleToWaitForSomeCount()
    {
        $a = new Promise(function () use (&$a) { $a->resolve('a'); });
        $d = \GuzzleHttp\Promise\some(2, [$a]);
        $d->wait();
    }

    /**
     * @expectedException \GuzzleHttp\Promise\AggregateException
     * @expectedExceptionMessage Not enough promises to fulfill count
     */
    public function testThrowsIfResolvedWithoutCountTotalResults()
    {
        $a = new Promise();
        $b = new Promise();
        $d = \GuzzleHttp\Promise\some(3, [$a, $b]);
        $a->resolve('a');
        $b->resolve('b');
        $d->wait();
    }

    public function testAnyReturnsFirstMatch()
    {
        $a = new Promise();
        $b = new Promise();
        $c = \GuzzleHttp\Promise\any([$a, $b]);
        $b->resolve('b');
        $a->resolve('a');
        //P\queue()->run();
        //$this->assertEquals('fulfilled', $c->getState());
        $c->then(function ($value) use (&$result) { $result = $value; });
        P\queue()->run();
        $this->assertEquals('b', $result);
    }

    public function testSettleFulfillsWithFulfilledAndRejected()
    {
        $a = new Promise();
        $b = new Promise();
        $c = new Promise();
        $d = \GuzzleHttp\Promise\settle([$a, $b, $c]);
        $b->resolve('b');
        $c->resolve('c');
        $a->reject('a');
        P\queue()->run();
        $this->assertEquals('fulfilled', $d->getState());
        $d->then(function ($value) use (&$result) { $result = $value; });
        P\queue()->run();
        $this->assertEquals([
            ['state' => 'rejected', 'reason' => 'a'],
            ['state' => 'fulfilled', 'value' => 'b'],
            ['state' => 'fulfilled', 'value' => 'c']
        ], $result);
    }

    public function testCanInspectFulfilledPromise()
    {
        $p = new FulfilledPromise('foo');
        $this->assertEquals([
            'state' => 'fulfilled',
            'value' => 'foo'
        ], \GuzzleHttp\Promise\inspect($p));
    }

    public function testCanInspectRejectedPromise()
    {
        $p = new RejectedPromise('foo');
        $this->assertEquals([
            'state'  => 'rejected',
            'reason' => 'foo'
        ], \GuzzleHttp\Promise\inspect($p));
    }

    public function testCanInspectRejectedPromiseWithNormalException()
    {
        $e = new \Exception('foo');
        $p = new RejectedPromise($e);
        $this->assertEquals([
            'state'  => 'rejected',
            'reason' => $e
        ], \GuzzleHttp\Promise\inspect($p));
    }

    public function testCallsEachLimit()
    {
        $p = new Promise();
        $aggregate = \GuzzleHttp\Promise\each_limit($p, 2);
        $p->resolve('a');
        P\queue()->run();
        $this->assertEquals($p::FULFILLED, $aggregate->getState());
    }

    public function testEachLimitAllRejectsOnFailure()
    {
        $p = [new FulfilledPromise('a'), new RejectedPromise('b')];
        $aggregate = \GuzzleHttp\Promise\each_limit_all($p, 2);
        P\queue()->run();
        $this->assertEquals(P\PromiseInterface::REJECTED, $aggregate->getState());
        $result = \GuzzleHttp\Promise\inspect($aggregate);
        $this->assertEquals('b', $result['reason']);
    }

    public function testIterForReturnsIterator()
    {
        $iter = new \ArrayIterator();
        $this->assertSame($iter, \GuzzleHttp\Promise\iter_for($iter));
    }

    public function testKnowsIfFulfilled()
    {
        $p = new FulfilledPromise(null);
        $this->assertTrue(P\is_fulfilled($p));
        $this->assertFalse(P\is_rejected($p));
    }

    public function testKnowsIfRejected()
    {
        $p = new RejectedPromise(null);
        $this->assertTrue(P\is_rejected($p));
        $this->assertFalse(P\is_fulfilled($p));
    }

    public function testKnowsIfSettled()
    {
        $p = new RejectedPromise(null);
        $this->assertTrue(P\is_settled($p));
        $p = new Promise();
        $this->assertFalse(P\is_settled($p));
    }

    public function testReturnsTrampoline()
    {
        $this->assertInstanceOf('GuzzleHttp\Promise\TaskQueue', P\queue());
        $this->assertSame(P\queue(), P\queue());
    }

    public function testCanScheduleThunk()
    {
        $tramp = P\queue();
        $promise = P\task(function () { return 'Hi!'; });
        $c = null;
        $promise->then(function ($v) use (&$c) { $c = $v; });
        $this->assertNull($c);
        $tramp->run();
        $this->assertEquals('Hi!', $c);
    }

    public function testCanScheduleThunkWithRejection()
    {
        $tramp = P\queue();
        $promise = P\task(function () { throw new \Exception('Hi!'); });
        $c = null;
        $promise->otherwise(function ($v) use (&$c) { $c = $v; });
        $this->assertNull($c);
        $tramp->run();
        $this->assertEquals('Hi!', $c->getMessage());
    }

    public function testCanScheduleThunkWithWait()
    {
        $tramp = P\queue();
        $promise = P\task(function () { return 'a'; });
        $this->assertEquals('a', $promise->wait());
        $tramp->run();
    }

    public function testYieldsFromCoroutine()
    {
        $promise = P\coroutine(function () {
            $value = (yield new P\FulfilledPromise('a'));
            yield  $value . 'b';
        });
        $promise->then(function ($value) use (&$result) { $result = $value; });
        P\queue()->run();
        $this->assertEquals('ab', $result);
    }

    public function testCanCatchExceptionsInCoroutine()
    {
        $promise = P\coroutine(function () {
            try {
                yield new P\RejectedPromise('a');
                $this->fail('Should have thrown into the coroutine!');
            } catch (P\RejectionException $e) {
                $value = (yield new P\FulfilledPromise($e->getReason()));
                yield  $value . 'b';
            }
        });
        $promise->then(function ($value) use (&$result) { $result = $value; });
        P\queue()->run();
        $this->assertEquals(P\PromiseInterface::FULFILLED, $promise->getState());
        $this->assertEquals('ab', $result);
    }

    public function testRejectsParentExceptionWhenException()
    {
        $promise = P\coroutine(function () {
            yield new P\FulfilledPromise(0);
            throw new \Exception('a');
        });
        $promise->then(
            function () { $this->fail(); },
            function ($reason) use (&$result) { $result = $reason; }
        );
        P\queue()->run();
        $this->assertInstanceOf('Exception', $result);
        $this->assertEquals('a', $result->getMessage());
    }

    public function testCanRejectFromRejectionCallback()
    {
        $promise = P\coroutine(function () {
            yield new P\FulfilledPromise(0);
            yield new P\RejectedPromise('no!');
        });
        $promise->then(
            function () { $this->fail(); },
            function ($reason) use (&$result) { $result = $reason; }
        );
        P\queue()->run();
        $this->assertInstanceOf('GuzzleHttp\Promise\RejectionException', $result);
        $this->assertEquals('no!', $result->getReason());
    }

    public function testCanAsyncReject()
    {
        $rej = new P\Promise();
        $promise = P\coroutine(function () use ($rej) {
            yield new P\FulfilledPromise(0);
            yield $rej;
        });
        $promise->then(
            function () { $this->fail(); },
            function ($reason) use (&$result) { $result = $reason; }
        );
        $rej->reject('no!');
        P\queue()->run();
        $this->assertInstanceOf('GuzzleHttp\Promise\RejectionException', $result);
        $this->assertEquals('no!', $result->getReason());
    }

    public function testCanCatchAndThrowOtherException()
    {
        $promise = P\coroutine(function () {
            try {
                yield new P\RejectedPromise('a');
                $this->fail('Should have thrown into the coroutine!');
            } catch (P\RejectionException $e) {
                throw new \Exception('foo');
            }
        });
        $promise->otherwise(function ($value) use (&$result) { $result = $value; });
        P\queue()->run();
        $this->assertEquals(P\PromiseInterface::REJECTED, $promise->getState());
        $this->assertContains('foo', $result->getMessage());
    }

    public function testCanCatchAndYieldOtherException()
    {
        $promise = P\coroutine(function () {
            try {
                yield new P\RejectedPromise('a');
                $this->fail('Should have thrown into the coroutine!');
            } catch (P\RejectionException $e) {
                yield new P\RejectedPromise('foo');
            }
        });
        $promise->otherwise(function ($value) use (&$result) { $result = $value; });
        P\queue()->run();
        $this->assertEquals(P\PromiseInterface::REJECTED, $promise->getState());
        $this->assertContains('foo', $result->getMessage());
    }

    public function createLotsOfSynchronousPromise()
    {
        return P\coroutine(function () {
            $value = 0;
            for ($i = 0; $i < 1000; $i++) {
                $value = (yield new P\FulfilledPromise($i));
            }
            yield $value;
        });
    }

    public function testLotsOfSynchronousDoesNotBlowStack()
    {
        $promise = $this->createLotsOfSynchronousPromise();
        $promise->then(function ($v) use (&$r) { $r = $v; });
        P\queue()->run();
        $this->assertEquals(999, $r);
    }

    public function testLotsOfSynchronousWaitDoesNotBlowStack()
    {
        $promise = $this->createLotsOfSynchronousPromise();
        $promise->then(function ($v) use (&$r) { $r = $v; });
        $this->assertEquals(999, $promise->wait());
        $this->assertEquals(999, $r);
    }

    private function createLotsOfFlappingPromise()
    {
        return P\coroutine(function () {
            $value = 0;
            for ($i = 0; $i < 1000; $i++) {
                try {
                    if ($i % 2) {
                        $value = (yield new P\FulfilledPromise($i));
                    } else {
                        $value = (yield new P\RejectedPromise($i));
                    }
                } catch (\Exception $e) {
                    $value = (yield new P\FulfilledPromise($i));
                }
            }
            yield $value;
        });
    }

    public function testLotsOfTryCatchingDoesNotBlowStack()
    {
        $promise = $this->createLotsOfFlappingPromise();
        $promise->then(function ($v) use (&$r) { $r = $v; });
        P\queue()->run();
        $this->assertEquals(999, $r);
    }

    public function testLotsOfTryCatchingWaitingDoesNotBlowStack()
    {
        $promise = $this->createLotsOfFlappingPromise();
        $promise->then(function ($v) use (&$r) { $r = $v; });
        $this->assertEquals(999, $promise->wait());
        $this->assertEquals(999, $r);
    }

    public function testAsyncPromisesWithCorrectlyYieldedValues()
    {
        $promises = [
            new P\Promise(),
            new P\Promise(),
            new P\Promise()
        ];

        $promise = P\coroutine(function () use ($promises) {
            $value = null;
            $this->assertEquals('skip', (yield new P\FulfilledPromise('skip')));
            foreach ($promises as $idx => $p) {
                $value = (yield $p);
                $this->assertEquals($value, $idx);
                $this->assertEquals('skip', (yield new P\FulfilledPromise('skip')));
            }
            $this->assertEquals('skip', (yield new P\FulfilledPromise('skip')));
            yield $value;
        });

        $promises[0]->resolve(0);
        $promises[1]->resolve(1);
        $promises[2]->resolve(2);

        $promise->then(function ($v) use (&$r) { $r = $v; });
        P\queue()->run();
        $this->assertEquals(2, $r);
    }

    public function testYieldFinalWaitablePromise()
    {
        $p1 = new P\Promise(function () use (&$p1) {
            $p1->resolve('skip me');
        });
        $p2 = new P\Promise(function () use (&$p2) {
            $p2->resolve('hello!');
        });
        $co = P\coroutine(function() use ($p1, $p2) {
            yield $p1;
            yield $p2;
        });
        P\queue()->run();
        $this->assertEquals('hello!', $co->wait());
    }

    public function testCanYieldFinalPendingPromise()
    {
        $p1 = new P\Promise();
        $p2 = new P\Promise();
        $co = P\coroutine(function() use ($p1, $p2) {
            yield $p1;
            yield $p2;
        });
        $p1->resolve('a');
        $p2->resolve('b');
        $co->then(function ($value) use (&$result) { $result = $value; });
        P\queue()->run();
        $this->assertEquals('b', $result);
    }

    public function testCanNestYieldsAndFailures()
    {
        $p1 = new P\Promise();
        $p2 = new P\Promise();
        $p3 = new P\Promise();
        $p4 = new P\Promise();
        $p5 = new P\Promise();
        $co = P\coroutine(function() use ($p1, $p2, $p3, $p4, $p5) {
            try {
                yield $p1;
            } catch (\Exception $e) {
                yield $p2;
                try {
                    yield $p3;
                    yield $p4;
                } catch (\Exception $e) {
                    yield $p5;
                }
            }
        });
        $p1->reject('a');
        $p2->resolve('b');
        $p3->resolve('c');
        $p4->reject('d');
        $p5->resolve('e');
        $co->then(function ($value) use (&$result) { $result = $value; });
        P\queue()->run();
        $this->assertEquals('e', $result);
    }

    public function testCanYieldErrorsAndSuccessesWithoutRecursion()
    {
        $promises = [];
        for ($i = 0; $i < 20; $i++) {
            $promises[] = new P\Promise();
        }

        $co = P\coroutine(function() use ($promises) {
            for ($i = 0; $i < 20; $i += 4) {
                try {
                    yield $promises[$i];
                    yield $promises[$i + 1];
                } catch (\Exception $e) {
                    yield $promises[$i + 2];
                    yield $promises[$i + 3];
                }
            }
        });

        for ($i = 0; $i < 20; $i += 4) {
            $promises[$i]->resolve($i);
            $promises[$i + 1]->reject($i + 1);
            $promises[$i + 2]->resolve($i + 2);
            $promises[$i + 3]->resolve($i + 3);
        }

        $co->then(function ($value) use (&$result) { $result = $value; });
        P\queue()->run();
        $this->assertEquals('19', $result);
    }

    public function testCanWaitOnPromiseAfterFulfilled()
    {
        $f = function () {
            static $i = 0;
            $i++;
            return $p = new P\Promise(function () use (&$p, $i) {
                $p->resolve($i . '-bar');
            });
        };

        $promises = [];
        for ($i = 0; $i < 20; $i++) {
            $promises[] = $f();
        }

        $p = P\coroutine(function () use ($promises) {
            yield new P\FulfilledPromise('foo!');
            foreach ($promises as $promise) {
                yield $promise;
            }
        });

        $this->assertEquals('20-bar', $p->wait());
    }

    public function testCanWaitOnErroredPromises()
    {
        $p1 = new P\Promise(function () use (&$p1) { $p1->reject('a'); });
        $p2 = new P\Promise(function () use (&$p2) { $p2->resolve('b'); });
        $p3 = new P\Promise(function () use (&$p3) { $p3->resolve('c'); });
        $p4 = new P\Promise(function () use (&$p4) { $p4->reject('d'); });
        $p5 = new P\Promise(function () use (&$p5) { $p5->resolve('e'); });
        $p6 = new P\Promise(function () use (&$p6) { $p6->reject('f'); });

        $co = P\coroutine(function() use ($p1, $p2, $p3, $p4, $p5, $p6) {
            try {
                yield $p1;
            } catch (\Exception $e) {
                yield $p2;
                try {
                    yield $p3;
                    yield $p4;
                } catch (\Exception $e) {
                    yield $p5;
                    yield $p6;
                }
            }
        });

        $res = P\inspect($co);
        $this->assertEquals('f', $res['reason']);
    }

    public function testCoroutineOtherwiseIntegrationTest()
    {
        $a = new P\Promise();
        $b = new P\Promise();
        $promise = P\coroutine(function () use ($a, $b) {
            // Execute the pool of commands concurrently, and process errors.
            yield $a;
            yield $b;
        })->otherwise(function (\Exception $e) {
            // Throw errors from the operations as a specific Multipart error.
            throw new \OutOfBoundsException('a', 0, $e);
        });
        $a->resolve('a');
        $b->reject('b');
        $reason = P\inspect($promise)['reason'];
        $this->assertInstanceOf('OutOfBoundsException', $reason);
        $this->assertInstanceOf('GuzzleHttp\Promise\RejectionException', $reason->getPrevious());
    }
}
<?php
namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\PromiseInterface;

class NotPromiseInstance extends Thennable implements PromiseInterface
{
    private $nextPromise = null;

    public function __construct()
    {
        $this->nextPromise = new Promise();
    }

    public function then(callable $res = null, callable $rej = null)
    {
        return $this->nextPromise->then($res, $rej);
    }

    public function otherwise(callable $onRejected)
    {
        return $this->then($onRejected);
    }

    public function resolve($value)
    {
        $this->nextPromise->resolve($value);
    }

    public function reject($reason)
    {
        $this->nextPromise->reject($reason);
    }

    public function wait($unwrap = true, $defaultResolution = null)
    {

    }

    public function cancel()
    {

    }

    public function getState()
    {
        return $this->nextPromise->getState();
    }
}
<?php
namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise\CancellationException;
use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Promise\RejectionException;

/**
 * @covers GuzzleHttp\Promise\Promise
 */
class PromiseTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @expectedException \LogicException
     * @expectedExceptionMessage The promise is already fulfilled
     */
    public function testCannotResolveNonPendingPromise()
    {
        $p = new Promise();
        $p->resolve('foo');
        $p->resolve('bar');
        $this->assertEquals('foo', $p->wait());
    }

    public function testCanResolveWithSameValue()
    {
        $p = new Promise();
        $p->resolve('foo');
        $p->resolve('foo');
    }

    /**
     * @expectedException \LogicException
     * @expectedExceptionMessage Cannot change a fulfilled promise to rejected
     */
    public function testCannotRejectNonPendingPromise()
    {
        $p = new Promise();
        $p->resolve('foo');
        $p->reject('bar');
        $this->assertEquals('foo', $p->wait());
    }

    public function testCanRejectWithSameValue()
    {
        $p = new Promise();
        $p->reject('foo');
        $p->reject('foo');
    }

    /**
     * @expectedException \LogicException
     * @expectedExceptionMessage Cannot change a fulfilled promise to rejected
     */
    public function testCannotRejectResolveWithSameValue()
    {
        $p = new Promise();
        $p->resolve('foo');
        $p->reject('foo');
    }

    public function testInvokesWaitFunction()
    {
        $p = new Promise(function () use (&$p) { $p->resolve('10'); });
        $this->assertEquals('10', $p->wait());
    }

    /**
     * @expectedException \GuzzleHttp\Promise\RejectionException
     */
    public function testRejectsAndThrowsWhenWaitFailsToResolve()
    {
        $p = new Promise(function () {});
        $p->wait();
    }

    /**
     * @expectedException \GuzzleHttp\Promise\RejectionException
     * @expectedExceptionMessage The promise was rejected with reason: foo
     */
    public function testThrowsWhenUnwrapIsRejectedWithNonException()
    {
        $p = new Promise(function () use (&$p) { $p->reject('foo'); });
        $p->wait();
    }

    /**
     * @expectedException \UnexpectedValueException
     * @expectedExceptionMessage foo
     */
    public function testThrowsWhenUnwrapIsRejectedWithException()
    {
        $e = new \UnexpectedValueException('foo');
        $p = new Promise(function () use (&$p, $e) { $p->reject($e); });
        $p->wait();
    }

    public function testDoesNotUnwrapExceptionsWhenDisabled()
    {
        $p = new Promise(function () use (&$p) { $p->reject('foo'); });
        $this->assertEquals('pending', $p->getState());
        $p->wait(false);
        $this->assertEquals('rejected', $p->getState());
    }

    public function testRejectsSelfWhenWaitThrows()
    {
        $e = new \UnexpectedValueException('foo');
        $p = new Promise(function () use ($e) { throw $e; });
        try {
            $p->wait();
            $this->fail();
        } catch (\UnexpectedValueException $e) {
            $this->assertEquals('rejected', $p->getState());
        }
    }

    public function testWaitsOnNestedPromises()
    {
        $p = new Promise(function () use (&$p) { $p->resolve('_'); });
        $p2 = new Promise(function () use (&$p2) { $p2->resolve('foo'); });
        $p3 = $p->then(function () use ($p2) { return $p2; });
        $this->assertSame('foo', $p3->wait());
    }

    /**
     * @expectedException \GuzzleHttp\Promise\RejectionException
     */
    public function testThrowsWhenWaitingOnPromiseWithNoWaitFunction()
    {
        $p = new Promise();
        $p->wait();
    }

    public function testThrowsWaitExceptionAfterPromiseIsResolved()
    {
        $p = new Promise(function () use (&$p) {
            $p->reject('Foo!');
            throw new \Exception('Bar?');
        });

        try {
            $p->wait();
            $this->fail();
        } catch (\Exception $e) {
            $this->assertEquals('Bar?', $e->getMessage());
        }
    }

    public function testGetsActualWaitValueFromThen()
    {
        $p = new Promise(function () use (&$p) { $p->reject('Foo!'); });
        $p2 = $p->then(null, function ($reason) {
            return new RejectedPromise([$reason]);
        });

        try {
            $p2->wait();
            $this->fail('Should have thrown');
        } catch (RejectionException $e) {
            $this->assertEquals(['Foo!'], $e->getReason());
        }
    }

    public function testWaitBehaviorIsBasedOnLastPromiseInChain()
    {
        $p3 = new Promise(function () use (&$p3) { $p3->resolve('Whoop'); });
        $p2 = new Promise(function () use (&$p2, $p3) { $p2->reject($p3); });
        $p = new Promise(function () use (&$p, $p2) { $p->reject($p2); });
        $this->assertEquals('Whoop', $p->wait());
    }

    public function testWaitsOnAPromiseChainEvenWhenNotUnwrapped()
    {
        $p2 = new Promise(function () use (&$p2) {
            $p2->reject('Fail');
        });
        $p = new Promise(function () use ($p2, &$p) {
            $p->resolve($p2);
        });
        $p->wait(false);
        $this->assertSame(Promise::REJECTED, $p2->getState());
    }

    public function testCannotCancelNonPending()
    {
        $p = new Promise();
        $p->resolve('foo');
        $p->cancel();
        $this->assertEquals('fulfilled', $p->getState());
    }

    /**
     * @expectedException \GuzzleHttp\Promise\CancellationException
     */
    public function testCancelsPromiseWhenNoCancelFunction()
    {
        $p = new Promise();
        $p->cancel();
        $this->assertEquals('rejected', $p->getState());
        $p->wait();
    }

    public function testCancelsPromiseWithCancelFunction()
    {
        $called = false;
        $p = new Promise(null, function () use (&$called) { $called = true; });
        $p->cancel();
        $this->assertEquals('rejected', $p->getState());
        $this->assertTrue($called);
    }

    public function testCancelsUppermostPendingPromise()
    {
        $called = false;
        $p1 = new Promise(null, function () use (&$called) { $called = true; });
        $p2 = $p1->then(function () {});
        $p3 = $p2->then(function () {});
        $p4 = $p3->then(function () {});
        $p3->cancel();
        $this->assertEquals('rejected', $p1->getState());
        $this->assertEquals('rejected', $p2->getState());
        $this->assertEquals('rejected', $p3->getState());
        $this->assertEquals('pending', $p4->getState());
        $this->assertTrue($called);

        try {
            $p3->wait();
            $this->fail();
        } catch (CancellationException $e) {
            $this->assertContains('cancelled', $e->getMessage());
        }

        try {
            $p4->wait();
            $this->fail();
        } catch (CancellationException $e) {
            $this->assertContains('cancelled', $e->getMessage());
        }

        $this->assertEquals('rejected', $p4->getState());
    }

    public function testCancelsChildPromises()
    {
        $called1 = $called2 = $called3 = false;
        $p1 = new Promise(null, function () use (&$called1) { $called1 = true; });
        $p2 = new Promise(null, function () use (&$called2) { $called2 = true; });
        $p3 = new Promise(null, function () use (&$called3) { $called3 = true; });
        $p4 = $p2->then(function () use ($p3) { return $p3; });
        $p5 = $p4->then(function () { $this->fail(); });
        $p4->cancel();
        $this->assertEquals('pending', $p1->getState());
        $this->assertEquals('rejected', $p2->getState());
        $this->assertEquals('rejected', $p4->getState());
        $this->assertEquals('pending', $p5->getState());
        $this->assertFalse($called1);
        $this->assertTrue($called2);
        $this->assertFalse($called3);
    }

    public function testRejectsPromiseWhenCancelFails()
    {
        $called = false;
        $p = new Promise(null, function () use (&$called) {
            $called = true;
            throw new \Exception('e');
        });
        $p->cancel();
        $this->assertEquals('rejected', $p->getState());
        $this->assertTrue($called);
        try {
            $p->wait();
            $this->fail();
        } catch (\Exception $e) {
            $this->assertEquals('e', $e->getMessage());
        }
    }

    public function testCreatesPromiseWhenFulfilledAfterThen()
    {
        $p = new Promise();
        $carry = null;
        $p2 = $p->then(function ($v) use (&$carry) { $carry = $v; });
        $this->assertNotSame($p, $p2);
        $p->resolve('foo');
        P\queue()->run();

        $this->assertEquals('foo', $carry);
    }

    public function testCreatesPromiseWhenFulfilledBeforeThen()
    {
        $p = new Promise();
        $p->resolve('foo');
        $carry = null;
        $p2 = $p->then(function ($v) use (&$carry) { $carry = $v; });
        $this->assertNotSame($p, $p2);
        $this->assertNull($carry);
        \GuzzleHttp\Promise\queue()->run();
        $this->assertEquals('foo', $carry);
    }

    public function testCreatesPromiseWhenFulfilledWithNoCallback()
    {
        $p = new Promise();
        $p->resolve('foo');
        $p2 = $p->then();
        $this->assertNotSame($p, $p2);
        $this->assertInstanceOf('GuzzleHttp\Promise\FulfilledPromise', $p2);
    }

    public function testCreatesPromiseWhenRejectedAfterThen()
    {
        $p = new Promise();
        $carry = null;
        $p2 = $p->then(null, function ($v) use (&$carry) { $carry = $v; });
        $this->assertNotSame($p, $p2);
        $p->reject('foo');
        P\queue()->run();
        $this->assertEquals('foo', $carry);
    }

    public function testCreatesPromiseWhenRejectedBeforeThen()
    {
        $p = new Promise();
        $p->reject('foo');
        $carry = null;
        $p2 = $p->then(null, function ($v) use (&$carry) { $carry = $v; });
        $this->assertNotSame($p, $p2);
        $this->assertNull($carry);
        P\queue()->run();
        $this->assertEquals('foo', $carry);
    }

    public function testCreatesPromiseWhenRejectedWithNoCallback()
    {
        $p = new Promise();
        $p->reject('foo');
        $p2 = $p->then();
        $this->assertNotSame($p, $p2);
        $this->assertInstanceOf('GuzzleHttp\Promise\RejectedPromise', $p2);
    }

    public function testInvokesWaitFnsForThens()
    {
        $p = new Promise(function () use (&$p) { $p->resolve('a'); });
        $p2 = $p
            ->then(function ($v) { return $v . '-1-'; })
            ->then(function ($v) { return $v . '2'; });
        $this->assertEquals('a-1-2', $p2->wait());
    }

    public function testStacksThenWaitFunctions()
    {
        $p1 = new Promise(function () use (&$p1) { $p1->resolve('a'); });
        $p2 = new Promise(function () use (&$p2) { $p2->resolve('b'); });
        $p3 = new Promise(function () use (&$p3) { $p3->resolve('c'); });
        $p4 = $p1
            ->then(function () use ($p2) { return $p2; })
            ->then(function () use ($p3) { return $p3; });
        $this->assertEquals('c', $p4->wait());
    }

    public function testForwardsFulfilledDownChainBetweenGaps()
    {
        $p = new Promise();
        $r = $r2 = null;
        $p->then(null, null)
            ->then(function ($v) use (&$r) { $r = $v; return $v . '2'; })
            ->then(function ($v) use (&$r2) { $r2 = $v; });
        $p->resolve('foo');
        P\queue()->run();
        $this->assertEquals('foo', $r);
        $this->assertEquals('foo2', $r2);
    }

    public function testForwardsRejectedPromisesDownChainBetweenGaps()
    {
        $p = new Promise();
        $r = $r2 = null;
        $p->then(null, null)
            ->then(null, function ($v) use (&$r) { $r = $v; return $v . '2'; })
            ->then(function ($v) use (&$r2) { $r2 = $v; });
        $p->reject('foo');
        P\queue()->run();
        $this->assertEquals('foo', $r);
        $this->assertEquals('foo2', $r2);
    }

    public function testForwardsThrownPromisesDownChainBetweenGaps()
    {
        $e = new \Exception();
        $p = new Promise();
        $r = $r2 = null;
        $p->then(null, null)
            ->then(null, function ($v) use (&$r, $e) {
                $r = $v;
                throw $e;
            })
            ->then(
                null,
                function ($v) use (&$r2) { $r2 = $v; }
            );
        $p->reject('foo');
        P\queue()->run();
        $this->assertEquals('foo', $r);
        $this->assertSame($e, $r2);
    }

    public function testForwardsReturnedRejectedPromisesDownChainBetweenGaps()
    {
        $p = new Promise();
        $rejected = new RejectedPromise('bar');
        $r = $r2 = null;
        $p->then(null, null)
            ->then(null, function ($v) use (&$r, $rejected) {
                $r = $v;
                return $rejected;
            })
            ->then(
                null,
                function ($v) use (&$r2) { $r2 = $v; }
            );
        $p->reject('foo');
        P\queue()->run();
        $this->assertEquals('foo', $r);
        $this->assertEquals('bar', $r2);
        try {
            $p->wait();
        } catch (RejectionException $e) {
            $this->assertEquals('foo', $e->getReason());
        }
    }

    public function testForwardsHandlersToNextPromise()
    {
        $p = new Promise();
        $p2 = new Promise();
        $resolved = null;
        $p
            ->then(function ($v) use ($p2) { return $p2; })
            ->then(function ($value) use (&$resolved) { $resolved = $value; });
        $p->resolve('a');
        $p2->resolve('b');
        P\queue()->run();
        $this->assertEquals('b', $resolved);
    }

    public function testRemovesReferenceFromChildWhenParentWaitedUpon()
    {
        $r = null;
        $p = new Promise(function () use (&$p) { $p->resolve('a'); });
        $p2 = new Promise(function () use (&$p2) { $p2->resolve('b'); });
        $pb = $p->then(
            function ($v) use ($p2, &$r) {
                $r = $v;
                return $p2;
            })
            ->then(function ($v) { return $v . '.'; });
        $this->assertEquals('a', $p->wait());
        $this->assertEquals('b', $p2->wait());
        $this->assertEquals('b.', $pb->wait());
        $this->assertEquals('a', $r);
    }

    public function testForwardsHandlersWhenFulfilledPromiseIsReturned()
    {
        $res = [];
        $p = new Promise();
        $p2 = new Promise();
        $p2->resolve('foo');
        $p2->then(function ($v) use (&$res) { $res[] = 'A:' . $v; });
        // $res is A:foo
        $p
            ->then(function () use ($p2, &$res) { $res[] = 'B'; return $p2; })
            ->then(function ($v) use (&$res) { $res[] = 'C:' . $v; });
        $p->resolve('a');
        $p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; });
        P\queue()->run();
        $this->assertEquals(['A:foo', 'B', 'D:a', 'C:foo'], $res);
    }

    public function testForwardsHandlersWhenRejectedPromiseIsReturned()
    {
        $res = [];
        $p = new Promise();
        $p2 = new Promise();
        $p2->reject('foo');
        $p2->then(null, function ($v) use (&$res) { $res[] = 'A:' . $v; });
        $p->then(null, function () use ($p2, &$res) { $res[] = 'B'; return $p2; })
            ->then(null, function ($v) use (&$res) { $res[] = 'C:' . $v; });
        $p->reject('a');
        $p->then(null, function ($v) use (&$res) { $res[] = 'D:' . $v; });
        P\queue()->run();
        $this->assertEquals(['A:foo', 'B', 'D:a', 'C:foo'], $res);
    }

    public function testDoesNotForwardRejectedPromise()
    {
        $res = [];
        $p = new Promise();
        $p2 = new Promise();
        $p2->cancel();
        $p2->then(function ($v) use (&$res) { $res[] = "B:$v"; return $v; });
        $p->then(function ($v) use ($p2, &$res) { $res[] = "B:$v"; return $p2; })
            ->then(function ($v) use (&$res) { $res[] = 'C:' . $v; });
        $p->resolve('a');
        $p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; });
        P\queue()->run();
        $this->assertEquals(['B:a', 'D:a'], $res);
    }

    public function testRecursivelyForwardsWhenOnlyThennable()
    {
        $res = [];
        $p = new Promise();
        $p2 = new Thennable();
        $p2->resolve('foo');
        $p2->then(function ($v) use (&$res) { $res[] = 'A:' . $v; });
        $p->then(function () use ($p2, &$res) { $res[] = 'B'; return $p2; })
            ->then(function ($v) use (&$res) { $res[] = 'C:' . $v; });
        $p->resolve('a');
        $p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; });
        P\queue()->run();
        $this->assertEquals(['A:foo', 'B', 'D:a', 'C:foo'], $res);
    }

    public function testRecursivelyForwardsWhenNotInstanceOfPromise()
    {
        $res = [];
        $p = new Promise();
        $p2 = new NotPromiseInstance();
        $p2->then(function ($v) use (&$res) { $res[] = 'A:' . $v; });
        $p->then(function () use ($p2, &$res) { $res[] = 'B'; return $p2; })
            ->then(function ($v) use (&$res) { $res[] = 'C:' . $v; });
        $p->resolve('a');
        $p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; });
        P\queue()->run();
        $this->assertEquals(['B', 'D:a'], $res);
        $p2->resolve('foo');
        P\queue()->run();
        $this->assertEquals(['B', 'D:a', 'A:foo', 'C:foo'], $res);
    }

    /**
     * @expectedException \LogicException
     * @expectedExceptionMessage Cannot fulfill or reject a promise with itself
     */
    public function testCannotResolveWithSelf()
    {
        $p = new Promise();
        $p->resolve($p);
    }

    /**
     * @expectedException \LogicException
     * @expectedExceptionMessage Cannot fulfill or reject a promise with itself
     */
    public function testCannotRejectWithSelf()
    {
        $p = new Promise();
        $p->reject($p);
    }

    public function testDoesNotBlowStackWhenWaitingOnNestedThens()
    {
        $inner = new Promise(function () use (&$inner) { $inner->resolve(0); });
        $prev = $inner;
        for ($i = 1; $i < 100; $i++) {
            $prev = $prev->then(function ($i) { return $i + 1; });
        }

        $parent = new Promise(function () use (&$parent, $prev) {
            $parent->resolve($prev);
        });

        $this->assertEquals(99, $parent->wait());
    }

    public function testOtherwiseIsSugarForRejections()
    {
        $p = new Promise();
        $p->reject('foo');
        $p->otherwise(function ($v) use (&$c) { $c = $v; });
        P\queue()->run();
        $this->assertEquals($c, 'foo');
    }
}
<?php
namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;

/**
 * @covers GuzzleHttp\Promise\RejectedPromise
 */
class RejectedPromiseTest extends \PHPUnit_Framework_TestCase
{
    public function testThrowsReasonWhenWaitedUpon()
    {
        $p = new RejectedPromise('foo');
        $this->assertEquals('rejected', $p->getState());
        try {
            $p->wait(true);
            $this->fail();
        } catch (\Exception $e) {
            $this->assertEquals('rejected', $p->getState());
            $this->assertContains('foo', $e->getMessage());
        }
    }

    public function testCannotCancel()
    {
        $p = new RejectedPromise('foo');
        $p->cancel();
        $this->assertEquals('rejected', $p->getState());
    }

    /**
     * @expectedException \LogicException
     * @exepctedExceptionMessage Cannot resolve a rejected promise
     */
    public function testCannotResolve()
    {
        $p = new RejectedPromise('foo');
        $p->resolve('bar');
    }

    /**
     * @expectedException \LogicException
     * @exepctedExceptionMessage Cannot reject a rejected promise
     */
    public function testCannotReject()
    {
        $p = new RejectedPromise('foo');
        $p->reject('bar');
    }

    public function testCanRejectWithSameValue()
    {
        $p = new RejectedPromise('foo');
        $p->reject('foo');
    }

    public function testThrowsSpecificException()
    {
        $e = new \Exception();
        $p = new RejectedPromise($e);
        try {
            $p->wait(true);
            $this->fail();
        } catch (\Exception $e2) {
            $this->assertSame($e, $e2);
        }
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testCannotResolveWithPromise()
    {
        new RejectedPromise(new Promise());
    }

    public function testReturnsSelfWhenNoOnReject()
    {
        $p = new RejectedPromise('a');
        $this->assertSame($p, $p->then());
    }

    public function testInvokesOnRejectedAsynchronously()
    {
        $p = new RejectedPromise('a');
        $r = null;
        $f = function ($reason) use (&$r) { $r = $reason; };
        $p->then(null, $f);
        $this->assertNull($r);
        \GuzzleHttp\Promise\queue()->run();
        $this->assertEquals('a', $r);
    }

    public function testReturnsNewRejectedWhenOnRejectedFails()
    {
        $p = new RejectedPromise('a');
        $f = function () { throw new \Exception('b'); };
        $p2 = $p->then(null, $f);
        $this->assertNotSame($p, $p2);
        try {
            $p2->wait();
            $this->fail();
        } catch (\Exception $e) {
            $this->assertEquals('b', $e->getMessage());
        }
    }

    public function testWaitingIsNoOp()
    {
        $p = new RejectedPromise('a');
        $p->wait(false);
    }

    public function testOtherwiseIsSugarForRejections()
    {
        $p = new RejectedPromise('foo');
        $p->otherwise(function ($v) use (&$c) { $c = $v; });
        \GuzzleHttp\Promise\queue()->run();
        $this->assertSame('foo', $c);
    }

    public function testCanResolveThenWithSuccess()
    {
        $actual = null;
        $p = new RejectedPromise('foo');
        $p->otherwise(function ($v) {
            return $v . ' bar';
        })->then(function ($v) use (&$actual) {
            $actual = $v;
        });
        \GuzzleHttp\Promise\queue()->run();
        $this->assertEquals('foo bar', $actual);
    }

    public function testDoesNotTryToRejectTwiceDuringTrampoline()
    {
        $fp = new RejectedPromise('a');
        $t1 = $fp->then(null, function ($v) { return $v . ' b'; });
        $t1->resolve('why!');
        $this->assertEquals('why!', $t1->wait());
    }
}
<?php
namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise\RejectionException;

class Thing1
{
    public function __construct($message)
    {
        $this->message = $message;
    }

    public function __toString()
    {
        return $this->message;
    }
}

class Thing2 implements \JsonSerializable
{
    public function jsonSerialize()
    {
        return '{}';
    }
}

/**
 * @covers GuzzleHttp\Promise\RejectionException
 */
class RejectionExceptionTest extends \PHPUnit_Framework_TestCase
{
    public function testCanGetReasonFromException()
    {
        $thing = new Thing1('foo');
        $e = new RejectionException($thing);

        $this->assertSame($thing, $e->getReason());
        $this->assertEquals('The promise was rejected with reason: foo', $e->getMessage());
    }

    public function testCanGetReasonMessageFromJson()
    {
        $reason = new Thing2();
        $e = new RejectionException($reason);
        $this->assertContains("{}", $e->getMessage());
    }
}
<?php
namespace GuzzleHttp\Promise\Test;

use GuzzleHttp\Promise\TaskQueue;

class TaskQueueTest extends \PHPUnit_Framework_TestCase
{
    public function testKnowsIfEmpty()
    {
        $tq = new TaskQueue(false);
        $this->assertTrue($tq->isEmpty());
    }

    public function testKnowsIfFull()
    {
        $tq = new TaskQueue(false);
        $tq->add(function () {});
        $this->assertFalse($tq->isEmpty());
    }

    public function testExecutesTasksInOrder()
    {
        $tq = new TaskQueue(false);
        $called = [];
        $tq->add(function () use (&$called) { $called[] = 'a'; });
        $tq->add(function () use (&$called) { $called[] = 'b'; });
        $tq->add(function () use (&$called) { $called[] = 'c'; });
        $tq->run();
        $this->assertEquals(['a', 'b', 'c'], $called);
    }
}
<?php
namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise\Promise;

class Thennable
{
    private $nextPromise = null;

    public function __construct()
    {
        $this->nextPromise = new Promise();
    }

    public function then(callable $res = null, callable $rej = null)
    {
        return $this->nextPromise->then($res, $rej);
    }

    public function resolve($value)
    {
        $this->nextPromise->resolve($value);
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Reads from multiple streams, one after the other.
 *
 * This is a read-only stream decorator.
 */
class AppendStream implements StreamInterface
{
    /** @var StreamInterface[] Streams being decorated */
    private $streams = [];

    private $seekable = true;
    private $current = 0;
    private $pos = 0;
    private $detached = false;

    /**
     * @param StreamInterface[] $streams Streams to decorate. Each stream must
     *                                   be readable.
     */
    public function __construct(array $streams = [])
    {
        foreach ($streams as $stream) {
            $this->addStream($stream);
        }
    }

    public function __toString()
    {
        try {
            $this->rewind();
            return $this->getContents();
        } catch (\Exception $e) {
            return '';
        }
    }

    /**
     * Add a stream to the AppendStream
     *
     * @param StreamInterface $stream Stream to append. Must be readable.
     *
     * @throws \InvalidArgumentException if the stream is not readable
     */
    public function addStream(StreamInterface $stream)
    {
        if (!$stream->isReadable()) {
            throw new \InvalidArgumentException('Each stream must be readable');
        }

        // The stream is only seekable if all streams are seekable
        if (!$stream->isSeekable()) {
            $this->seekable = false;
        }

        $this->streams[] = $stream;
    }

    public function getContents()
    {
        return copy_to_string($this);
    }

    /**
     * Closes each attached stream.
     *
     * {@inheritdoc}
     */
    public function close()
    {
        $this->pos = $this->current = 0;

        foreach ($this->streams as $stream) {
            $stream->close();
        }

        $this->streams = [];
    }

    /**
     * Detaches each attached stream
     *
     * {@inheritdoc}
     */
    public function detach()
    {
        $this->close();
        $this->detached = true;
    }

    public function tell()
    {
        return $this->pos;
    }

    /**
     * Tries to calculate the size by adding the size of each stream.
     *
     * If any of the streams do not return a valid number, then the size of the
     * append stream cannot be determined and null is returned.
     *
     * {@inheritdoc}
     */
    public function getSize()
    {
        $size = 0;

        foreach ($this->streams as $stream) {
            $s = $stream->getSize();
            if ($s === null) {
                return null;
            }
            $size += $s;
        }

        return $size;
    }

    public function eof()
    {
        return !$this->streams ||
            ($this->current >= count($this->streams) - 1 &&
             $this->streams[$this->current]->eof());
    }

    public function rewind()
    {
        $this->seek(0);
    }

    /**
     * Attempts to seek to the given position. Only supports SEEK_SET.
     *
     * {@inheritdoc}
     */
    public function seek($offset, $whence = SEEK_SET)
    {
        if (!$this->seekable) {
            throw new \RuntimeException('This AppendStream is not seekable');
        } elseif ($whence !== SEEK_SET) {
            throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
        }

        $this->pos = $this->current = 0;

        // Rewind each stream
        foreach ($this->streams as $i => $stream) {
            try {
                $stream->rewind();
            } catch (\Exception $e) {
                throw new \RuntimeException('Unable to seek stream '
                    . $i . ' of the AppendStream', 0, $e);
            }
        }

        // Seek to the actual position by reading from each stream
        while ($this->pos < $offset && !$this->eof()) {
            $result = $this->read(min(8096, $offset - $this->pos));
            if ($result === '') {
                break;
            }
        }
    }

    /**
     * Reads from all of the appended streams until the length is met or EOF.
     *
     * {@inheritdoc}
     */
    public function read($length)
    {
        $buffer = '';
        $total = count($this->streams) - 1;
        $remaining = $length;
        $progressToNext = false;

        while ($remaining > 0) {

            // Progress to the next stream if needed.
            if ($progressToNext || $this->streams[$this->current]->eof()) {
                $progressToNext = false;
                if ($this->current === $total) {
                    break;
                }
                $this->current++;
            }

            $result = $this->streams[$this->current]->read($remaining);

            // Using a loose comparison here to match on '', false, and null
            if ($result == null) {
                $progressToNext = true;
                continue;
            }

            $buffer .= $result;
            $remaining = $length - strlen($buffer);
        }

        $this->pos += strlen($buffer);

        return $buffer;
    }

    public function isReadable()
    {
        return true;
    }

    public function isWritable()
    {
        return false;
    }

    public function isSeekable()
    {
        return $this->seekable;
    }

    public function write($string)
    {
        throw new \RuntimeException('Cannot write to an AppendStream');
    }

    public function getMetadata($key = null)
    {
        return $key ? null : [];
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Provides a buffer stream that can be written to to fill a buffer, and read
 * from to remove bytes from the buffer.
 *
 * This stream returns a "hwm" metadata value that tells upstream consumers
 * what the configured high water mark of the stream is, or the maximum
 * preferred size of the buffer.
 */
class BufferStream implements StreamInterface
{
    private $hwm;
    private $buffer = '';

    /**
     * @param int $hwm High water mark, representing the preferred maximum
     *                 buffer size. If the size of the buffer exceeds the high
     *                 water mark, then calls to write will continue to succeed
     *                 but will return false to inform writers to slow down
     *                 until the buffer has been drained by reading from it.
     */
    public function __construct($hwm = 16384)
    {
        $this->hwm = $hwm;
    }

    public function __toString()
    {
        return $this->getContents();
    }

    public function getContents()
    {
        $buffer = $this->buffer;
        $this->buffer = '';

        return $buffer;
    }

    public function close()
    {
        $this->buffer = '';
    }

    public function detach()
    {
        $this->close();
    }

    public function getSize()
    {
        return strlen($this->buffer);
    }

    public function isReadable()
    {
        return true;
    }

    public function isWritable()
    {
        return true;
    }

    public function isSeekable()
    {
        return false;
    }

    public function rewind()
    {
        $this->seek(0);
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        throw new \RuntimeException('Cannot seek a BufferStream');
    }

    public function eof()
    {
        return strlen($this->buffer) === 0;
    }

    public function tell()
    {
        throw new \RuntimeException('Cannot determine the position of a BufferStream');
    }

    /**
     * Reads data from the buffer.
     */
    public function read($length)
    {
        $currentLength = strlen($this->buffer);

        if ($length >= $currentLength) {
            // No need to slice the buffer because we don't have enough data.
            $result = $this->buffer;
            $this->buffer = '';
        } else {
            // Slice up the result to provide a subset of the buffer.
            $result = substr($this->buffer, 0, $length);
            $this->buffer = substr($this->buffer, $length);
        }

        return $result;
    }

    /**
     * Writes data to the buffer.
     */
    public function write($string)
    {
        $this->buffer .= $string;

        // TODO: What should happen here?
        if (strlen($this->buffer) >= $this->hwm) {
            return false;
        }

        return strlen($string);
    }

    public function getMetadata($key = null)
    {
        if ($key == 'hwm') {
            return $this->hwm;
        }

        return $key ? null : [];
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Stream decorator that can cache previously read bytes from a sequentially
 * read stream.
 */
class CachingStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var StreamInterface Stream being wrapped */
    private $remoteStream;

    /** @var int Number of bytes to skip reading due to a write on the buffer */
    private $skipReadBytes = 0;

    /**
     * We will treat the buffer object as the body of the stream
     *
     * @param StreamInterface $stream Stream to cache
     * @param StreamInterface $target Optionally specify where data is cached
     */
    public function __construct(
        StreamInterface $stream,
        StreamInterface $target = null
    ) {
        $this->remoteStream = $stream;
        $this->stream = $target ?: new Stream(fopen('php://temp', 'r+'));
    }

    public function getSize()
    {
        return max($this->stream->getSize(), $this->remoteStream->getSize());
    }

    public function rewind()
    {
        $this->seek(0);
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        if ($whence == SEEK_SET) {
            $byte = $offset;
        } elseif ($whence == SEEK_CUR) {
            $byte = $offset + $this->tell();
        } elseif ($whence == SEEK_END) {
            $size = $this->remoteStream->getSize();
            if ($size === null) {
                $size = $this->cacheEntireStream();
            }
            $byte = $size + $offset;
        } else {
            throw new \InvalidArgumentException('Invalid whence');
        }

        $diff = $byte - $this->stream->getSize();

        if ($diff > 0) {
            // Read the remoteStream until we have read in at least the amount
            // of bytes requested, or we reach the end of the file.
            while ($diff > 0 && !$this->remoteStream->eof()) {
                $this->read($diff);
                $diff = $byte - $this->stream->getSize();
            }
        } else {
            // We can just do a normal seek since we've already seen this byte.
            $this->stream->seek($byte);
        }
    }

    public function read($length)
    {
        // Perform a regular read on any previously read data from the buffer
        $data = $this->stream->read($length);
        $remaining = $length - strlen($data);

        // More data was requested so read from the remote stream
        if ($remaining) {
            // If data was written to the buffer in a position that would have
            // been filled from the remote stream, then we must skip bytes on
            // the remote stream to emulate overwriting bytes from that
            // position. This mimics the behavior of other PHP stream wrappers.
            $remoteData = $this->remoteStream->read(
                $remaining + $this->skipReadBytes
            );

            if ($this->skipReadBytes) {
                $len = strlen($remoteData);
                $remoteData = substr($remoteData, $this->skipReadBytes);
                $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
            }

            $data .= $remoteData;
            $this->stream->write($remoteData);
        }

        return $data;
    }

    public function write($string)
    {
        // When appending to the end of the currently read stream, you'll want
        // to skip bytes from being read from the remote stream to emulate
        // other stream wrappers. Basically replacing bytes of data of a fixed
        // length.
        $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
        if ($overflow > 0) {
            $this->skipReadBytes += $overflow;
        }

        return $this->stream->write($string);
    }

    public function eof()
    {
        return $this->stream->eof() && $this->remoteStream->eof();
    }

    /**
     * Close both the remote stream and buffer stream
     */
    public function close()
    {
        $this->remoteStream->close() && $this->stream->close();
    }

    private function cacheEntireStream()
    {
        $target = new FnStream(['write' => 'strlen']);
        copy_to_stream($this, $target);

        return $this->tell();
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Stream decorator that begins dropping data once the size of the underlying
 * stream becomes too full.
 */
class DroppingStream implements StreamInterface
{
    use StreamDecoratorTrait;

    private $maxLength;

    /**
     * @param StreamInterface $stream    Underlying stream to decorate.
     * @param int             $maxLength Maximum size before dropping data.
     */
    public function __construct(StreamInterface $stream, $maxLength)
    {
        $this->stream = $stream;
        $this->maxLength = $maxLength;
    }

    public function write($string)
    {
        $diff = $this->maxLength - $this->stream->getSize();

        // Begin returning 0 when the underlying stream is too large.
        if ($diff <= 0) {
            return 0;
        }

        // Write the stream or a subset of the stream if needed.
        if (strlen($string) < $diff) {
            return $this->stream->write($string);
        }

        return $this->stream->write(substr($string, 0, $diff));
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Compose stream implementations based on a hash of functions.
 *
 * Allows for easy testing and extension of a provided stream without needing
 * to create a concrete class for a simple extension point.
 */
class FnStream implements StreamInterface
{
    /** @var array */
    private $methods;

    /** @var array Methods that must be implemented in the given array */
    private static $slots = ['__toString', 'close', 'detach', 'rewind',
        'getSize', 'tell', 'eof', 'isSeekable', 'seek', 'isWritable', 'write',
        'isReadable', 'read', 'getContents', 'getMetadata'];

    /**
     * @param array $methods Hash of method name to a callable.
     */
    public function __construct(array $methods)
    {
        $this->methods = $methods;

        // Create the functions on the class
        foreach ($methods as $name => $fn) {
            $this->{'_fn_' . $name} = $fn;
        }
    }

    /**
     * Lazily determine which methods are not implemented.
     * @throws \BadMethodCallException
     */
    public function __get($name)
    {
        throw new \BadMethodCallException(str_replace('_fn_', '', $name)
            . '() is not implemented in the FnStream');
    }

    /**
     * The close method is called on the underlying stream only if possible.
     */
    public function __destruct()
    {
        if (isset($this->_fn_close)) {
            call_user_func($this->_fn_close);
        }
    }

    /**
     * Adds custom functionality to an underlying stream by intercepting
     * specific method calls.
     *
     * @param StreamInterface $stream  Stream to decorate
     * @param array           $methods Hash of method name to a closure
     *
     * @return FnStream
     */
    public static function decorate(StreamInterface $stream, array $methods)
    {
        // If any of the required methods were not provided, then simply
        // proxy to the decorated stream.
        foreach (array_diff(self::$slots, array_keys($methods)) as $diff) {
            $methods[$diff] = [$stream, $diff];
        }

        return new self($methods);
    }

    public function __toString()
    {
        return call_user_func($this->_fn___toString);
    }

    public function close()
    {
        return call_user_func($this->_fn_close);
    }

    public function detach()
    {
        return call_user_func($this->_fn_detach);
    }

    public function getSize()
    {
        return call_user_func($this->_fn_getSize);
    }

    public function tell()
    {
        return call_user_func($this->_fn_tell);
    }

    public function eof()
    {
        return call_user_func($this->_fn_eof);
    }

    public function isSeekable()
    {
        return call_user_func($this->_fn_isSeekable);
    }

    public function rewind()
    {
        call_user_func($this->_fn_rewind);
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        call_user_func($this->_fn_seek, $offset, $whence);
    }

    public function isWritable()
    {
        return call_user_func($this->_fn_isWritable);
    }

    public function write($string)
    {
        return call_user_func($this->_fn_write, $string);
    }

    public function isReadable()
    {
        return call_user_func($this->_fn_isReadable);
    }

    public function read($length)
    {
        return call_user_func($this->_fn_read, $length);
    }

    public function getContents()
    {
        return call_user_func($this->_fn_getContents);
    }

    public function getMetadata($key = null)
    {
        return call_user_func($this->_fn_getMetadata, $key);
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;

/**
 * Returns the string representation of an HTTP message.
 *
 * @param MessageInterface $message Message to convert to a string.
 *
 * @return string
 */
function str(MessageInterface $message)
{
    if ($message instanceof RequestInterface) {
        $msg = trim($message->getMethod() . ' '
                . $message->getRequestTarget())
            . ' HTTP/' . $message->getProtocolVersion();
        if (!$message->hasHeader('host')) {
            $msg .= "\r\nHost: " . $message->getUri()->getHost();
        }
    } elseif ($message instanceof ResponseInterface) {
        $msg = 'HTTP/' . $message->getProtocolVersion() . ' '
            . $message->getStatusCode() . ' '
            . $message->getReasonPhrase();
    } else {
        throw new \InvalidArgumentException('Unknown message type');
    }

    foreach ($message->getHeaders() as $name => $values) {
        $msg .= "\r\n{$name}: " . implode(', ', $values);
    }

    return "{$msg}\r\n\r\n" . $message->getBody();
}

/**
 * Returns a UriInterface for the given value.
 *
 * This function accepts a string or {@see Psr\Http\Message\UriInterface} and
 * returns a UriInterface for the given value. If the value is already a
 * `UriInterface`, it is returned as-is.
 *
 * @param string|UriInterface $uri
 *
 * @return UriInterface
 * @throws \InvalidArgumentException
 */
function uri_for($uri)
{
    if ($uri instanceof UriInterface) {
        return $uri;
    } elseif (is_string($uri)) {
        return new Uri($uri);
    }

    throw new \InvalidArgumentException('URI must be a string or UriInterface');
}

/**
 * Create a new stream based on the input type.
 *
 * Options is an associative array that can contain the following keys:
 * - metadata: Array of custom metadata.
 * - size: Size of the stream.
 *
 * @param resource|string|null|int|float|bool|StreamInterface|callable $resource Entity body data
 * @param array                                                        $options  Additional options
 *
 * @return Stream
 * @throws \InvalidArgumentException if the $resource arg is not valid.
 */
function stream_for($resource = '', array $options = [])
{
    if (is_scalar($resource)) {
        $stream = fopen('php://temp', 'r+');
        if ($resource !== '') {
            fwrite($stream, $resource);
            fseek($stream, 0);
        }
        return new Stream($stream, $options);
    }

    switch (gettype($resource)) {
        case 'resource':
            return new Stream($resource, $options);
        case 'object':
            if ($resource instanceof StreamInterface) {
                return $resource;
            } elseif ($resource instanceof \Iterator) {
                return new PumpStream(function () use ($resource) {
                    if (!$resource->valid()) {
                        return false;
                    }
                    $result = $resource->current();
                    $resource->next();
                    return $result;
                }, $options);
            } elseif (method_exists($resource, '__toString')) {
                return stream_for((string) $resource, $options);
            }
            break;
        case 'NULL':
            return new Stream(fopen('php://temp', 'r+'), $options);
    }

    if (is_callable($resource)) {
        return new PumpStream($resource, $options);
    }

    throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource));
}

/**
 * Parse an array of header values containing ";" separated data into an
 * array of associative arrays representing the header key value pair
 * data of the header. When a parameter does not contain a value, but just
 * contains a key, this function will inject a key with a '' string value.
 *
 * @param string|array $header Header to parse into components.
 *
 * @return array Returns the parsed header values.
 */
function parse_header($header)
{
    static $trimmed = "\"'  \n\t\r";
    $params = $matches = [];

    foreach (normalize_header($header) as $val) {
        $part = [];
        foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
            if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
                $m = $matches[0];
                if (isset($m[1])) {
                    $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
                } else {
                    $part[] = trim($m[0], $trimmed);
                }
            }
        }
        if ($part) {
            $params[] = $part;
        }
    }

    return $params;
}

/**
 * Converts an array of header values that may contain comma separated
 * headers into an array of headers with no comma separated values.
 *
 * @param string|array $header Header to normalize.
 *
 * @return array Returns the normalized header field values.
 */
function normalize_header($header)
{
    if (!is_array($header)) {
        return array_map('trim', explode(',', $header));
    }

    $result = [];
    foreach ($header as $value) {
        foreach ((array) $value as $v) {
            if (strpos($v, ',') === false) {
                $result[] = $v;
                continue;
            }
            foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) {
                $result[] = trim($vv);
            }
        }
    }

    return $result;
}

/**
 * Clone and modify a request with the given changes.
 *
 * The changes can be one of:
 * - method: (string) Changes the HTTP method.
 * - set_headers: (array) Sets the given headers.
 * - remove_headers: (array) Remove the given headers.
 * - body: (mixed) Sets the given body.
 * - uri: (UriInterface) Set the URI.
 * - query: (string) Set the query string value of the URI.
 * - version: (string) Set the protocol version.
 *
 * @param RequestInterface $request Request to clone and modify.
 * @param array            $changes Changes to apply.
 *
 * @return RequestInterface
 */
function modify_request(RequestInterface $request, array $changes)
{
    if (!$changes) {
        return $request;
    }

    $headers = $request->getHeaders();

    if (!isset($changes['uri'])) {
        $uri = $request->getUri();
    } else {
        // Remove the host header if one is on the URI
        if ($host = $changes['uri']->getHost()) {
            $changes['set_headers']['Host'] = $host;

            if ($port = $changes['uri']->getPort()) {
                $standardPorts = ['http' => 80, 'https' => 443];
                $scheme = $changes['uri']->getScheme();
                if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
                    $changes['set_headers']['Host'] .= ':'.$port;
                }
            }
        }
        $uri = $changes['uri'];
    }

    if (!empty($changes['remove_headers'])) {
        $headers = _caseless_remove($changes['remove_headers'], $headers);
    }

    if (!empty($changes['set_headers'])) {
        $headers = _caseless_remove(array_keys($changes['set_headers']), $headers);
        $headers = $changes['set_headers'] + $headers;
    }

    if (isset($changes['query'])) {
        $uri = $uri->withQuery($changes['query']);
    }

    if ($request instanceof ServerRequestInterface) {
        return new ServerRequest(
            isset($changes['method']) ? $changes['method'] : $request->getMethod(),
            $uri,
            $headers,
            isset($changes['body']) ? $changes['body'] : $request->getBody(),
            isset($changes['version'])
                ? $changes['version']
                : $request->getProtocolVersion(),
            $request->getServerParams()
        );
    }

    return new Request(
        isset($changes['method']) ? $changes['method'] : $request->getMethod(),
        $uri,
        $headers,
        isset($changes['body']) ? $changes['body'] : $request->getBody(),
        isset($changes['version'])
            ? $changes['version']
            : $request->getProtocolVersion()
    );
}

/**
 * Attempts to rewind a message body and throws an exception on failure.
 *
 * The body of the message will only be rewound if a call to `tell()` returns a
 * value other than `0`.
 *
 * @param MessageInterface $message Message to rewind
 *
 * @throws \RuntimeException
 */
function rewind_body(MessageInterface $message)
{
    $body = $message->getBody();

    if ($body->tell()) {
        $body->rewind();
    }
}

/**
 * Safely opens a PHP stream resource using a filename.
 *
 * When fopen fails, PHP normally raises a warning. This function adds an
 * error handler that checks for errors and throws an exception instead.
 *
 * @param string $filename File to open
 * @param string $mode     Mode used to open the file
 *
 * @return resource
 * @throws \RuntimeException if the file cannot be opened
 */
function try_fopen($filename, $mode)
{
    $ex = null;
    set_error_handler(function () use ($filename, $mode, &$ex) {
        $ex = new \RuntimeException(sprintf(
            'Unable to open %s using mode %s: %s',
            $filename,
            $mode,
            func_get_args()[1]
        ));
    });

    $handle = fopen($filename, $mode);
    restore_error_handler();

    if ($ex) {
        /** @var $ex \RuntimeException */
        throw $ex;
    }

    return $handle;
}

/**
 * Copy the contents of a stream into a string until the given number of
 * bytes have been read.
 *
 * @param StreamInterface $stream Stream to read
 * @param int             $maxLen Maximum number of bytes to read. Pass -1
 *                                to read the entire stream.
 * @return string
 * @throws \RuntimeException on error.
 */
function copy_to_string(StreamInterface $stream, $maxLen = -1)
{
    $buffer = '';

    if ($maxLen === -1) {
        while (!$stream->eof()) {
            $buf = $stream->read(1048576);
            // Using a loose equality here to match on '' and false.
            if ($buf == null) {
                break;
            }
            $buffer .= $buf;
        }
        return $buffer;
    }

    $len = 0;
    while (!$stream->eof() && $len < $maxLen) {
        $buf = $stream->read($maxLen - $len);
        // Using a loose equality here to match on '' and false.
        if ($buf == null) {
            break;
        }
        $buffer .= $buf;
        $len = strlen($buffer);
    }

    return $buffer;
}

/**
 * Copy the contents of a stream into another stream until the given number
 * of bytes have been read.
 *
 * @param StreamInterface $source Stream to read from
 * @param StreamInterface $dest   Stream to write to
 * @param int             $maxLen Maximum number of bytes to read. Pass -1
 *                                to read the entire stream.
 *
 * @throws \RuntimeException on error.
 */
function copy_to_stream(
    StreamInterface $source,
    StreamInterface $dest,
    $maxLen = -1
) {
    if ($maxLen === -1) {
        while (!$source->eof()) {
            if (!$dest->write($source->read(1048576))) {
                break;
            }
        }
        return;
    }

    $bytes = 0;
    while (!$source->eof()) {
        $buf = $source->read($maxLen - $bytes);
        if (!($len = strlen($buf))) {
            break;
        }
        $bytes += $len;
        $dest->write($buf);
        if ($bytes == $maxLen) {
            break;
        }
    }
}

/**
 * Calculate a hash of a Stream
 *
 * @param StreamInterface $stream    Stream to calculate the hash for
 * @param string          $algo      Hash algorithm (e.g. md5, crc32, etc)
 * @param bool            $rawOutput Whether or not to use raw output
 *
 * @return string Returns the hash of the stream
 * @throws \RuntimeException on error.
 */
function hash(
    StreamInterface $stream,
    $algo,
    $rawOutput = false
) {
    $pos = $stream->tell();

    if ($pos > 0) {
        $stream->rewind();
    }

    $ctx = hash_init($algo);
    while (!$stream->eof()) {
        hash_update($ctx, $stream->read(1048576));
    }

    $out = hash_final($ctx, (bool) $rawOutput);
    $stream->seek($pos);

    return $out;
}

/**
 * Read a line from the stream up to the maximum allowed buffer length
 *
 * @param StreamInterface $stream    Stream to read from
 * @param int             $maxLength Maximum buffer length
 *
 * @return string|bool
 */
function readline(StreamInterface $stream, $maxLength = null)
{
    $buffer = '';
    $size = 0;

    while (!$stream->eof()) {
        // Using a loose equality here to match on '' and false.
        if (null == ($byte = $stream->read(1))) {
            return $buffer;
        }
        $buffer .= $byte;
        // Break when a new line is found or the max length - 1 is reached
        if ($byte === "\n" || ++$size === $maxLength - 1) {
            break;
        }
    }

    return $buffer;
}

/**
 * Parses a request message string into a request object.
 *
 * @param string $message Request message string.
 *
 * @return Request
 */
function parse_request($message)
{
    $data = _parse_message($message);
    $matches = [];
    if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
        throw new \InvalidArgumentException('Invalid request string');
    }
    $parts = explode(' ', $data['start-line'], 3);
    $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';

    $request = new Request(
        $parts[0],
        $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1],
        $data['headers'],
        $data['body'],
        $version
    );

    return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
}

/**
 * Parses a response message string into a response object.
 *
 * @param string $message Response message string.
 *
 * @return Response
 */
function parse_response($message)
{
    $data = _parse_message($message);
    if (!preg_match('/^HTTP\/.* [0-9]{3} .*/', $data['start-line'])) {
        throw new \InvalidArgumentException('Invalid response string');
    }
    $parts = explode(' ', $data['start-line'], 3);

    return new Response(
        $parts[1],
        $data['headers'],
        $data['body'],
        explode('/', $parts[0])[1],
        isset($parts[2]) ? $parts[2] : null
    );
}

/**
 * Parse a query string into an associative array.
 *
 * If multiple values are found for the same key, the value of that key
 * value pair will become an array. This function does not parse nested
 * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will
 * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']).
 *
 * @param string      $str         Query string to parse
 * @param bool|string $urlEncoding How the query string is encoded
 *
 * @return array
 */
function parse_query($str, $urlEncoding = true)
{
    $result = [];

    if ($str === '') {
        return $result;
    }

    if ($urlEncoding === true) {
        $decoder = function ($value) {
            return rawurldecode(str_replace('+', ' ', $value));
        };
    } elseif ($urlEncoding == PHP_QUERY_RFC3986) {
        $decoder = 'rawurldecode';
    } elseif ($urlEncoding == PHP_QUERY_RFC1738) {
        $decoder = 'urldecode';
    } else {
        $decoder = function ($str) { return $str; };
    }

    foreach (explode('&', $str) as $kvp) {
        $parts = explode('=', $kvp, 2);
        $key = $decoder($parts[0]);
        $value = isset($parts[1]) ? $decoder($parts[1]) : null;
        if (!isset($result[$key])) {
            $result[$key] = $value;
        } else {
            if (!is_array($result[$key])) {
                $result[$key] = [$result[$key]];
            }
            $result[$key][] = $value;
        }
    }

    return $result;
}

/**
 * Build a query string from an array of key value pairs.
 *
 * This function can use the return value of parse_query() to build a query
 * string. This function does not modify the provided keys when an array is
 * encountered (like http_build_query would).
 *
 * @param array     $params   Query string parameters.
 * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
 *                            to encode using RFC3986, or PHP_QUERY_RFC1738
 *                            to encode using RFC1738.
 * @return string
 */
function build_query(array $params, $encoding = PHP_QUERY_RFC3986)
{
    if (!$params) {
        return '';
    }

    if ($encoding === false) {
        $encoder = function ($str) { return $str; };
    } elseif ($encoding === PHP_QUERY_RFC3986) {
        $encoder = 'rawurlencode';
    } elseif ($encoding === PHP_QUERY_RFC1738) {
        $encoder = 'urlencode';
    } else {
        throw new \InvalidArgumentException('Invalid type');
    }

    $qs = '';
    foreach ($params as $k => $v) {
        $k = $encoder($k);
        if (!is_array($v)) {
            $qs .= $k;
            if ($v !== null) {
                $qs .= '=' . $encoder($v);
            }
            $qs .= '&';
        } else {
            foreach ($v as $vv) {
                $qs .= $k;
                if ($vv !== null) {
                    $qs .= '=' . $encoder($vv);
                }
                $qs .= '&';
            }
        }
    }

    return $qs ? (string) substr($qs, 0, -1) : '';
}

/**
 * Determines the mimetype of a file by looking at its extension.
 *
 * @param $filename
 *
 * @return null|string
 */
function mimetype_from_filename($filename)
{
    return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION));
}

/**
 * Maps a file extensions to a mimetype.
 *
 * @param $extension string The file extension.
 *
 * @return string|null
 * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
 */
function mimetype_from_extension($extension)
{
    static $mimetypes = [
        '7z' => 'application/x-7z-compressed',
        'aac' => 'audio/x-aac',
        'ai' => 'application/postscript',
        'aif' => 'audio/x-aiff',
        'asc' => 'text/plain',
        'asf' => 'video/x-ms-asf',
        'atom' => 'application/atom+xml',
        'avi' => 'video/x-msvideo',
        'bmp' => 'image/bmp',
        'bz2' => 'application/x-bzip2',
        'cer' => 'application/pkix-cert',
        'crl' => 'application/pkix-crl',
        'crt' => 'application/x-x509-ca-cert',
        'css' => 'text/css',
        'csv' => 'text/csv',
        'cu' => 'application/cu-seeme',
        'deb' => 'application/x-debian-package',
        'doc' => 'application/msword',
        'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'dvi' => 'application/x-dvi',
        'eot' => 'application/vnd.ms-fontobject',
        'eps' => 'application/postscript',
        'epub' => 'application/epub+zip',
        'etx' => 'text/x-setext',
        'flac' => 'audio/flac',
        'flv' => 'video/x-flv',
        'gif' => 'image/gif',
        'gz' => 'application/gzip',
        'htm' => 'text/html',
        'html' => 'text/html',
        'ico' => 'image/x-icon',
        'ics' => 'text/calendar',
        'ini' => 'text/plain',
        'iso' => 'application/x-iso9660-image',
        'jar' => 'application/java-archive',
        'jpe' => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'jpg' => 'image/jpeg',
        'js' => 'text/javascript',
        'json' => 'application/json',
        'latex' => 'application/x-latex',
        'log' => 'text/plain',
        'm4a' => 'audio/mp4',
        'm4v' => 'video/mp4',
        'mid' => 'audio/midi',
        'midi' => 'audio/midi',
        'mov' => 'video/quicktime',
        'mp3' => 'audio/mpeg',
        'mp4' => 'video/mp4',
        'mp4a' => 'audio/mp4',
        'mp4v' => 'video/mp4',
        'mpe' => 'video/mpeg',
        'mpeg' => 'video/mpeg',
        'mpg' => 'video/mpeg',
        'mpg4' => 'video/mp4',
        'oga' => 'audio/ogg',
        'ogg' => 'audio/ogg',
        'ogv' => 'video/ogg',
        'ogx' => 'application/ogg',
        'pbm' => 'image/x-portable-bitmap',
        'pdf' => 'application/pdf',
        'pgm' => 'image/x-portable-graymap',
        'png' => 'image/png',
        'pnm' => 'image/x-portable-anymap',
        'ppm' => 'image/x-portable-pixmap',
        'ppt' => 'application/vnd.ms-powerpoint',
        'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
        'ps' => 'application/postscript',
        'qt' => 'video/quicktime',
        'rar' => 'application/x-rar-compressed',
        'ras' => 'image/x-cmu-raster',
        'rss' => 'application/rss+xml',
        'rtf' => 'application/rtf',
        'sgm' => 'text/sgml',
        'sgml' => 'text/sgml',
        'svg' => 'image/svg+xml',
        'swf' => 'application/x-shockwave-flash',
        'tar' => 'application/x-tar',
        'tif' => 'image/tiff',
        'tiff' => 'image/tiff',
        'torrent' => 'application/x-bittorrent',
        'ttf' => 'application/x-font-ttf',
        'txt' => 'text/plain',
        'wav' => 'audio/x-wav',
        'webm' => 'video/webm',
        'wma' => 'audio/x-ms-wma',
        'wmv' => 'video/x-ms-wmv',
        'woff' => 'application/x-font-woff',
        'wsdl' => 'application/wsdl+xml',
        'xbm' => 'image/x-xbitmap',
        'xls' => 'application/vnd.ms-excel',
        'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        'xml' => 'application/xml',
        'xpm' => 'image/x-xpixmap',
        'xwd' => 'image/x-xwindowdump',
        'yaml' => 'text/yaml',
        'yml' => 'text/yaml',
        'zip' => 'application/zip',
    ];

    $extension = strtolower($extension);

    return isset($mimetypes[$extension])
        ? $mimetypes[$extension]
        : null;
}

/**
 * Parses an HTTP message into an associative array.
 *
 * The array contains the "start-line" key containing the start line of
 * the message, "headers" key containing an associative array of header
 * array values, and a "body" key containing the body of the message.
 *
 * @param string $message HTTP request or response to parse.
 *
 * @return array
 * @internal
 */
function _parse_message($message)
{
    if (!$message) {
        throw new \InvalidArgumentException('Invalid message');
    }

    // Iterate over each line in the message, accounting for line endings
    $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
    $result = ['start-line' => array_shift($lines), 'headers' => [], 'body' => ''];
    array_shift($lines);

    for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) {
        $line = $lines[$i];
        // If two line breaks were encountered, then this is the end of body
        if (empty($line)) {
            if ($i < $totalLines - 1) {
                $result['body'] = implode('', array_slice($lines, $i + 2));
            }
            break;
        }
        if (strpos($line, ':')) {
            $parts = explode(':', $line, 2);
            $key = trim($parts[0]);
            $value = isset($parts[1]) ? trim($parts[1]) : '';
            $result['headers'][$key][] = $value;
        }
    }

    return $result;
}

/**
 * Constructs a URI for an HTTP request message.
 *
 * @param string $path    Path from the start-line
 * @param array  $headers Array of headers (each value an array).
 *
 * @return string
 * @internal
 */
function _parse_request_uri($path, array $headers)
{
    $hostKey = array_filter(array_keys($headers), function ($k) {
        return strtolower($k) === 'host';
    });

    // If no host is found, then a full URI cannot be constructed.
    if (!$hostKey) {
        return $path;
    }

    $host = $headers[reset($hostKey)][0];
    $scheme = substr($host, -4) === ':443' ? 'https' : 'http';

    return $scheme . '://' . $host . '/' . ltrim($path, '/');
}

/** @internal */
function _caseless_remove($keys, array $data)
{
    $result = [];

    foreach ($keys as &$key) {
        $key = strtolower($key);
    }

    foreach ($data as $k => $v) {
        if (!in_array(strtolower($k), $keys)) {
            $result[$k] = $v;
        }
    }

    return $result;
}
<?php

// Don't redefine the functions if included multiple times.
if (!function_exists('GuzzleHttp\Psr7\str')) {
    require __DIR__ . '/functions.php';
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Uses PHP's zlib.inflate filter to inflate deflate or gzipped content.
 *
 * This stream decorator skips the first 10 bytes of the given stream to remove
 * the gzip header, converts the provided stream to a PHP stream resource,
 * then appends the zlib.inflate filter. The stream is then converted back
 * to a Guzzle stream resource to be used as a Guzzle stream.
 *
 * @link http://tools.ietf.org/html/rfc1952
 * @link http://php.net/manual/en/filters.compression.php
 */
class InflateStream implements StreamInterface
{
    use StreamDecoratorTrait;

    public function __construct(StreamInterface $stream)
    {
        // read the first 10 bytes, ie. gzip header
        $header = $stream->read(10);
        $filenameHeaderLength = $this->getLengthOfPossibleFilenameHeader($stream, $header);
        // Skip the header, that is 10 + length of filename + 1 (nil) bytes
        $stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength);
        $resource = StreamWrapper::getResource($stream);
        stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ);
        $this->stream = new Stream($resource);
    }

    /**
     * @param StreamInterface $stream
     * @param $header
     * @return int
     */
    private function getLengthOfPossibleFilenameHeader(StreamInterface $stream, $header)
    {
        $filename_header_length = 0;

        if (substr(bin2hex($header), 6, 2) === '08') {
            // we have a filename, read until nil
            $filename_header_length = 1;
            while ($stream->read(1) !== chr(0)) {
                $filename_header_length++;
            }
        }

        return $filename_header_length;
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Lazily reads or writes to a file that is opened only after an IO operation
 * take place on the stream.
 */
class LazyOpenStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var string File to open */
    private $filename;

    /** @var string $mode */
    private $mode;

    /**
     * @param string $filename File to lazily open
     * @param string $mode     fopen mode to use when opening the stream
     */
    public function __construct($filename, $mode)
    {
        $this->filename = $filename;
        $this->mode = $mode;
    }

    /**
     * Creates the underlying stream lazily when required.
     *
     * @return StreamInterface
     */
    protected function createStream()
    {
        return stream_for(try_fopen($this->filename, $this->mode));
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;


/**
 * Decorator used to return only a subset of a stream
 */
class LimitStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var int Offset to start reading from */
    private $offset;

    /** @var int Limit the number of bytes that can be read */
    private $limit;

    /**
     * @param StreamInterface $stream Stream to wrap
     * @param int             $limit  Total number of bytes to allow to be read
     *                                from the stream. Pass -1 for no limit.
     * @param int|null        $offset Position to seek to before reading (only
     *                                works on seekable streams).
     */
    public function __construct(
        StreamInterface $stream,
        $limit = -1,
        $offset = 0
    ) {
        $this->stream = $stream;
        $this->setLimit($limit);
        $this->setOffset($offset);
    }

    public function eof()
    {
        // Always return true if the underlying stream is EOF
        if ($this->stream->eof()) {
            return true;
        }

        // No limit and the underlying stream is not at EOF
        if ($this->limit == -1) {
            return false;
        }

        return $this->stream->tell() >= $this->offset + $this->limit;
    }

    /**
     * Returns the size of the limited subset of data
     * {@inheritdoc}
     */
    public function getSize()
    {
        if (null === ($length = $this->stream->getSize())) {
            return null;
        } elseif ($this->limit == -1) {
            return $length - $this->offset;
        } else {
            return min($this->limit, $length - $this->offset);
        }
    }

    /**
     * Allow for a bounded seek on the read limited stream
     * {@inheritdoc}
     */
    public function seek($offset, $whence = SEEK_SET)
    {
        if ($whence !== SEEK_SET || $offset < 0) {
            throw new \RuntimeException(sprintf(
                'Cannot seek to offset % with whence %s',
                $offset,
                $whence
            ));
        }

        $offset += $this->offset;

        if ($this->limit !== -1) {
            if ($offset > $this->offset + $this->limit) {
                $offset = $this->offset + $this->limit;
            }
        }

        $this->stream->seek($offset);
    }

    /**
     * Give a relative tell()
     * {@inheritdoc}
     */
    public function tell()
    {
        return $this->stream->tell() - $this->offset;
    }

    /**
     * Set the offset to start limiting from
     *
     * @param int $offset Offset to seek to and begin byte limiting from
     *
     * @throws \RuntimeException if the stream cannot be seeked.
     */
    public function setOffset($offset)
    {
        $current = $this->stream->tell();

        if ($current !== $offset) {
            // If the stream cannot seek to the offset position, then read to it
            if ($this->stream->isSeekable()) {
                $this->stream->seek($offset);
            } elseif ($current > $offset) {
                throw new \RuntimeException("Could not seek to stream offset $offset");
            } else {
                $this->stream->read($offset - $current);
            }
        }

        $this->offset = $offset;
    }

    /**
     * Set the limit of bytes that the decorator allows to be read from the
     * stream.
     *
     * @param int $limit Number of bytes to allow to be read from the stream.
     *                   Use -1 for no limit.
     */
    public function setLimit($limit)
    {
        $this->limit = $limit;
    }

    public function read($length)
    {
        if ($this->limit == -1) {
            return $this->stream->read($length);
        }

        // Check if the current position is less than the total allowed
        // bytes + original offset
        $remaining = ($this->offset + $this->limit) - $this->stream->tell();
        if ($remaining > 0) {
            // Only return the amount of requested data, ensuring that the byte
            // limit is not exceeded
            return $this->stream->read(min($remaining, $length));
        }

        return '';
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Trait implementing functionality common to requests and responses.
 */
trait MessageTrait
{
    /** @var array Map of all registered headers, as original name => array of values */
    private $headers = [];

    /** @var array Map of lowercase header name => original name at registration */
    private $headerNames  = [];

    /** @var string */
    private $protocol = '1.1';

    /** @var StreamInterface */
    private $stream;

    public function getProtocolVersion()
    {
        return $this->protocol;
    }

    public function withProtocolVersion($version)
    {
        if ($this->protocol === $version) {
            return $this;
        }

        $new = clone $this;
        $new->protocol = $version;
        return $new;
    }

    public function getHeaders()
    {
        return $this->headers;
    }

    public function hasHeader($header)
    {
        return isset($this->headerNames[strtolower($header)]);
    }

    public function getHeader($header)
    {
        $header = strtolower($header);

        if (!isset($this->headerNames[$header])) {
            return [];
        }

        $header = $this->headerNames[$header];

        return $this->headers[$header];
    }

    public function getHeaderLine($header)
    {
        return implode(', ', $this->getHeader($header));
    }

    public function withHeader($header, $value)
    {
        if (!is_array($value)) {
            $value = [$value];
        }

        $value = $this->trimHeaderValues($value);
        $normalized = strtolower($header);

        $new = clone $this;
        if (isset($new->headerNames[$normalized])) {
            unset($new->headers[$new->headerNames[$normalized]]);
        }
        $new->headerNames[$normalized] = $header;
        $new->headers[$header] = $value;

        return $new;
    }

    public function withAddedHeader($header, $value)
    {
        if (!is_array($value)) {
            $value = [$value];
        }

        $value = $this->trimHeaderValues($value);
        $normalized = strtolower($header);

        $new = clone $this;
        if (isset($new->headerNames[$normalized])) {
            $header = $this->headerNames[$normalized];
            $new->headers[$header] = array_merge($this->headers[$header], $value);
        } else {
            $new->headerNames[$normalized] = $header;
            $new->headers[$header] = $value;
        }

        return $new;
    }

    public function withoutHeader($header)
    {
        $normalized = strtolower($header);

        if (!isset($this->headerNames[$normalized])) {
            return $this;
        }

        $header = $this->headerNames[$normalized];

        $new = clone $this;
        unset($new->headers[$header], $new->headerNames[$normalized]);

        return $new;
    }

    public function getBody()
    {
        if (!$this->stream) {
            $this->stream = stream_for('');
        }

        return $this->stream;
    }

    public function withBody(StreamInterface $body)
    {
        if ($body === $this->stream) {
            return $this;
        }

        $new = clone $this;
        $new->stream = $body;
        return $new;
    }

    private function setHeaders(array $headers)
    {
        $this->headerNames = $this->headers = [];
        foreach ($headers as $header => $value) {
            if (!is_array($value)) {
                $value = [$value];
            }

            $value = $this->trimHeaderValues($value);
            $normalized = strtolower($header);
            if (isset($this->headerNames[$normalized])) {
                $header = $this->headerNames[$normalized];
                $this->headers[$header] = array_merge($this->headers[$header], $value);
            } else {
                $this->headerNames[$normalized] = $header;
                $this->headers[$header] = $value;
            }
        }
    }

    /**
     * Trims whitespace from the header values.
     *
     * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
     *
     * header-field = field-name ":" OWS field-value OWS
     * OWS          = *( SP / HTAB )
     *
     * @param string[] $values Header values
     *
     * @return string[] Trimmed header values
     *
     * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
     */
    private function trimHeaderValues(array $values)
    {
        return array_map(function ($value) {
            return trim($value, " \t");
        }, $values);
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Stream that when read returns bytes for a streaming multipart or
 * multipart/form-data stream.
 */
class MultipartStream implements StreamInterface
{
    use StreamDecoratorTrait;

    private $boundary;

    /**
     * @param array  $elements Array of associative arrays, each containing a
     *                         required "name" key mapping to the form field,
     *                         name, a required "contents" key mapping to a
     *                         StreamInterface/resource/string, an optional
     *                         "headers" associative array of custom headers,
     *                         and an optional "filename" key mapping to a
     *                         string to send as the filename in the part.
     * @param string $boundary You can optionally provide a specific boundary
     *
     * @throws \InvalidArgumentException
     */
    public function __construct(array $elements = [], $boundary = null)
    {
        $this->boundary = $boundary ?: uniqid();
        $this->stream = $this->createStream($elements);
    }

    /**
     * Get the boundary
     *
     * @return string
     */
    public function getBoundary()
    {
        return $this->boundary;
    }

    public function isWritable()
    {
        return false;
    }

    /**
     * Get the headers needed before transferring the content of a POST file
     */
    private function getHeaders(array $headers)
    {
        $str = '';
        foreach ($headers as $key => $value) {
            $str .= "{$key}: {$value}\r\n";
        }

        return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n";
    }

    /**
     * Create the aggregate stream that will be used to upload the POST data
     */
    protected function createStream(array $elements)
    {
        $stream = new AppendStream();

        foreach ($elements as $element) {
            $this->addElement($stream, $element);
        }

        // Add the trailing boundary with CRLF
        $stream->addStream(stream_for("--{$this->boundary}--\r\n"));

        return $stream;
    }

    private function addElement(AppendStream $stream, array $element)
    {
        foreach (['contents', 'name'] as $key) {
            if (!array_key_exists($key, $element)) {
                throw new \InvalidArgumentException("A '{$key}' key is required");
            }
        }

        $element['contents'] = stream_for($element['contents']);

        if (empty($element['filename'])) {
            $uri = $element['contents']->getMetadata('uri');
            if (substr($uri, 0, 6) !== 'php://') {
                $element['filename'] = $uri;
            }
        }

        list($body, $headers) = $this->createElement(
            $element['name'],
            $element['contents'],
            isset($element['filename']) ? $element['filename'] : null,
            isset($element['headers']) ? $element['headers'] : []
        );

        $stream->addStream(stream_for($this->getHeaders($headers)));
        $stream->addStream($body);
        $stream->addStream(stream_for("\r\n"));
    }

    /**
     * @return array
     */
    private function createElement($name, $stream, $filename, array $headers)
    {
        // Set a default content-disposition header if one was no provided
        $disposition = $this->getHeader($headers, 'content-disposition');
        if (!$disposition) {
            $headers['Content-Disposition'] = ($filename === '0' || $filename)
                ? sprintf('form-data; name="%s"; filename="%s"',
                    $name,
                    basename($filename))
                : "form-data; name=\"{$name}\"";
        }

        // Set a default content-length header if one was no provided
        $length = $this->getHeader($headers, 'content-length');
        if (!$length) {
            if ($length = $stream->getSize()) {
                $headers['Content-Length'] = (string) $length;
            }
        }

        // Set a default Content-Type if one was not supplied
        $type = $this->getHeader($headers, 'content-type');
        if (!$type && ($filename === '0' || $filename)) {
            if ($type = mimetype_from_filename($filename)) {
                $headers['Content-Type'] = $type;
            }
        }

        return [$stream, $headers];
    }

    private function getHeader(array $headers, $key)
    {
        $lowercaseHeader = strtolower($key);
        foreach ($headers as $k => $v) {
            if (strtolower($k) === $lowercaseHeader) {
                return $v;
            }
        }

        return null;
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Stream decorator that prevents a stream from being seeked
 */
class NoSeekStream implements StreamInterface
{
    use StreamDecoratorTrait;

    public function seek($offset, $whence = SEEK_SET)
    {
        throw new \RuntimeException('Cannot seek a NoSeekStream');
    }

    public function isSeekable()
    {
        return false;
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Provides a read only stream that pumps data from a PHP callable.
 *
 * When invoking the provided callable, the PumpStream will pass the amount of
 * data requested to read to the callable. The callable can choose to ignore
 * this value and return fewer or more bytes than requested. Any extra data
 * returned by the provided callable is buffered internally until drained using
 * the read() function of the PumpStream. The provided callable MUST return
 * false when there is no more data to read.
 */
class PumpStream implements StreamInterface
{
    /** @var callable */
    private $source;

    /** @var int */
    private $size;

    /** @var int */
    private $tellPos = 0;

    /** @var array */
    private $metadata;

    /** @var BufferStream */
    private $buffer;

    /**
     * @param callable $source Source of the stream data. The callable MAY
     *                         accept an integer argument used to control the
     *                         amount of data to return. The callable MUST
     *                         return a string when called, or false on error
     *                         or EOF.
     * @param array $options   Stream options:
     *                         - metadata: Hash of metadata to use with stream.
     *                         - size: Size of the stream, if known.
     */
    public function __construct(callable $source, array $options = [])
    {
        $this->source = $source;
        $this->size = isset($options['size']) ? $options['size'] : null;
        $this->metadata = isset($options['metadata']) ? $options['metadata'] : [];
        $this->buffer = new BufferStream();
    }

    public function __toString()
    {
        try {
            return copy_to_string($this);
        } catch (\Exception $e) {
            return '';
        }
    }

    public function close()
    {
        $this->detach();
    }

    public function detach()
    {
        $this->tellPos = false;
        $this->source = null;
    }

    public function getSize()
    {
        return $this->size;
    }

    public function tell()
    {
        return $this->tellPos;
    }

    public function eof()
    {
        return !$this->source;
    }

    public function isSeekable()
    {
        return false;
    }

    public function rewind()
    {
        $this->seek(0);
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        throw new \RuntimeException('Cannot seek a PumpStream');
    }

    public function isWritable()
    {
        return false;
    }

    public function write($string)
    {
        throw new \RuntimeException('Cannot write to a PumpStream');
    }

    public function isReadable()
    {
        return true;
    }

    public function read($length)
    {
        $data = $this->buffer->read($length);
        $readLen = strlen($data);
        $this->tellPos += $readLen;
        $remaining = $length - $readLen;

        if ($remaining) {
            $this->pump($remaining);
            $data .= $this->buffer->read($remaining);
            $this->tellPos += strlen($data) - $readLen;
        }

        return $data;
    }

    public function getContents()
    {
        $result = '';
        while (!$this->eof()) {
            $result .= $this->read(1000000);
        }

        return $result;
    }

    public function getMetadata($key = null)
    {
        if (!$key) {
            return $this->metadata;
        }

        return isset($this->metadata[$key]) ? $this->metadata[$key] : null;
    }

    private function pump($length)
    {
        if ($this->source) {
            do {
                $data = call_user_func($this->source, $length);
                if ($data === false || $data === null) {
                    $this->source = null;
                    return;
                }
                $this->buffer->write($data);
                $length -= strlen($data);
            } while ($length > 0);
        }
    }
}
<?php
namespace GuzzleHttp\Psr7;

use InvalidArgumentException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;

/**
 * PSR-7 request implementation.
 */
class Request implements RequestInterface
{
    use MessageTrait;

    /** @var string */
    private $method;

    /** @var null|string */
    private $requestTarget;

    /** @var null|UriInterface */
    private $uri;

    /**
     * @param string                               $method  HTTP method
     * @param string|UriInterface                  $uri     URI
     * @param array                                $headers Request headers
     * @param string|null|resource|StreamInterface $body    Request body
     * @param string                               $version Protocol version
     */
    public function __construct(
        $method,
        $uri,
        array $headers = [],
        $body = null,
        $version = '1.1'
    ) {
        if (!($uri instanceof UriInterface)) {
            $uri = new Uri($uri);
        }

        $this->method = strtoupper($method);
        $this->uri = $uri;
        $this->setHeaders($headers);
        $this->protocol = $version;

        if (!$this->hasHeader('Host')) {
            $this->updateHostFromUri();
        }

        if ($body !== '' && $body !== null) {
            $this->stream = stream_for($body);
        }
    }

    public function getRequestTarget()
    {
        if ($this->requestTarget !== null) {
            return $this->requestTarget;
        }

        $target = $this->uri->getPath();
        if ($target == '') {
            $target = '/';
        }
        if ($this->uri->getQuery() != '') {
            $target .= '?' . $this->uri->getQuery();
        }

        return $target;
    }

    public function withRequestTarget($requestTarget)
    {
        if (preg_match('#\s#', $requestTarget)) {
            throw new InvalidArgumentException(
                'Invalid request target provided; cannot contain whitespace'
            );
        }

        $new = clone $this;
        $new->requestTarget = $requestTarget;
        return $new;
    }

    public function getMethod()
    {
        return $this->method;
    }

    public function withMethod($method)
    {
        $new = clone $this;
        $new->method = strtoupper($method);
        return $new;
    }

    public function getUri()
    {
        return $this->uri;
    }

    public function withUri(UriInterface $uri, $preserveHost = false)
    {
        if ($uri === $this->uri) {
            return $this;
        }

        $new = clone $this;
        $new->uri = $uri;

        if (!$preserveHost) {
            $new->updateHostFromUri();
        }

        return $new;
    }

    private function updateHostFromUri()
    {
        $host = $this->uri->getHost();

        if ($host == '') {
            return;
        }

        if (($port = $this->uri->getPort()) !== null) {
            $host .= ':' . $port;
        }

        if (isset($this->headerNames['host'])) {
            $header = $this->headerNames['host'];
        } else {
            $header = 'Host';
            $this->headerNames['host'] = 'Host';
        }
        // Ensure Host is the first header.
        // See: http://tools.ietf.org/html/rfc7230#section-5.4
        $this->headers = [$header => [$host]] + $this->headers;
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\ResponseInterface;

/**
 * PSR-7 response implementation.
 */
class Response implements ResponseInterface
{
    use MessageTrait;

    /** @var array Map of standard HTTP status code/reason phrases */
    private static $phrases = [
        100 => 'Continue',
        101 => 'Switching Protocols',
        102 => 'Processing',
        200 => 'OK',
        201 => 'Created',
        202 => 'Accepted',
        203 => 'Non-Authoritative Information',
        204 => 'No Content',
        205 => 'Reset Content',
        206 => 'Partial Content',
        207 => 'Multi-status',
        208 => 'Already Reported',
        300 => 'Multiple Choices',
        301 => 'Moved Permanently',
        302 => 'Found',
        303 => 'See Other',
        304 => 'Not Modified',
        305 => 'Use Proxy',
        306 => 'Switch Proxy',
        307 => 'Temporary Redirect',
        400 => 'Bad Request',
        401 => 'Unauthorized',
        402 => 'Payment Required',
        403 => 'Forbidden',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        406 => 'Not Acceptable',
        407 => 'Proxy Authentication Required',
        408 => 'Request Time-out',
        409 => 'Conflict',
        410 => 'Gone',
        411 => 'Length Required',
        412 => 'Precondition Failed',
        413 => 'Request Entity Too Large',
        414 => 'Request-URI Too Large',
        415 => 'Unsupported Media Type',
        416 => 'Requested range not satisfiable',
        417 => 'Expectation Failed',
        418 => 'I\'m a teapot',
        422 => 'Unprocessable Entity',
        423 => 'Locked',
        424 => 'Failed Dependency',
        425 => 'Unordered Collection',
        426 => 'Upgrade Required',
        428 => 'Precondition Required',
        429 => 'Too Many Requests',
        431 => 'Request Header Fields Too Large',
        451 => 'Unavailable For Legal Reasons',
        500 => 'Internal Server Error',
        501 => 'Not Implemented',
        502 => 'Bad Gateway',
        503 => 'Service Unavailable',
        504 => 'Gateway Time-out',
        505 => 'HTTP Version not supported',
        506 => 'Variant Also Negotiates',
        507 => 'Insufficient Storage',
        508 => 'Loop Detected',
        511 => 'Network Authentication Required',
    ];

    /** @var string */
    private $reasonPhrase = '';

    /** @var int */
    private $statusCode = 200;

    /**
     * @param int                                  $status  Status code
     * @param array                                $headers Response headers
     * @param string|null|resource|StreamInterface $body    Response body
     * @param string                               $version Protocol version
     * @param string|null                          $reason  Reason phrase (when empty a default will be used based on the status code)
     */
    public function __construct(
        $status = 200,
        array $headers = [],
        $body = null,
        $version = '1.1',
        $reason = null
    ) {
        $this->statusCode = (int) $status;

        if ($body !== '' && $body !== null) {
            $this->stream = stream_for($body);
        }

        $this->setHeaders($headers);
        if ($reason == '' && isset(self::$phrases[$this->statusCode])) {
            $this->reasonPhrase = self::$phrases[$status];
        } else {
            $this->reasonPhrase = (string) $reason;
        }

        $this->protocol = $version;
    }

    public function getStatusCode()
    {
        return $this->statusCode;
    }

    public function getReasonPhrase()
    {
        return $this->reasonPhrase;
    }

    public function withStatus($code, $reasonPhrase = '')
    {
        $new = clone $this;
        $new->statusCode = (int) $code;
        if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) {
            $reasonPhrase = self::$phrases[$new->statusCode];
        }
        $new->reasonPhrase = $reasonPhrase;
        return $new;
    }
}
<?php

namespace GuzzleHttp\Psr7;

use InvalidArgumentException;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;

/**
 * Server-side HTTP request
 *
 * Extends the Request definition to add methods for accessing incoming data,
 * specifically server parameters, cookies, matched path parameters, query
 * string arguments, body parameters, and upload file information.
 *
 * "Attributes" are discovered via decomposing the request (and usually
 * specifically the URI path), and typically will be injected by the application.
 *
 * Requests are considered immutable; all methods that might change state are
 * implemented such that they retain the internal state of the current
 * message and return a new instance that contains the changed state.
 */
class ServerRequest extends Request implements ServerRequestInterface
{
    /**
     * @var array
     */
    private $attributes = [];

    /**
     * @var array
     */
    private $cookieParams = [];

    /**
     * @var null|array|object
     */
    private $parsedBody;

    /**
     * @var array
     */
    private $queryParams = [];

    /**
     * @var array
     */
    private $serverParams;

    /**
     * @var array
     */
    private $uploadedFiles = [];

    /**
     * @param string                               $method       HTTP method
     * @param string|UriInterface                  $uri          URI
     * @param array                                $headers      Request headers
     * @param string|null|resource|StreamInterface $body         Request body
     * @param string                               $version      Protocol version
     * @param array                                $serverParams Typically the $_SERVER superglobal
     */
    public function __construct(
        $method,
        $uri,
        array $headers = [],
        $body = null,
        $version = '1.1',
        array $serverParams = []
    ) {
        $this->serverParams = $serverParams;

        parent::__construct($method, $uri, $headers, $body, $version);
    }

    /**
     * Return an UploadedFile instance array.
     *
     * @param array $files A array which respect $_FILES structure
     * @throws InvalidArgumentException for unrecognized values
     * @return array
     */
    public static function normalizeFiles(array $files)
    {
        $normalized = [];

        foreach ($files as $key => $value) {
            if ($value instanceof UploadedFileInterface) {
                $normalized[$key] = $value;
            } elseif (is_array($value) && isset($value['tmp_name'])) {
                $normalized[$key] = self::createUploadedFileFromSpec($value);
            } elseif (is_array($value)) {
                $normalized[$key] = self::normalizeFiles($value);
                continue;
            } else {
                throw new InvalidArgumentException('Invalid value in files specification');
            }
        }

        return $normalized;
    }

    /**
     * Create and return an UploadedFile instance from a $_FILES specification.
     *
     * If the specification represents an array of values, this method will
     * delegate to normalizeNestedFileSpec() and return that return value.
     *
     * @param array $value $_FILES struct
     * @return array|UploadedFileInterface
     */
    private static function createUploadedFileFromSpec(array $value)
    {
        if (is_array($value['tmp_name'])) {
            return self::normalizeNestedFileSpec($value);
        }

        return new UploadedFile(
            $value['tmp_name'],
            (int) $value['size'],
            (int) $value['error'],
            $value['name'],
            $value['type']
        );
    }

    /**
     * Normalize an array of file specifications.
     *
     * Loops through all nested files and returns a normalized array of
     * UploadedFileInterface instances.
     *
     * @param array $files
     * @return UploadedFileInterface[]
     */
    private static function normalizeNestedFileSpec(array $files = [])
    {
        $normalizedFiles = [];

        foreach (array_keys($files['tmp_name']) as $key) {
            $spec = [
                'tmp_name' => $files['tmp_name'][$key],
                'size'     => $files['size'][$key],
                'error'    => $files['error'][$key],
                'name'     => $files['name'][$key],
                'type'     => $files['type'][$key],
            ];
            $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
        }

        return $normalizedFiles;
    }

    /**
     * Return a ServerRequest populated with superglobals:
     * $_GET
     * $_POST
     * $_COOKIE
     * $_FILES
     * $_SERVER
     *
     * @return ServerRequestInterface
     */
    public static function fromGlobals()
    {
        $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
        $headers = function_exists('getallheaders') ? getallheaders() : [];
        $uri = self::getUriFromGlobals();
        $body = new LazyOpenStream('php://input', 'r+');
        $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';

        $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);

        return $serverRequest
            ->withCookieParams($_COOKIE)
            ->withQueryParams($_GET)
            ->withParsedBody($_POST)
            ->withUploadedFiles(self::normalizeFiles($_FILES));
    }

    /**
     * Get a Uri populated with values from $_SERVER.
     *
     * @return UriInterface
     */
    public static function getUriFromGlobals() {
        $uri = new Uri('');

        if (isset($_SERVER['HTTPS'])) {
            $uri = $uri->withScheme($_SERVER['HTTPS'] == 'on' ? 'https' : 'http');
        }

        if (isset($_SERVER['HTTP_HOST'])) {
            $uri = $uri->withHost($_SERVER['HTTP_HOST']);
        } elseif (isset($_SERVER['SERVER_NAME'])) {
            $uri = $uri->withHost($_SERVER['SERVER_NAME']);
        }

        if (isset($_SERVER['SERVER_PORT'])) {
            $uri = $uri->withPort($_SERVER['SERVER_PORT']);
        }

        if (isset($_SERVER['REQUEST_URI'])) {
            $uri = $uri->withPath(current(explode('?', $_SERVER['REQUEST_URI'])));
        }

        if (isset($_SERVER['QUERY_STRING'])) {
            $uri = $uri->withQuery($_SERVER['QUERY_STRING']);
        }

        return $uri;
    }


    /**
     * {@inheritdoc}
     */
    public function getServerParams()
    {
        return $this->serverParams;
    }

    /**
     * {@inheritdoc}
     */
    public function getUploadedFiles()
    {
        return $this->uploadedFiles;
    }

    /**
     * {@inheritdoc}
     */
    public function withUploadedFiles(array $uploadedFiles)
    {
        $new = clone $this;
        $new->uploadedFiles = $uploadedFiles;

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function getCookieParams()
    {
        return $this->cookieParams;
    }

    /**
     * {@inheritdoc}
     */
    public function withCookieParams(array $cookies)
    {
        $new = clone $this;
        $new->cookieParams = $cookies;

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function getQueryParams()
    {
        return $this->queryParams;
    }

    /**
     * {@inheritdoc}
     */
    public function withQueryParams(array $query)
    {
        $new = clone $this;
        $new->queryParams = $query;

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function getParsedBody()
    {
        return $this->parsedBody;
    }

    /**
     * {@inheritdoc}
     */
    public function withParsedBody($data)
    {
        $new = clone $this;
        $new->parsedBody = $data;

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function getAttributes()
    {
        return $this->attributes;
    }

    /**
     * {@inheritdoc}
     */
    public function getAttribute($attribute, $default = null)
    {
        if (false === array_key_exists($attribute, $this->attributes)) {
            return $default;
        }

        return $this->attributes[$attribute];
    }

    /**
     * {@inheritdoc}
     */
    public function withAttribute($attribute, $value)
    {
        $new = clone $this;
        $new->attributes[$attribute] = $value;

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function withoutAttribute($attribute)
    {
        if (false === array_key_exists($attribute, $this->attributes)) {
            return $this;
        }

        $new = clone $this;
        unset($new->attributes[$attribute]);

        return $new;
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * PHP stream implementation.
 *
 * @var $stream
 */
class Stream implements StreamInterface
{
    private $stream;
    private $size;
    private $seekable;
    private $readable;
    private $writable;
    private $uri;
    private $customMetadata;

    /** @var array Hash of readable and writable stream types */
    private static $readWriteHash = [
        'read' => [
            'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
            'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
            'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
            'x+t' => true, 'c+t' => true, 'a+' => true
        ],
        'write' => [
            'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
            'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
            'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
            'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
        ]
    ];

    /**
     * This constructor accepts an associative array of options.
     *
     * - size: (int) If a read stream would otherwise have an indeterminate
     *   size, but the size is known due to foreknowledge, then you can
     *   provide that size, in bytes.
     * - metadata: (array) Any additional metadata to return when the metadata
     *   of the stream is accessed.
     *
     * @param resource $stream  Stream resource to wrap.
     * @param array    $options Associative array of options.
     *
     * @throws \InvalidArgumentException if the stream is not a stream resource
     */
    public function __construct($stream, $options = [])
    {
        if (!is_resource($stream)) {
            throw new \InvalidArgumentException('Stream must be a resource');
        }

        if (isset($options['size'])) {
            $this->size = $options['size'];
        }

        $this->customMetadata = isset($options['metadata'])
            ? $options['metadata']
            : [];

        $this->stream = $stream;
        $meta = stream_get_meta_data($this->stream);
        $this->seekable = $meta['seekable'];
        $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
        $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
        $this->uri = $this->getMetadata('uri');
    }

    public function __get($name)
    {
        if ($name == 'stream') {
            throw new \RuntimeException('The stream is detached');
        }

        throw new \BadMethodCallException('No value for ' . $name);
    }

    /**
     * Closes the stream when the destructed
     */
    public function __destruct()
    {
        $this->close();
    }

    public function __toString()
    {
        try {
            $this->seek(0);
            return (string) stream_get_contents($this->stream);
        } catch (\Exception $e) {
            return '';
        }
    }

    public function getContents()
    {
        $contents = stream_get_contents($this->stream);

        if ($contents === false) {
            throw new \RuntimeException('Unable to read stream contents');
        }

        return $contents;
    }

    public function close()
    {
        if (isset($this->stream)) {
            if (is_resource($this->stream)) {
                fclose($this->stream);
            }
            $this->detach();
        }
    }

    public function detach()
    {
        if (!isset($this->stream)) {
            return null;
        }

        $result = $this->stream;
        unset($this->stream);
        $this->size = $this->uri = null;
        $this->readable = $this->writable = $this->seekable = false;

        return $result;
    }

    public function getSize()
    {
        if ($this->size !== null) {
            return $this->size;
        }

        if (!isset($this->stream)) {
            return null;
        }

        // Clear the stat cache if the stream has a URI
        if ($this->uri) {
            clearstatcache(true, $this->uri);
        }

        $stats = fstat($this->stream);
        if (isset($stats['size'])) {
            $this->size = $stats['size'];
            return $this->size;
        }

        return null;
    }

    public function isReadable()
    {
        return $this->readable;
    }

    public function isWritable()
    {
        return $this->writable;
    }

    public function isSeekable()
    {
        return $this->seekable;
    }

    public function eof()
    {
        return !$this->stream || feof($this->stream);
    }

    public function tell()
    {
        $result = ftell($this->stream);

        if ($result === false) {
            throw new \RuntimeException('Unable to determine stream position');
        }

        return $result;
    }

    public function rewind()
    {
        $this->seek(0);
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        if (!$this->seekable) {
            throw new \RuntimeException('Stream is not seekable');
        } elseif (fseek($this->stream, $offset, $whence) === -1) {
            throw new \RuntimeException('Unable to seek to stream position '
                . $offset . ' with whence ' . var_export($whence, true));
        }
    }

    public function read($length)
    {
        if (!$this->readable) {
            throw new \RuntimeException('Cannot read from non-readable stream');
        }

        return fread($this->stream, $length);
    }

    public function write($string)
    {
        if (!$this->writable) {
            throw new \RuntimeException('Cannot write to a non-writable stream');
        }

        // We can't know the size after writing anything
        $this->size = null;
        $result = fwrite($this->stream, $string);

        if ($result === false) {
            throw new \RuntimeException('Unable to write to stream');
        }

        return $result;
    }

    public function getMetadata($key = null)
    {
        if (!isset($this->stream)) {
            return $key ? null : [];
        } elseif (!$key) {
            return $this->customMetadata + stream_get_meta_data($this->stream);
        } elseif (isset($this->customMetadata[$key])) {
            return $this->customMetadata[$key];
        }

        $meta = stream_get_meta_data($this->stream);

        return isset($meta[$key]) ? $meta[$key] : null;
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Stream decorator trait
 * @property StreamInterface stream
 */
trait StreamDecoratorTrait
{
    /**
     * @param StreamInterface $stream Stream to decorate
     */
    public function __construct(StreamInterface $stream)
    {
        $this->stream = $stream;
    }

    /**
     * Magic method used to create a new stream if streams are not added in
     * the constructor of a decorator (e.g., LazyOpenStream).
     *
     * @param string $name Name of the property (allows "stream" only).
     *
     * @return StreamInterface
     */
    public function __get($name)
    {
        if ($name == 'stream') {
            $this->stream = $this->createStream();
            return $this->stream;
        }

        throw new \UnexpectedValueException("$name not found on class");
    }

    public function __toString()
    {
        try {
            if ($this->isSeekable()) {
                $this->seek(0);
            }
            return $this->getContents();
        } catch (\Exception $e) {
            // Really, PHP? https://bugs.php.net/bug.php?id=53648
            trigger_error('StreamDecorator::__toString exception: '
                . (string) $e, E_USER_ERROR);
            return '';
        }
    }

    public function getContents()
    {
        return copy_to_string($this);
    }

    /**
     * Allow decorators to implement custom methods
     *
     * @param string $method Missing method name
     * @param array  $args   Method arguments
     *
     * @return mixed
     */
    public function __call($method, array $args)
    {
        $result = call_user_func_array([$this->stream, $method], $args);

        // Always return the wrapped object if the result is a return $this
        return $result === $this->stream ? $this : $result;
    }

    public function close()
    {
        $this->stream->close();
    }

    public function getMetadata($key = null)
    {
        return $this->stream->getMetadata($key);
    }

    public function detach()
    {
        return $this->stream->detach();
    }

    public function getSize()
    {
        return $this->stream->getSize();
    }

    public function eof()
    {
        return $this->stream->eof();
    }

    public function tell()
    {
        return $this->stream->tell();
    }

    public function isReadable()
    {
        return $this->stream->isReadable();
    }

    public function isWritable()
    {
        return $this->stream->isWritable();
    }

    public function isSeekable()
    {
        return $this->stream->isSeekable();
    }

    public function rewind()
    {
        $this->seek(0);
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        $this->stream->seek($offset, $whence);
    }

    public function read($length)
    {
        return $this->stream->read($length);
    }

    public function write($string)
    {
        return $this->stream->write($string);
    }

    /**
     * Implement in subclasses to dynamically create streams when requested.
     *
     * @return StreamInterface
     * @throws \BadMethodCallException
     */
    protected function createStream()
    {
        throw new \BadMethodCallException('Not implemented');
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Converts Guzzle streams into PHP stream resources.
 */
class StreamWrapper
{
    /** @var resource */
    public $context;

    /** @var StreamInterface */
    private $stream;

    /** @var string r, r+, or w */
    private $mode;

    /**
     * Returns a resource representing the stream.
     *
     * @param StreamInterface $stream The stream to get a resource for
     *
     * @return resource
     * @throws \InvalidArgumentException if stream is not readable or writable
     */
    public static function getResource(StreamInterface $stream)
    {
        self::register();

        if ($stream->isReadable()) {
            $mode = $stream->isWritable() ? 'r+' : 'r';
        } elseif ($stream->isWritable()) {
            $mode = 'w';
        } else {
            throw new \InvalidArgumentException('The stream must be readable, '
                . 'writable, or both.');
        }

        return fopen('guzzle://stream', $mode, null, stream_context_create([
            'guzzle' => ['stream' => $stream]
        ]));
    }

    /**
     * Registers the stream wrapper if needed
     */
    public static function register()
    {
        if (!in_array('guzzle', stream_get_wrappers())) {
            stream_wrapper_register('guzzle', __CLASS__);
        }
    }

    public function stream_open($path, $mode, $options, &$opened_path)
    {
        $options = stream_context_get_options($this->context);

        if (!isset($options['guzzle']['stream'])) {
            return false;
        }

        $this->mode = $mode;
        $this->stream = $options['guzzle']['stream'];

        return true;
    }

    public function stream_read($count)
    {
        return $this->stream->read($count);
    }

    public function stream_write($data)
    {
        return (int) $this->stream->write($data);
    }

    public function stream_tell()
    {
        return $this->stream->tell();
    }

    public function stream_eof()
    {
        return $this->stream->eof();
    }

    public function stream_seek($offset, $whence)
    {
        $this->stream->seek($offset, $whence);

        return true;
    }

    public function stream_stat()
    {
        static $modeMap = [
            'r'  => 33060,
            'r+' => 33206,
            'w'  => 33188
        ];

        return [
            'dev'     => 0,
            'ino'     => 0,
            'mode'    => $modeMap[$this->mode],
            'nlink'   => 0,
            'uid'     => 0,
            'gid'     => 0,
            'rdev'    => 0,
            'size'    => $this->stream->getSize() ?: 0,
            'atime'   => 0,
            'mtime'   => 0,
            'ctime'   => 0,
            'blksize' => 0,
            'blocks'  => 0
        ];
    }
}
<?php
namespace GuzzleHttp\Psr7;

use InvalidArgumentException;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
use RuntimeException;

class UploadedFile implements UploadedFileInterface
{
    /**
     * @var int[]
     */
    private static $errors = [
        UPLOAD_ERR_OK,
        UPLOAD_ERR_INI_SIZE,
        UPLOAD_ERR_FORM_SIZE,
        UPLOAD_ERR_PARTIAL,
        UPLOAD_ERR_NO_FILE,
        UPLOAD_ERR_NO_TMP_DIR,
        UPLOAD_ERR_CANT_WRITE,
        UPLOAD_ERR_EXTENSION,
    ];

    /**
     * @var string
     */
    private $clientFilename;

    /**
     * @var string
     */
    private $clientMediaType;

    /**
     * @var int
     */
    private $error;

    /**
     * @var null|string
     */
    private $file;

    /**
     * @var bool
     */
    private $moved = false;

    /**
     * @var int
     */
    private $size;

    /**
     * @var StreamInterface|null
     */
    private $stream;

    /**
     * @param StreamInterface|string|resource $streamOrFile
     * @param int $size
     * @param int $errorStatus
     * @param string|null $clientFilename
     * @param string|null $clientMediaType
     */
    public function __construct(
        $streamOrFile,
        $size,
        $errorStatus,
        $clientFilename = null,
        $clientMediaType = null
    ) {
        $this->setError($errorStatus);
        $this->setSize($size);
        $this->setClientFilename($clientFilename);
        $this->setClientMediaType($clientMediaType);

        if ($this->isOk()) {
            $this->setStreamOrFile($streamOrFile);
        }
    }

    /**
     * Depending on the value set file or stream variable
     *
     * @param mixed $streamOrFile
     * @throws InvalidArgumentException
     */
    private function setStreamOrFile($streamOrFile)
    {
        if (is_string($streamOrFile)) {
            $this->file = $streamOrFile;
        } elseif (is_resource($streamOrFile)) {
            $this->stream = new Stream($streamOrFile);
        } elseif ($streamOrFile instanceof StreamInterface) {
            $this->stream = $streamOrFile;
        } else {
            throw new InvalidArgumentException(
                'Invalid stream or file provided for UploadedFile'
            );
        }
    }

    /**
     * @param int $error
     * @throws InvalidArgumentException
     */
    private function setError($error)
    {
        if (false === is_int($error)) {
            throw new InvalidArgumentException(
                'Upload file error status must be an integer'
            );
        }

        if (false === in_array($error, UploadedFile::$errors)) {
            throw new InvalidArgumentException(
                'Invalid error status for UploadedFile'
            );
        }

        $this->error = $error;
    }

    /**
     * @param int $size
     * @throws InvalidArgumentException
     */
    private function setSize($size)
    {
        if (false === is_int($size)) {
            throw new InvalidArgumentException(
                'Upload file size must be an integer'
            );
        }

        $this->size = $size;
    }

    /**
     * @param mixed $param
     * @return boolean
     */
    private function isStringOrNull($param)
    {
        return in_array(gettype($param), ['string', 'NULL']);
    }

    /**
     * @param mixed $param
     * @return boolean
     */
    private function isStringNotEmpty($param)
    {
        return is_string($param) && false === empty($param);
    }

    /**
     * @param string|null $clientFilename
     * @throws InvalidArgumentException
     */
    private function setClientFilename($clientFilename)
    {
        if (false === $this->isStringOrNull($clientFilename)) {
            throw new InvalidArgumentException(
                'Upload file client filename must be a string or null'
            );
        }

        $this->clientFilename = $clientFilename;
    }

    /**
     * @param string|null $clientMediaType
     * @throws InvalidArgumentException
     */
    private function setClientMediaType($clientMediaType)
    {
        if (false === $this->isStringOrNull($clientMediaType)) {
            throw new InvalidArgumentException(
                'Upload file client media type must be a string or null'
            );
        }

        $this->clientMediaType = $clientMediaType;
    }

    /**
     * Return true if there is no upload error
     *
     * @return boolean
     */
    private function isOk()
    {
        return $this->error === UPLOAD_ERR_OK;
    }

    /**
     * @return boolean
     */
    public function isMoved()
    {
        return $this->moved;
    }

    /**
     * @throws RuntimeException if is moved or not ok
     */
    private function validateActive()
    {
        if (false === $this->isOk()) {
            throw new RuntimeException('Cannot retrieve stream due to upload error');
        }

        if ($this->isMoved()) {
            throw new RuntimeException('Cannot retrieve stream after it has already been moved');
        }
    }

    /**
     * {@inheritdoc}
     * @throws RuntimeException if the upload was not successful.
     */
    public function getStream()
    {
        $this->validateActive();

        if ($this->stream instanceof StreamInterface) {
            return $this->stream;
        }

        return new LazyOpenStream($this->file, 'r+');
    }

    /**
     * {@inheritdoc}
     *
     * @see http://php.net/is_uploaded_file
     * @see http://php.net/move_uploaded_file
     * @param string $targetPath Path to which to move the uploaded file.
     * @throws RuntimeException if the upload was not successful.
     * @throws InvalidArgumentException if the $path specified is invalid.
     * @throws RuntimeException on any error during the move operation, or on
     *     the second or subsequent call to the method.
     */
    public function moveTo($targetPath)
    {
        $this->validateActive();

        if (false === $this->isStringNotEmpty($targetPath)) {
            throw new InvalidArgumentException(
                'Invalid path provided for move operation; must be a non-empty string'
            );
        }

        if ($this->file) {
            $this->moved = php_sapi_name() == 'cli'
                ? rename($this->file, $targetPath)
                : move_uploaded_file($this->file, $targetPath);
        } else {
            copy_to_stream(
                $this->getStream(),
                new LazyOpenStream($targetPath, 'w')
            );

            $this->moved = true;
        }

        if (false === $this->moved) {
            throw new RuntimeException(
                sprintf('Uploaded file could not be moved to %s', $targetPath)
            );
        }
    }

    /**
     * {@inheritdoc}
     *
     * @return int|null The file size in bytes or null if unknown.
     */
    public function getSize()
    {
        return $this->size;
    }

    /**
     * {@inheritdoc}
     *
     * @see http://php.net/manual/en/features.file-upload.errors.php
     * @return int One of PHP's UPLOAD_ERR_XXX constants.
     */
    public function getError()
    {
        return $this->error;
    }

    /**
     * {@inheritdoc}
     *
     * @return string|null The filename sent by the client or null if none
     *     was provided.
     */
    public function getClientFilename()
    {
        return $this->clientFilename;
    }

    /**
     * {@inheritdoc}
     */
    public function getClientMediaType()
    {
        return $this->clientMediaType;
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\UriInterface;

/**
 * PSR-7 URI implementation.
 *
 * @author Michael Dowling
 * @author Tobias Schultze
 * @author Matthew Weier O'Phinney
 */
class Uri implements UriInterface
{
    private static $schemes = [
        'http'  => 80,
        'https' => 443,
    ];

    private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
    private static $charSubDelims = '!\$&\'\(\)\*\+,;=';
    private static $replaceQuery = ['=' => '%3D', '&' => '%26'];

    /** @var string Uri scheme. */
    private $scheme = '';

    /** @var string Uri user info. */
    private $userInfo = '';

    /** @var string Uri host. */
    private $host = '';

    /** @var int|null Uri port. */
    private $port;

    /** @var string Uri path. */
    private $path = '';

    /** @var string Uri query string. */
    private $query = '';

    /** @var string Uri fragment. */
    private $fragment = '';

    /**
     * @param string $uri URI to parse
     */
    public function __construct($uri = '')
    {
        if ($uri != '') {
            $parts = parse_url($uri);
            if ($parts === false) {
                throw new \InvalidArgumentException("Unable to parse URI: $uri");
            }
            $this->applyParts($parts);
        }
    }

    public function __toString()
    {
        return self::createUriString(
            $this->scheme,
            $this->getAuthority(),
            $this->path,
            $this->query,
            $this->fragment
        );
    }

    /**
     * Removes dot segments from a path and returns the new path.
     *
     * @param string $path
     *
     * @return string
     * @link http://tools.ietf.org/html/rfc3986#section-5.2.4
     */
    public static function removeDotSegments($path)
    {
        static $noopPaths = ['' => true, '/' => true, '*' => true];
        static $ignoreSegments = ['.' => true, '..' => true];

        if (isset($noopPaths[$path])) {
            return $path;
        }

        $results = [];
        $segments = explode('/', $path);
        foreach ($segments as $segment) {
            if ($segment === '..') {
                array_pop($results);
            } elseif (!isset($ignoreSegments[$segment])) {
                $results[] = $segment;
            }
        }

        $newPath = implode('/', $results);
        // Add the leading slash if necessary
        if (substr($path, 0, 1) === '/' &&
            substr($newPath, 0, 1) !== '/'
        ) {
            $newPath = '/' . $newPath;
        }

        // Add the trailing slash if necessary
        if ($newPath !== '/' && isset($ignoreSegments[end($segments)])) {
            $newPath .= '/';
        }

        return $newPath;
    }

    /**
     * Resolve a base URI with a relative URI and return a new URI.
     *
     * @param UriInterface        $base Base URI
     * @param string|UriInterface $rel  Relative URI
     *
     * @return UriInterface
     * @link http://tools.ietf.org/html/rfc3986#section-5.2
     */
    public static function resolve(UriInterface $base, $rel)
    {
        if (!($rel instanceof UriInterface)) {
            $rel = new self($rel);
        }

        if ((string) $rel === '') {
            // we can simply return the same base URI instance for this same-document reference
            return $base;
        }

        if ($rel->getScheme() != '') {
            return $rel->withPath(self::removeDotSegments($rel->getPath()));
        }

        if ($rel->getAuthority() != '') {
            $targetAuthority = $rel->getAuthority();
            $targetPath = self::removeDotSegments($rel->getPath());
            $targetQuery = $rel->getQuery();
        } else {
            $targetAuthority = $base->getAuthority();
            if ($rel->getPath() === '') {
                $targetPath = $base->getPath();
                $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
            } else {
                if ($rel->getPath()[0] === '/') {
                    $targetPath = $rel->getPath();
                } else {
                    if ($targetAuthority != '' && $base->getPath() === '') {
                        $targetPath = '/' . $rel->getPath();
                    } else {
                        $lastSlashPos = strrpos($base->getPath(), '/');
                        if ($lastSlashPos === false) {
                            $targetPath = $rel->getPath();
                        } else {
                            $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath();
                        }
                    }
                }
                $targetPath = self::removeDotSegments($targetPath);
                $targetQuery = $rel->getQuery();
            }
        }

        return new self(self::createUriString(
            $base->getScheme(),
            $targetAuthority,
            $targetPath,
            $targetQuery,
            $rel->getFragment()
        ));
    }

    /**
     * Create a new URI with a specific query string value removed.
     *
     * Any existing query string values that exactly match the provided key are
     * removed.
     *
     * @param UriInterface $uri URI to use as a base.
     * @param string       $key Query string key to remove.
     *
     * @return UriInterface
     */
    public static function withoutQueryValue(UriInterface $uri, $key)
    {
        $current = $uri->getQuery();
        if ($current == '') {
            return $uri;
        }

        $decodedKey = rawurldecode($key);
        $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
            return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
        });

        return $uri->withQuery(implode('&', $result));
    }

    /**
     * Create a new URI with a specific query string value.
     *
     * Any existing query string values that exactly match the provided key are
     * removed and replaced with the given key value pair.
     *
     * A value of null will set the query string key without a value, e.g. "key"
     * instead of "key=value".
     *
     * @param UriInterface $uri   URI to use as a base.
     * @param string       $key   Key to set.
     * @param string|null  $value Value to set
     *
     * @return UriInterface
     */
    public static function withQueryValue(UriInterface $uri, $key, $value)
    {
        $current = $uri->getQuery();

        if ($current == '') {
            $result = [];
        } else {
            $decodedKey = rawurldecode($key);
            $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
                return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
            });
        }

        // Query string separators ("=", "&") within the key or value need to be encoded
        // (while preventing double-encoding) before setting the query string. All other
        // chars that need percent-encoding will be encoded by withQuery().
        $key = strtr($key, self::$replaceQuery);

        if ($value !== null) {
            $result[] = $key . '=' . strtr($value, self::$replaceQuery);
        } else {
            $result[] = $key;
        }

        return $uri->withQuery(implode('&', $result));
    }

    /**
     * Create a URI from a hash of parse_url parts.
     *
     * @param array $parts
     *
     * @return self
     */
    public static function fromParts(array $parts)
    {
        $uri = new self();
        $uri->applyParts($parts);
        return $uri;
    }

    public function getScheme()
    {
        return $this->scheme;
    }

    public function getAuthority()
    {
        if ($this->host == '') {
            return '';
        }

        $authority = $this->host;
        if ($this->userInfo != '') {
            $authority = $this->userInfo . '@' . $authority;
        }

        if ($this->port !== null) {
            $authority .= ':' . $this->port;
        }

        return $authority;
    }

    public function getUserInfo()
    {
        return $this->userInfo;
    }

    public function getHost()
    {
        return $this->host;
    }

    public function getPort()
    {
        return $this->port;
    }

    public function getPath()
    {
        return $this->path;
    }

    public function getQuery()
    {
        return $this->query;
    }

    public function getFragment()
    {
        return $this->fragment;
    }

    public function withScheme($scheme)
    {
        $scheme = $this->filterScheme($scheme);

        if ($this->scheme === $scheme) {
            return $this;
        }

        $new = clone $this;
        $new->scheme = $scheme;
        $new->port = $new->filterPort($new->port);
        return $new;
    }

    public function withUserInfo($user, $password = null)
    {
        $info = $user;
        if ($password != '') {
            $info .= ':' . $password;
        }

        if ($this->userInfo === $info) {
            return $this;
        }

        $new = clone $this;
        $new->userInfo = $info;
        return $new;
    }

    public function withHost($host)
    {
        $host = $this->filterHost($host);

        if ($this->host === $host) {
            return $this;
        }

        $new = clone $this;
        $new->host = $host;
        return $new;
    }

    public function withPort($port)
    {
        $port = $this->filterPort($port);

        if ($this->port === $port) {
            return $this;
        }

        $new = clone $this;
        $new->port = $port;
        return $new;
    }

    public function withPath($path)
    {
        $path = $this->filterPath($path);

        if ($this->path === $path) {
            return $this;
        }

        $new = clone $this;
        $new->path = $path;
        return $new;
    }

    public function withQuery($query)
    {
        $query = $this->filterQueryAndFragment($query);

        if ($this->query === $query) {
            return $this;
        }

        $new = clone $this;
        $new->query = $query;
        return $new;
    }

    public function withFragment($fragment)
    {
        $fragment = $this->filterQueryAndFragment($fragment);

        if ($this->fragment === $fragment) {
            return $this;
        }

        $new = clone $this;
        $new->fragment = $fragment;
        return $new;
    }

    /**
     * Apply parse_url parts to a URI.
     *
     * @param array $parts Array of parse_url parts to apply.
     */
    private function applyParts(array $parts)
    {
        $this->scheme = isset($parts['scheme'])
            ? $this->filterScheme($parts['scheme'])
            : '';
        $this->userInfo = isset($parts['user']) ? $parts['user'] : '';
        $this->host = isset($parts['host'])
            ? $this->filterHost($parts['host'])
            : '';
        $this->port = isset($parts['port'])
            ? $this->filterPort($parts['port'])
            : null;
        $this->path = isset($parts['path'])
            ? $this->filterPath($parts['path'])
            : '';
        $this->query = isset($parts['query'])
            ? $this->filterQueryAndFragment($parts['query'])
            : '';
        $this->fragment = isset($parts['fragment'])
            ? $this->filterQueryAndFragment($parts['fragment'])
            : '';
        if (isset($parts['pass'])) {
            $this->userInfo .= ':' . $parts['pass'];
        }
    }

    /**
     * Create a URI string from its various parts
     *
     * @param string $scheme
     * @param string $authority
     * @param string $path
     * @param string $query
     * @param string $fragment
     * @return string
     */
    private static function createUriString($scheme, $authority, $path, $query, $fragment)
    {
        $uri = '';

        if ($scheme != '') {
            $uri .= $scheme . ':';
        }

        if ($authority != '') {
            $uri .= '//' . $authority;
        }

        if ($path != '') {
            if ($path[0] !== '/') {
                if ($authority != '') {
                    // If the path is rootless and an authority is present, the path MUST be prefixed by "/"
                    $path = '/' . $path;
                }
            } elseif (isset($path[1]) && $path[1] === '/') {
                if ($authority == '') {
                    // If the path is starting with more than one "/" and no authority is present, the
                    // starting slashes MUST be reduced to one.
                    $path = '/' . ltrim($path, '/');
                }
            }

            $uri .= $path;
        }

        if ($query != '') {
            $uri .= '?' . $query;
        }

        if ($fragment != '') {
            $uri .= '#' . $fragment;
        }

        return $uri;
    }

    /**
     * Is a given port non-standard for the current scheme?
     *
     * @param string $scheme
     * @param int    $port
     *
     * @return bool
     */
    private static function isNonStandardPort($scheme, $port)
    {
        return !isset(self::$schemes[$scheme]) || $port !== self::$schemes[$scheme];
    }

    /**
     * @param string $scheme
     *
     * @return string
     *
     * @throws \InvalidArgumentException If the scheme is invalid.
     */
    private function filterScheme($scheme)
    {
        if (!is_string($scheme)) {
            throw new \InvalidArgumentException('Scheme must be a string');
        }

        return strtolower($scheme);
    }

    /**
     * @param string $host
     *
     * @return string
     *
     * @throws \InvalidArgumentException If the host is invalid.
     */
    private function filterHost($host)
    {
        if (!is_string($host)) {
            throw new \InvalidArgumentException('Host must be a string');
        }

        return strtolower($host);
    }

    /**
     * @param int|null $port
     *
     * @return int|null
     *
     * @throws \InvalidArgumentException If the port is invalid.
     */
    private function filterPort($port)
    {
        if ($port === null) {
            return null;
        }

        $port = (int) $port;
        if (1 > $port || 0xffff < $port) {
            throw new \InvalidArgumentException(
                sprintf('Invalid port: %d. Must be between 1 and 65535', $port)
            );
        }

        return self::isNonStandardPort($this->scheme, $port) ? $port : null;
    }

    /**
     * Filters the path of a URI
     *
     * @param string $path
     *
     * @return string
     *
     * @throws \InvalidArgumentException If the path is invalid.
     */
    private function filterPath($path)
    {
        if (!is_string($path)) {
            throw new \InvalidArgumentException('Path must be a string');
        }

        return preg_replace_callback(
            '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
            [$this, 'rawurlencodeMatchZero'],
            $path
        );
    }

    /**
     * Filters the query string or fragment of a URI.
     *
     * @param string $str
     *
     * @return string
     *
     * @throws \InvalidArgumentException If the query or fragment is invalid.
     */
    private function filterQueryAndFragment($str)
    {
        if (!is_string($str)) {
            throw new \InvalidArgumentException('Query and fragment must be a string');
        }

        return preg_replace_callback(
            '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
            [$this, 'rawurlencodeMatchZero'],
            $str
        );
    }

    private function rawurlencodeMatchZero(array $match)
    {
        return rawurlencode($match[0]);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\AppendStream;
use GuzzleHttp\Psr7;

class AppendStreamTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Each stream must be readable
     */
    public function testValidatesStreamsAreReadable()
    {
        $a = new AppendStream();
        $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
            ->setMethods(['isReadable'])
            ->getMockForAbstractClass();
        $s->expects($this->once())
            ->method('isReadable')
            ->will($this->returnValue(false));
        $a->addStream($s);
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage The AppendStream can only seek with SEEK_SET
     */
    public function testValidatesSeekType()
    {
        $a = new AppendStream();
        $a->seek(100, SEEK_CUR);
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Unable to seek stream 0 of the AppendStream
     */
    public function testTriesToRewindOnSeek()
    {
        $a = new AppendStream();
        $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
            ->setMethods(['isReadable', 'rewind', 'isSeekable'])
            ->getMockForAbstractClass();
        $s->expects($this->once())
            ->method('isReadable')
            ->will($this->returnValue(true));
        $s->expects($this->once())
            ->method('isSeekable')
            ->will($this->returnValue(true));
        $s->expects($this->once())
            ->method('rewind')
            ->will($this->throwException(new \RuntimeException()));
        $a->addStream($s);
        $a->seek(10);
    }

    public function testSeeksToPositionByReading()
    {
        $a = new AppendStream([
            Psr7\stream_for('foo'),
            Psr7\stream_for('bar'),
            Psr7\stream_for('baz'),
        ]);

        $a->seek(3);
        $this->assertEquals(3, $a->tell());
        $this->assertEquals('bar', $a->read(3));

        $a->seek(6);
        $this->assertEquals(6, $a->tell());
        $this->assertEquals('baz', $a->read(3));
    }

    public function testDetachesEachStream()
    {
        $s1 = Psr7\stream_for('foo');
        $s2 = Psr7\stream_for('bar');
        $a = new AppendStream([$s1, $s2]);
        $this->assertSame('foobar', (string) $a);
        $a->detach();
        $this->assertSame('', (string) $a);
        $this->assertSame(0, $a->getSize());
    }

    public function testClosesEachStream()
    {
        $s1 = Psr7\stream_for('foo');
        $a = new AppendStream([$s1]);
        $a->close();
        $this->assertSame('', (string) $a);
    }

    /**
     * @expectedExceptionMessage Cannot write to an AppendStream
     * @expectedException \RuntimeException
     */
    public function testIsNotWritable()
    {
        $a = new AppendStream([Psr7\stream_for('foo')]);
        $this->assertFalse($a->isWritable());
        $this->assertTrue($a->isSeekable());
        $this->assertTrue($a->isReadable());
        $a->write('foo');
    }

    public function testDoesNotNeedStreams()
    {
        $a = new AppendStream();
        $this->assertEquals('', (string) $a);
    }

    public function testCanReadFromMultipleStreams()
    {
        $a = new AppendStream([
            Psr7\stream_for('foo'),
            Psr7\stream_for('bar'),
            Psr7\stream_for('baz'),
        ]);
        $this->assertFalse($a->eof());
        $this->assertSame(0, $a->tell());
        $this->assertEquals('foo', $a->read(3));
        $this->assertEquals('bar', $a->read(3));
        $this->assertEquals('baz', $a->read(3));
        $this->assertSame('', $a->read(1));
        $this->assertTrue($a->eof());
        $this->assertSame(9, $a->tell());
        $this->assertEquals('foobarbaz', (string) $a);
    }

    public function testCanDetermineSizeFromMultipleStreams()
    {
        $a = new AppendStream([
            Psr7\stream_for('foo'),
            Psr7\stream_for('bar')
        ]);
        $this->assertEquals(6, $a->getSize());

        $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
            ->setMethods(['isSeekable', 'isReadable'])
            ->getMockForAbstractClass();
        $s->expects($this->once())
            ->method('isSeekable')
            ->will($this->returnValue(null));
        $s->expects($this->once())
            ->method('isReadable')
            ->will($this->returnValue(true));
        $a->addStream($s);
        $this->assertNull($a->getSize());
    }

    public function testCatchesExceptionsWhenCastingToString()
    {
        $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
            ->setMethods(['isSeekable', 'read', 'isReadable', 'eof'])
            ->getMockForAbstractClass();
        $s->expects($this->once())
            ->method('isSeekable')
            ->will($this->returnValue(true));
        $s->expects($this->once())
            ->method('read')
            ->will($this->throwException(new \RuntimeException('foo')));
        $s->expects($this->once())
            ->method('isReadable')
            ->will($this->returnValue(true));
        $s->expects($this->any())
            ->method('eof')
            ->will($this->returnValue(false));
        $a = new AppendStream([$s]);
        $this->assertFalse($a->eof());
        $this->assertSame('', (string) $a);
    }

    public function testCanDetach()
    {
        $s = new AppendStream();
        $s->detach();
    }

    public function testReturnsEmptyMetadata()
    {
        $s = new AppendStream();
        $this->assertEquals([], $s->getMetadata());
        $this->assertNull($s->getMetadata('foo'));
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

require __DIR__ . '/../vendor/autoload.php';

class HasToString
{
    public function __toString() {
        return 'foo';
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\BufferStream;

class BufferStreamTest extends \PHPUnit_Framework_TestCase
{
    public function testHasMetadata()
    {
        $b = new BufferStream(10);
        $this->assertTrue($b->isReadable());
        $this->assertTrue($b->isWritable());
        $this->assertFalse($b->isSeekable());
        $this->assertEquals(null, $b->getMetadata('foo'));
        $this->assertEquals(10, $b->getMetadata('hwm'));
        $this->assertEquals([], $b->getMetadata());
    }

    public function testRemovesReadDataFromBuffer()
    {
        $b = new BufferStream();
        $this->assertEquals(3, $b->write('foo'));
        $this->assertEquals(3, $b->getSize());
        $this->assertFalse($b->eof());
        $this->assertEquals('foo', $b->read(10));
        $this->assertTrue($b->eof());
        $this->assertEquals('', $b->read(10));
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Cannot determine the position of a BufferStream
     */
    public function testCanCastToStringOrGetContents()
    {
        $b = new BufferStream();
        $b->write('foo');
        $b->write('baz');
        $this->assertEquals('foo', $b->read(3));
        $b->write('bar');
        $this->assertEquals('bazbar', (string) $b);
        $b->tell();
    }

    public function testDetachClearsBuffer()
    {
        $b = new BufferStream();
        $b->write('foo');
        $b->detach();
        $this->assertTrue($b->eof());
        $this->assertEquals(3, $b->write('abc'));
        $this->assertEquals('abc', $b->read(10));
    }

    public function testExceedingHighwaterMarkReturnsFalseButStillBuffers()
    {
        $b = new BufferStream(5);
        $this->assertEquals(3, $b->write('hi '));
        $this->assertFalse($b->write('hello'));
        $this->assertEquals('hi hello', (string) $b);
        $this->assertEquals(4, $b->write('test'));
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\CachingStream;

/**
 * @covers GuzzleHttp\Psr7\CachingStream
 */
class CachingStreamTest extends \PHPUnit_Framework_TestCase
{
    /** @var CachingStream */
    protected $body;
    protected $decorated;

    public function setUp()
    {
        $this->decorated = Psr7\stream_for('testing');
        $this->body = new CachingStream($this->decorated);
    }

    public function tearDown()
    {
        $this->decorated->close();
        $this->body->close();
    }

    public function testUsesRemoteSizeIfPossible()
    {
        $body = Psr7\stream_for('test');
        $caching = new CachingStream($body);
        $this->assertEquals(4, $caching->getSize());
    }

    public function testReadsUntilCachedToByte()
    {
        $this->body->seek(5);
        $this->assertEquals('n', $this->body->read(1));
        $this->body->seek(0);
        $this->assertEquals('t', $this->body->read(1));
    }

    public function testCanSeekNearEndWithSeekEnd()
    {
        $baseStream = Psr7\stream_for(implode('', range('a', 'z')));
        $cached = new CachingStream($baseStream);
        $cached->seek(-1, SEEK_END);
        $this->assertEquals(25, $baseStream->tell());
        $this->assertEquals('z', $cached->read(1));
        $this->assertEquals(26, $cached->getSize());
    }

    public function testCanSeekToEndWithSeekEnd()
    {
        $baseStream = Psr7\stream_for(implode('', range('a', 'z')));
        $cached = new CachingStream($baseStream);
        $cached->seek(0, SEEK_END);
        $this->assertEquals(26, $baseStream->tell());
        $this->assertEquals('', $cached->read(1));
        $this->assertEquals(26, $cached->getSize());
    }

    public function testCanUseSeekEndWithUnknownSize()
    {
        $baseStream = Psr7\stream_for('testing');
        $decorated = Psr7\FnStream::decorate($baseStream, [
            'getSize' => function () { return null; }
        ]);
        $cached = new CachingStream($decorated);
        $cached->seek(-1, SEEK_END);
        $this->assertEquals('g', $cached->read(1));
    }

    public function testRewindUsesSeek()
    {
        $a = Psr7\stream_for('foo');
        $d = $this->getMockBuilder('GuzzleHttp\Psr7\CachingStream')
            ->setMethods(array('seek'))
            ->setConstructorArgs(array($a))
            ->getMock();
        $d->expects($this->once())
            ->method('seek')
            ->with(0)
            ->will($this->returnValue(true));
        $d->seek(0);
    }

    public function testCanSeekToReadBytes()
    {
        $this->assertEquals('te', $this->body->read(2));
        $this->body->seek(0);
        $this->assertEquals('test', $this->body->read(4));
        $this->assertEquals(4, $this->body->tell());
        $this->body->seek(2);
        $this->assertEquals(2, $this->body->tell());
        $this->body->seek(2, SEEK_CUR);
        $this->assertEquals(4, $this->body->tell());
        $this->assertEquals('ing', $this->body->read(3));
    }

    public function testCanSeekToReadBytesWithPartialBodyReturned()
    {
        $stream = fopen('php://temp', 'r+');
        fwrite($stream, 'testing');
        fseek($stream, 0);

        $this->decorated = $this->getMockBuilder('\GuzzleHttp\Psr7\Stream')
            ->setConstructorArgs([$stream])
            ->setMethods(['read'])
            ->getMock();

        $this->decorated->expects($this->exactly(2))
            ->method('read')
            ->willReturnCallback(function($length) use ($stream){
                return fread($stream, 2);
            });

        $this->body = new CachingStream($this->decorated);

        $this->assertEquals(0, $this->body->tell());
        $this->body->seek(4, SEEK_SET);
        $this->assertEquals(4, $this->body->tell());

        $this->body->seek(0);
        $this->assertEquals('test', $this->body->read(4));
    }

    public function testWritesToBufferStream()
    {
        $this->body->read(2);
        $this->body->write('hi');
        $this->body->seek(0);
        $this->assertEquals('tehiing', (string) $this->body);
    }

    public function testSkipsOverwrittenBytes()
    {
        $decorated = Psr7\stream_for(
            implode("\n", array_map(function ($n) {
                return str_pad($n, 4, '0', STR_PAD_LEFT);
            }, range(0, 25)))
        );

        $body = new CachingStream($decorated);

        $this->assertEquals("0000\n", Psr7\readline($body));
        $this->assertEquals("0001\n", Psr7\readline($body));
        // Write over part of the body yet to be read, so skip some bytes
        $this->assertEquals(5, $body->write("TEST\n"));
        $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
        // Read, which skips bytes, then reads
        $this->assertEquals("0003\n", Psr7\readline($body));
        $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
        $this->assertEquals("0004\n", Psr7\readline($body));
        $this->assertEquals("0005\n", Psr7\readline($body));

        // Overwrite part of the cached body (so don't skip any bytes)
        $body->seek(5);
        $this->assertEquals(5, $body->write("ABCD\n"));
        $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
        $this->assertEquals("TEST\n", Psr7\readline($body));
        $this->assertEquals("0003\n", Psr7\readline($body));
        $this->assertEquals("0004\n", Psr7\readline($body));
        $this->assertEquals("0005\n", Psr7\readline($body));
        $this->assertEquals("0006\n", Psr7\readline($body));
        $this->assertEquals(5, $body->write("1234\n"));
        $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));

        // Seek to 0 and ensure the overwritten bit is replaced
        $body->seek(0);
        $this->assertEquals("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", $body->read(50));

        // Ensure that casting it to a string does not include the bit that was overwritten
        $this->assertContains("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", (string) $body);
    }

    public function testClosesBothStreams()
    {
        $s = fopen('php://temp', 'r');
        $a = Psr7\stream_for($s);
        $d = new CachingStream($a);
        $d->close();
        $this->assertFalse(is_resource($s));
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testEnsuresValidWhence()
    {
        $this->body->seek(10, -123456);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\BufferStream;
use GuzzleHttp\Psr7\DroppingStream;

class DroppingStreamTest extends \PHPUnit_Framework_TestCase
{
    public function testBeginsDroppingWhenSizeExceeded()
    {
        $stream = new BufferStream();
        $drop = new DroppingStream($stream, 5);
        $this->assertEquals(3, $drop->write('hel'));
        $this->assertEquals(2, $drop->write('lo'));
        $this->assertEquals(5, $drop->getSize());
        $this->assertEquals('hello', $drop->read(5));
        $this->assertEquals(0, $drop->getSize());
        $drop->write('12345678910');
        $this->assertEquals(5, $stream->getSize());
        $this->assertEquals(5, $drop->getSize());
        $this->assertEquals('12345', (string) $drop);
        $this->assertEquals(0, $drop->getSize());
        $drop->write('hello');
        $this->assertSame(0, $drop->write('test'));
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\FnStream;

/**
 * @covers GuzzleHttp\Psr7\FnStream
 */
class FnStreamTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @expectedException \BadMethodCallException
     * @expectedExceptionMessage seek() is not implemented in the FnStream
     */
    public function testThrowsWhenNotImplemented()
    {
        (new FnStream([]))->seek(1);
    }

    public function testProxiesToFunction()
    {
        $s = new FnStream([
            'read' => function ($len) {
                $this->assertEquals(3, $len);
                return 'foo';
            }
        ]);

        $this->assertEquals('foo', $s->read(3));
    }

    public function testCanCloseOnDestruct()
    {
        $called = false;
        $s = new FnStream([
            'close' => function () use (&$called) {
                $called = true;
            }
        ]);
        unset($s);
        $this->assertTrue($called);
    }

    public function testDoesNotRequireClose()
    {
        $s = new FnStream([]);
        unset($s);
    }

    public function testDecoratesStream()
    {
        $a = Psr7\stream_for('foo');
        $b = FnStream::decorate($a, []);
        $this->assertEquals(3, $b->getSize());
        $this->assertEquals($b->isWritable(), true);
        $this->assertEquals($b->isReadable(), true);
        $this->assertEquals($b->isSeekable(), true);
        $this->assertEquals($b->read(3), 'foo');
        $this->assertEquals($b->tell(), 3);
        $this->assertEquals($a->tell(), 3);
        $this->assertSame('', $a->read(1));
        $this->assertEquals($b->eof(), true);
        $this->assertEquals($a->eof(), true);
        $b->seek(0);
        $this->assertEquals('foo', (string) $b);
        $b->seek(0);
        $this->assertEquals('foo', $b->getContents());
        $this->assertEquals($a->getMetadata(), $b->getMetadata());
        $b->seek(0, SEEK_END);
        $b->write('bar');
        $this->assertEquals('foobar', (string) $b);
        $this->assertInternalType('resource', $b->detach());
        $b->close();
    }

    public function testDecoratesWithCustomizations()
    {
        $called = false;
        $a = Psr7\stream_for('foo');
        $b = FnStream::decorate($a, [
            'read' => function ($len) use (&$called, $a) {
                $called = true;
                return $a->read($len);
            }
        ]);
        $this->assertEquals('foo', $b->read(3));
        $this->assertTrue($called);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\FnStream;
use GuzzleHttp\Psr7\NoSeekStream;

class FunctionsTest extends \PHPUnit_Framework_TestCase
{
    public function testCopiesToString()
    {
        $s = Psr7\stream_for('foobaz');
        $this->assertEquals('foobaz', Psr7\copy_to_string($s));
        $s->seek(0);
        $this->assertEquals('foo', Psr7\copy_to_string($s, 3));
        $this->assertEquals('baz', Psr7\copy_to_string($s, 3));
        $this->assertEquals('', Psr7\copy_to_string($s));
    }

    public function testCopiesToStringStopsWhenReadFails()
    {
        $s1 = Psr7\stream_for('foobaz');
        $s1 = FnStream::decorate($s1, [
            'read' => function () { return ''; }
        ]);
        $result = Psr7\copy_to_string($s1);
        $this->assertEquals('', $result);
    }

    public function testCopiesToStream()
    {
        $s1 = Psr7\stream_for('foobaz');
        $s2 = Psr7\stream_for('');
        Psr7\copy_to_stream($s1, $s2);
        $this->assertEquals('foobaz', (string) $s2);
        $s2 = Psr7\stream_for('');
        $s1->seek(0);
        Psr7\copy_to_stream($s1, $s2, 3);
        $this->assertEquals('foo', (string) $s2);
        Psr7\copy_to_stream($s1, $s2, 3);
        $this->assertEquals('foobaz', (string) $s2);
    }

    public function testStopsCopyToStreamWhenWriteFails()
    {
        $s1 = Psr7\stream_for('foobaz');
        $s2 = Psr7\stream_for('');
        $s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]);
        Psr7\copy_to_stream($s1, $s2);
        $this->assertEquals('', (string) $s2);
    }

    public function testStopsCopyToSteamWhenWriteFailsWithMaxLen()
    {
        $s1 = Psr7\stream_for('foobaz');
        $s2 = Psr7\stream_for('');
        $s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]);
        Psr7\copy_to_stream($s1, $s2, 10);
        $this->assertEquals('', (string) $s2);
    }

    public function testStopsCopyToSteamWhenReadFailsWithMaxLen()
    {
        $s1 = Psr7\stream_for('foobaz');
        $s1 = FnStream::decorate($s1, ['read' => function () { return ''; }]);
        $s2 = Psr7\stream_for('');
        Psr7\copy_to_stream($s1, $s2, 10);
        $this->assertEquals('', (string) $s2);
    }

    public function testReadsLines()
    {
        $s = Psr7\stream_for("foo\nbaz\nbar");
        $this->assertEquals("foo\n", Psr7\readline($s));
        $this->assertEquals("baz\n", Psr7\readline($s));
        $this->assertEquals("bar", Psr7\readline($s));
    }

    public function testReadsLinesUpToMaxLength()
    {
        $s = Psr7\stream_for("12345\n");
        $this->assertEquals("123", Psr7\readline($s, 4));
        $this->assertEquals("45\n", Psr7\readline($s));
    }

    public function testReadsLineUntilFalseReturnedFromRead()
    {
        $s = $this->getMockBuilder('GuzzleHttp\Psr7\Stream')
            ->setMethods(['read', 'eof'])
            ->disableOriginalConstructor()
            ->getMock();
        $s->expects($this->exactly(2))
            ->method('read')
            ->will($this->returnCallback(function () {
                static $c = false;
                if ($c) {
                    return false;
                }
                $c = true;
                return 'h';
            }));
        $s->expects($this->exactly(2))
            ->method('eof')
            ->will($this->returnValue(false));
        $this->assertEquals("h", Psr7\readline($s));
    }

    public function testCalculatesHash()
    {
        $s = Psr7\stream_for('foobazbar');
        $this->assertEquals(md5('foobazbar'), Psr7\hash($s, 'md5'));
    }

    /**
     * @expectedException \RuntimeException
     */
    public function testCalculatesHashThrowsWhenSeekFails()
    {
        $s = new NoSeekStream(Psr7\stream_for('foobazbar'));
        $s->read(2);
        Psr7\hash($s, 'md5');
    }

    public function testCalculatesHashSeeksToOriginalPosition()
    {
        $s = Psr7\stream_for('foobazbar');
        $s->seek(4);
        $this->assertEquals(md5('foobazbar'), Psr7\hash($s, 'md5'));
        $this->assertEquals(4, $s->tell());
    }

    public function testOpensFilesSuccessfully()
    {
        $r = Psr7\try_fopen(__FILE__, 'r');
        $this->assertInternalType('resource', $r);
        fclose($r);
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Unable to open /path/to/does/not/exist using mode r
     */
    public function testThrowsExceptionNotWarning()
    {
        Psr7\try_fopen('/path/to/does/not/exist', 'r');
    }

    public function parseQueryProvider()
    {
        return [
            // Does not need to parse when the string is empty
            ['', []],
            // Can parse mult-values items
            ['q=a&q=b', ['q' => ['a', 'b']]],
            // Can parse multi-valued items that use numeric indices
            ['q[0]=a&q[1]=b', ['q[0]' => 'a', 'q[1]' => 'b']],
            // Can parse duplicates and does not include numeric indices
            ['q[]=a&q[]=b', ['q[]' => ['a', 'b']]],
            // Ensures that the value of "q" is an array even though one value
            ['q[]=a', ['q[]' => 'a']],
            // Does not modify "." to "_" like PHP's parse_str()
            ['q.a=a&q.b=b', ['q.a' => 'a', 'q.b' => 'b']],
            // Can decode %20 to " "
            ['q%20a=a%20b', ['q a' => 'a b']],
            // Can parse funky strings with no values by assigning each to null
            ['q&a', ['q' => null, 'a' => null]],
            // Does not strip trailing equal signs
            ['data=abc=', ['data' => 'abc=']],
            // Can store duplicates without affecting other values
            ['foo=a&foo=b&?µ=c', ['foo' => ['a', 'b'], '?µ' => 'c']],
            // Sets value to null when no "=" is present
            ['foo', ['foo' => null]],
            // Preserves "0" keys.
            ['0', ['0' => null]],
            // Sets the value to an empty string when "=" is present
            ['0=', ['0' => '']],
            // Preserves falsey keys
            ['var=0', ['var' => '0']],
            ['a[b][c]=1&a[b][c]=2', ['a[b][c]' => ['1', '2']]],
            ['a[b]=c&a[d]=e', ['a[b]' => 'c', 'a[d]' => 'e']],
            // Ensure it doesn't leave things behind with repeated values
            // Can parse mult-values items
            ['q=a&q=b&q=c', ['q' => ['a', 'b', 'c']]],
        ];
    }

    /**
     * @dataProvider parseQueryProvider
     */
    public function testParsesQueries($input, $output)
    {
        $result = Psr7\parse_query($input);
        $this->assertSame($output, $result);
    }

    public function testDoesNotDecode()
    {
        $str = 'foo%20=bar';
        $data = Psr7\parse_query($str, false);
        $this->assertEquals(['foo%20' => 'bar'], $data);
    }

    /**
     * @dataProvider parseQueryProvider
     */
    public function testParsesAndBuildsQueries($input, $output)
    {
        $result = Psr7\parse_query($input, false);
        $this->assertSame($input, Psr7\build_query($result, false));
    }

    public function testEncodesWithRfc1738()
    {
        $str = Psr7\build_query(['foo bar' => 'baz+'], PHP_QUERY_RFC1738);
        $this->assertEquals('foo+bar=baz%2B', $str);
    }

    public function testEncodesWithRfc3986()
    {
        $str = Psr7\build_query(['foo bar' => 'baz+'], PHP_QUERY_RFC3986);
        $this->assertEquals('foo%20bar=baz%2B', $str);
    }

    public function testDoesNotEncode()
    {
        $str = Psr7\build_query(['foo bar' => 'baz+'], false);
        $this->assertEquals('foo bar=baz+', $str);
    }

    public function testCanControlDecodingType()
    {
        $result = Psr7\parse_query('var=foo+bar', PHP_QUERY_RFC3986);
        $this->assertEquals('foo+bar', $result['var']);
        $result = Psr7\parse_query('var=foo+bar', PHP_QUERY_RFC1738);
        $this->assertEquals('foo bar', $result['var']);
    }

    public function testParsesRequestMessages()
    {
        $req = "GET /abc HTTP/1.0\r\nHost: foo.com\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest";
        $request = Psr7\parse_request($req);
        $this->assertEquals('GET', $request->getMethod());
        $this->assertEquals('/abc', $request->getRequestTarget());
        $this->assertEquals('1.0', $request->getProtocolVersion());
        $this->assertEquals('foo.com', $request->getHeaderLine('Host'));
        $this->assertEquals('Bar', $request->getHeaderLine('Foo'));
        $this->assertEquals('Bam, Qux', $request->getHeaderLine('Baz'));
        $this->assertEquals('Test', (string) $request->getBody());
        $this->assertEquals('http://foo.com/abc', (string) $request->getUri());
    }

    public function testParsesRequestMessagesWithHttpsScheme()
    {
        $req = "PUT /abc?baz=bar HTTP/1.1\r\nHost: foo.com:443\r\n\r\n";
        $request = Psr7\parse_request($req);
        $this->assertEquals('PUT', $request->getMethod());
        $this->assertEquals('/abc?baz=bar', $request->getRequestTarget());
        $this->assertEquals('1.1', $request->getProtocolVersion());
        $this->assertEquals('foo.com:443', $request->getHeaderLine('Host'));
        $this->assertEquals('', (string) $request->getBody());
        $this->assertEquals('https://foo.com/abc?baz=bar', (string) $request->getUri());
    }

    public function testParsesRequestMessagesWithUriWhenHostIsNotFirst()
    {
        $req = "PUT / HTTP/1.1\r\nFoo: Bar\r\nHost: foo.com\r\n\r\n";
        $request = Psr7\parse_request($req);
        $this->assertEquals('PUT', $request->getMethod());
        $this->assertEquals('/', $request->getRequestTarget());
        $this->assertEquals('http://foo.com/', (string) $request->getUri());
    }

    public function testParsesRequestMessagesWithFullUri()
    {
        $req = "GET https://www.google.com:443/search?q=foobar HTTP/1.1\r\nHost: www.google.com\r\n\r\n";
        $request = Psr7\parse_request($req);
        $this->assertEquals('GET', $request->getMethod());
        $this->assertEquals('https://www.google.com:443/search?q=foobar', $request->getRequestTarget());
        $this->assertEquals('1.1', $request->getProtocolVersion());
        $this->assertEquals('www.google.com', $request->getHeaderLine('Host'));
        $this->assertEquals('', (string) $request->getBody());
        $this->assertEquals('https://www.google.com/search?q=foobar', (string) $request->getUri());
    }

    public function testParsesRequestMessagesWithCustomMethod()
    {
        $req = "GET_DATA / HTTP/1.1\r\nFoo: Bar\r\nHost: foo.com\r\n\r\n";
        $request = Psr7\parse_request($req);
        $this->assertEquals('GET_DATA', $request->getMethod());
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testValidatesRequestMessages()
    {
        Psr7\parse_request("HTTP/1.1 200 OK\r\n\r\n");
    }

    public function testParsesResponseMessages()
    {
        $res = "HTTP/1.0 200 OK\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest";
        $response = Psr7\parse_response($res);
        $this->assertEquals(200, $response->getStatusCode());
        $this->assertEquals('OK', $response->getReasonPhrase());
        $this->assertEquals('1.0', $response->getProtocolVersion());
        $this->assertEquals('Bar', $response->getHeaderLine('Foo'));
        $this->assertEquals('Bam, Qux', $response->getHeaderLine('Baz'));
        $this->assertEquals('Test', (string) $response->getBody());
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testValidatesResponseMessages()
    {
        Psr7\parse_response("GET / HTTP/1.1\r\n\r\n");
    }

    public function testDetermineMimetype()
    {
        $this->assertNull(Psr7\mimetype_from_extension('not-a-real-extension'));
        $this->assertEquals(
            'application/json',
            Psr7\mimetype_from_extension('json')
        );
        $this->assertEquals(
            'image/jpeg',
            Psr7\mimetype_from_filename('/tmp/images/IMG034821.JPEG')
        );
    }

    public function testCreatesUriForValue()
    {
        $this->assertInstanceOf('GuzzleHttp\Psr7\Uri', Psr7\uri_for('/foo'));
        $this->assertInstanceOf(
            'GuzzleHttp\Psr7\Uri',
            Psr7\uri_for(new Psr7\Uri('/foo'))
        );
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testValidatesUri()
    {
        Psr7\uri_for([]);
    }

    public function testKeepsPositionOfResource()
    {
        $h = fopen(__FILE__, 'r');
        fseek($h, 10);
        $stream = Psr7\stream_for($h);
        $this->assertEquals(10, $stream->tell());
        $stream->close();
    }

    public function testCreatesWithFactory()
    {
        $stream = Psr7\stream_for('foo');
        $this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $stream);
        $this->assertEquals('foo', $stream->getContents());
        $stream->close();
    }

    public function testFactoryCreatesFromEmptyString()
    {
        $s = Psr7\stream_for();
        $this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s);
    }

    public function testFactoryCreatesFromNull()
    {
        $s = Psr7\stream_for(null);
        $this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s);
    }

    public function testFactoryCreatesFromResource()
    {
        $r = fopen(__FILE__, 'r');
        $s = Psr7\stream_for($r);
        $this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s);
        $this->assertSame(file_get_contents(__FILE__), (string) $s);
    }

    public function testFactoryCreatesFromObjectWithToString()
    {
        $r = new HasToString();
        $s = Psr7\stream_for($r);
        $this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s);
        $this->assertEquals('foo', (string) $s);
    }

    public function testCreatePassesThrough()
    {
        $s = Psr7\stream_for('foo');
        $this->assertSame($s, Psr7\stream_for($s));
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testThrowsExceptionForUnknown()
    {
        Psr7\stream_for(new \stdClass());
    }

    public function testReturnsCustomMetadata()
    {
        $s = Psr7\stream_for('foo', ['metadata' => ['hwm' => 3]]);
        $this->assertEquals(3, $s->getMetadata('hwm'));
        $this->assertArrayHasKey('hwm', $s->getMetadata());
    }

    public function testCanSetSize()
    {
        $s = Psr7\stream_for('', ['size' => 10]);
        $this->assertEquals(10, $s->getSize());
    }

    public function testCanCreateIteratorBasedStream()
    {
        $a = new \ArrayIterator(['foo', 'bar', '123']);
        $p = Psr7\stream_for($a);
        $this->assertInstanceOf('GuzzleHttp\Psr7\PumpStream', $p);
        $this->assertEquals('foo', $p->read(3));
        $this->assertFalse($p->eof());
        $this->assertEquals('b', $p->read(1));
        $this->assertEquals('a', $p->read(1));
        $this->assertEquals('r12', $p->read(3));
        $this->assertFalse($p->eof());
        $this->assertEquals('3', $p->getContents());
        $this->assertTrue($p->eof());
        $this->assertEquals(9, $p->tell());
    }

    public function testConvertsRequestsToStrings()
    {
        $request = new Psr7\Request('PUT', 'http://foo.com/hi?123', [
            'Baz' => 'bar',
            'Qux' => 'ipsum'
        ], 'hello', '1.0');
        $this->assertEquals(
            "PUT /hi?123 HTTP/1.0\r\nHost: foo.com\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello",
            Psr7\str($request)
        );
    }

    public function testConvertsResponsesToStrings()
    {
        $response = new Psr7\Response(200, [
            'Baz' => 'bar',
            'Qux' => 'ipsum'
        ], 'hello', '1.0', 'FOO');
        $this->assertEquals(
            "HTTP/1.0 200 FOO\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello",
            Psr7\str($response)
        );
    }

    public function parseParamsProvider()
    {
        $res1 = array(
            array(
                '<http:/.../front.jpeg>',
                'rel' => 'front',
                'type' => 'image/jpeg',
            ),
            array(
                '<http://.../back.jpeg>',
                'rel' => 'back',
                'type' => 'image/jpeg',
            ),
        );
        return array(
            array(
                '<http:/.../front.jpeg>; rel="front"; type="image/jpeg", <http://.../back.jpeg>; rel=back; type="image/jpeg"',
                $res1
            ),
            array(
                '<http:/.../front.jpeg>; rel="front"; type="image/jpeg",<http://.../back.jpeg>; rel=back; type="image/jpeg"',
                $res1
            ),
            array(
                'foo="baz"; bar=123, boo, test="123", foobar="foo;bar"',
                array(
                    array('foo' => 'baz', 'bar' => '123'),
                    array('boo'),
                    array('test' => '123'),
                    array('foobar' => 'foo;bar')
                )
            ),
            array(
                '<http://.../side.jpeg?test=1>; rel="side"; type="image/jpeg",<http://.../side.jpeg?test=2>; rel=side; type="image/jpeg"',
                array(
                    array('<http://.../side.jpeg?test=1>', 'rel' => 'side', 'type' => 'image/jpeg'),
                    array('<http://.../side.jpeg?test=2>', 'rel' => 'side', 'type' => 'image/jpeg')
                )
            ),
            array(
                '',
                array()
            )
        );
    }
    /**
     * @dataProvider parseParamsProvider
     */
    public function testParseParams($header, $result)
    {
        $this->assertEquals($result, Psr7\parse_header($header));
    }

    public function testParsesArrayHeaders()
    {
        $header = ['a, b', 'c', 'd, e'];
        $this->assertEquals(['a', 'b', 'c', 'd', 'e'], Psr7\normalize_header($header));
    }

    public function testRewindsBody()
    {
        $body = Psr7\stream_for('abc');
        $res = new Psr7\Response(200, [], $body);
        Psr7\rewind_body($res);
        $this->assertEquals(0, $body->tell());
        $body->rewind(1);
        Psr7\rewind_body($res);
        $this->assertEquals(0, $body->tell());
    }

    /**
     * @expectedException \RuntimeException
     */
    public function testThrowsWhenBodyCannotBeRewound()
    {
        $body = Psr7\stream_for('abc');
        $body->read(1);
        $body = FnStream::decorate($body, [
            'rewind' => function () { throw new \RuntimeException('a'); }
        ]);
        $res = new Psr7\Response(200, [], $body);
        Psr7\rewind_body($res);
    }

    public function testCanModifyRequestWithUri()
    {
        $r1 = new Psr7\Request('GET', 'http://foo.com');
        $r2 = Psr7\modify_request($r1, [
            'uri' => new Psr7\Uri('http://www.foo.com')
        ]);
        $this->assertEquals('http://www.foo.com', (string) $r2->getUri());
        $this->assertEquals('www.foo.com', (string) $r2->getHeaderLine('host'));
    }

    public function testCanModifyRequestWithUriAndPort()
    {
        $r1 = new Psr7\Request('GET', 'http://foo.com:8000');
        $r2 = Psr7\modify_request($r1, [
            'uri' => new Psr7\Uri('http://www.foo.com:8000')
        ]);
        $this->assertEquals('http://www.foo.com:8000', (string) $r2->getUri());
        $this->assertEquals('www.foo.com:8000', (string) $r2->getHeaderLine('host'));
    }

    public function testCanModifyRequestWithCaseInsensitiveHeader()
    {
        $r1 = new Psr7\Request('GET', 'http://foo.com', ['User-Agent' => 'foo']);
        $r2 = Psr7\modify_request($r1, ['set_headers' => ['User-agent' => 'bar']]);
        $this->assertEquals('bar', $r2->getHeaderLine('User-Agent'));
        $this->assertEquals('bar', $r2->getHeaderLine('User-agent'));
    }

    public function testReturnsAsIsWhenNoChanges()
    {
        $r1 = new Psr7\Request('GET', 'http://foo.com');
        $r2 = Psr7\modify_request($r1, []);
        $this->assertTrue($r2 instanceof Psr7\Request);

        $r1 = new Psr7\ServerRequest('GET', 'http://foo.com');
        $r2 = Psr7\modify_request($r1, []);
        $this->assertTrue($r2 instanceof \Psr\Http\Message\ServerRequestInterface);
    }

    public function testReturnsUriAsIsWhenNoChanges()
    {
        $r1 = new Psr7\Request('GET', 'http://foo.com');
        $r2 = Psr7\modify_request($r1, ['set_headers' => ['foo' => 'bar']]);
        $this->assertNotSame($r1, $r2);
        $this->assertEquals('bar', $r2->getHeaderLine('foo'));
    }

    public function testRemovesHeadersFromMessage()
    {
        $r1 = new Psr7\Request('GET', 'http://foo.com', ['foo' => 'bar']);
        $r2 = Psr7\modify_request($r1, ['remove_headers' => ['foo']]);
        $this->assertNotSame($r1, $r2);
        $this->assertFalse($r2->hasHeader('foo'));
    }

    public function testAddsQueryToUri()
    {
        $r1 = new Psr7\Request('GET', 'http://foo.com');
        $r2 = Psr7\modify_request($r1, ['query' => 'foo=bar']);
        $this->assertNotSame($r1, $r2);
        $this->assertEquals('foo=bar', $r2->getUri()->getQuery());
    }

    public function testModifyRequestKeepInstanceOfRequest()
    {
        $r1 = new Psr7\Request('GET', 'http://foo.com');
        $r2 = Psr7\modify_request($r1, ['remove_headers' => ['non-existent']]);
        $this->assertTrue($r2 instanceof Psr7\Request);

        $r1 = new Psr7\ServerRequest('GET', 'http://foo.com');
        $r2 = Psr7\modify_request($r1, ['remove_headers' => ['non-existent']]);
        $this->assertTrue($r2 instanceof \Psr\Http\Message\ServerRequestInterface);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\InflateStream;

class InflateStreamtest extends \PHPUnit_Framework_TestCase
{
    public function testInflatesStreams()
    {
        $content = gzencode('test');
        $a = Psr7\stream_for($content);
        $b = new InflateStream($a);
        $this->assertEquals('test', (string) $b);
    }

    public function testInflatesStreamsWithFilename()
    {
        $content = $this->getGzipStringWithFilename('test');
        $a = Psr7\stream_for($content);
        $b = new InflateStream($a);
        $this->assertEquals('test', (string) $b);
    }

    private function getGzipStringWithFilename($original_string)
    {
        $gzipped = bin2hex(gzencode($original_string));

        $header = substr($gzipped, 0, 20);
        // set FNAME flag
        $header[6]=0;
        $header[7]=8;
        // make a dummy filename
        $filename = "64756d6d7900";
        $rest = substr($gzipped, 20);

        return hex2bin($header . $filename . $rest);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\LazyOpenStream;

class LazyOpenStreamTest extends \PHPUnit_Framework_TestCase
{
    private $fname;

    public function setup()
    {
        $this->fname = tempnam('/tmp', 'tfile');

        if (file_exists($this->fname)) {
            unlink($this->fname);
        }
    }

    public function tearDown()
    {
        if (file_exists($this->fname)) {
            unlink($this->fname);
        }
    }

    public function testOpensLazily()
    {
        $l = new LazyOpenStream($this->fname, 'w+');
        $l->write('foo');
        $this->assertInternalType('array', $l->getMetadata());
        $this->assertFileExists($this->fname);
        $this->assertEquals('foo', file_get_contents($this->fname));
        $this->assertEquals('foo', (string) $l);
    }

    public function testProxiesToFile()
    {
        file_put_contents($this->fname, 'foo');
        $l = new LazyOpenStream($this->fname, 'r');
        $this->assertEquals('foo', $l->read(4));
        $this->assertTrue($l->eof());
        $this->assertEquals(3, $l->tell());
        $this->assertTrue($l->isReadable());
        $this->assertTrue($l->isSeekable());
        $this->assertFalse($l->isWritable());
        $l->seek(1);
        $this->assertEquals('oo', $l->getContents());
        $this->assertEquals('foo', (string) $l);
        $this->assertEquals(3, $l->getSize());
        $this->assertInternalType('array', $l->getMetadata());
        $l->close();
    }

    public function testDetachesUnderlyingStream()
    {
        file_put_contents($this->fname, 'foo');
        $l = new LazyOpenStream($this->fname, 'r');
        $r = $l->detach();
        $this->assertInternalType('resource', $r);
        fseek($r, 0);
        $this->assertEquals('foo', stream_get_contents($r));
        fclose($r);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\FnStream;
use GuzzleHttp\Psr7\Stream;
use GuzzleHttp\Psr7\LimitStream;
use GuzzleHttp\Psr7\NoSeekStream;

/**
 * @covers GuzzleHttp\Psr7\LimitStream
 */
class LimitStreamTest extends \PHPUnit_Framework_TestCase
{
    /** @var LimitStream */
    protected $body;

    /** @var Stream */
    protected $decorated;

    public function setUp()
    {
        $this->decorated = Psr7\stream_for(fopen(__FILE__, 'r'));
        $this->body = new LimitStream($this->decorated, 10, 3);
    }

    public function testReturnsSubset()
    {
        $body = new LimitStream(Psr7\stream_for('foo'), -1, 1);
        $this->assertEquals('oo', (string) $body);
        $this->assertTrue($body->eof());
        $body->seek(0);
        $this->assertFalse($body->eof());
        $this->assertEquals('oo', $body->read(100));
        $this->assertSame('', $body->read(1));
        $this->assertTrue($body->eof());
    }

    public function testReturnsSubsetWhenCastToString()
    {
        $body = Psr7\stream_for('foo_baz_bar');
        $limited = new LimitStream($body, 3, 4);
        $this->assertEquals('baz', (string) $limited);
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Unable to seek to stream position 10 with whence 0
     */
    public function testEnsuresPositionCanBeekSeekedTo()
    {
        new LimitStream(Psr7\stream_for(''), 0, 10);
    }

    public function testReturnsSubsetOfEmptyBodyWhenCastToString()
    {
        $body = Psr7\stream_for('01234567891234');
        $limited = new LimitStream($body, 0, 10);
        $this->assertEquals('', (string) $limited);
    }

    public function testReturnsSpecificSubsetOBodyWhenCastToString()
    {
        $body = Psr7\stream_for('0123456789abcdef');
        $limited = new LimitStream($body, 3, 10);
        $this->assertEquals('abc', (string) $limited);
    }

    public function testSeeksWhenConstructed()
    {
        $this->assertEquals(0, $this->body->tell());
        $this->assertEquals(3, $this->decorated->tell());
    }

    public function testAllowsBoundedSeek()
    {
        $this->body->seek(100);
        $this->assertEquals(10, $this->body->tell());
        $this->assertEquals(13, $this->decorated->tell());
        $this->body->seek(0);
        $this->assertEquals(0, $this->body->tell());
        $this->assertEquals(3, $this->decorated->tell());
        try {
            $this->body->seek(-10);
            $this->fail();
        } catch (\RuntimeException $e) {}
        $this->assertEquals(0, $this->body->tell());
        $this->assertEquals(3, $this->decorated->tell());
        $this->body->seek(5);
        $this->assertEquals(5, $this->body->tell());
        $this->assertEquals(8, $this->decorated->tell());
        // Fail
        try {
            $this->body->seek(1000, SEEK_END);
            $this->fail();
        } catch (\RuntimeException $e) {}
    }

    public function testReadsOnlySubsetOfData()
    {
        $data = $this->body->read(100);
        $this->assertEquals(10, strlen($data));
        $this->assertSame('', $this->body->read(1000));

        $this->body->setOffset(10);
        $newData = $this->body->read(100);
        $this->assertEquals(10, strlen($newData));
        $this->assertNotSame($data, $newData);
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Could not seek to stream offset 2
     */
    public function testThrowsWhenCurrentGreaterThanOffsetSeek()
    {
        $a = Psr7\stream_for('foo_bar');
        $b = new NoSeekStream($a);
        $c = new LimitStream($b);
        $a->getContents();
        $c->setOffset(2);
    }

    public function testCanGetContentsWithoutSeeking()
    {
        $a = Psr7\stream_for('foo_bar');
        $b = new NoSeekStream($a);
        $c = new LimitStream($b);
        $this->assertEquals('foo_bar', $c->getContents());
    }

    public function testClaimsConsumedWhenReadLimitIsReached()
    {
        $this->assertFalse($this->body->eof());
        $this->body->read(1000);
        $this->assertTrue($this->body->eof());
    }

    public function testContentLengthIsBounded()
    {
        $this->assertEquals(10, $this->body->getSize());
    }

    public function testGetContentsIsBasedOnSubset()
    {
        $body = new LimitStream(Psr7\stream_for('foobazbar'), 3, 3);
        $this->assertEquals('baz', $body->getContents());
    }

    public function testReturnsNullIfSizeCannotBeDetermined()
    {
        $a = new FnStream([
            'getSize' => function () { return null; },
            'tell'    => function () { return 0; },
        ]);
        $b = new LimitStream($a);
        $this->assertNull($b->getSize());
    }

    public function testLengthLessOffsetWhenNoLimitSize()
    {
        $a = Psr7\stream_for('foo_bar');
        $b = new LimitStream($a, -1, 4);
        $this->assertEquals(3, $b->getSize());
    }
}
<?php
namespace GuzzleHttp\Tests;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\MultipartStream;

class MultipartStreamTest extends \PHPUnit_Framework_TestCase
{
    public function testCreatesDefaultBoundary()
    {
        $b = new MultipartStream();
        $this->assertNotEmpty($b->getBoundary());
    }

    public function testCanProvideBoundary()
    {
        $b = new MultipartStream([], 'foo');
        $this->assertEquals('foo', $b->getBoundary());
    }

    public function testIsNotWritable()
    {
        $b = new MultipartStream();
        $this->assertFalse($b->isWritable());
    }

    public function testCanCreateEmptyStream()
    {
        $b = new MultipartStream();
        $boundary = $b->getBoundary();
        $this->assertSame("--{$boundary}--\r\n", $b->getContents());
        $this->assertSame(strlen($boundary) + 6, $b->getSize());
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testValidatesFilesArrayElement()
    {
        new MultipartStream([['foo' => 'bar']]);
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testEnsuresFileHasName()
    {
        new MultipartStream([['contents' => 'bar']]);
    }

    public function testSerializesFields()
    {
        $b = new MultipartStream([
            [
                'name'     => 'foo',
                'contents' => 'bar'
            ],
            [
                'name' => 'baz',
                'contents' => 'bam'
            ]
        ], 'boundary');
        $this->assertEquals(
            "--boundary\r\nContent-Disposition: form-data; name=\"foo\"\r\nContent-Length: 3\r\n\r\n"
            . "bar\r\n--boundary\r\nContent-Disposition: form-data; name=\"baz\"\r\nContent-Length: 3"
            . "\r\n\r\nbam\r\n--boundary--\r\n", (string) $b);
    }

    public function testSerializesNonStringFields()
    {
        $b = new MultipartStream([
            [
                'name'     => 'int',
                'contents' => (int) 1
            ],
            [
                'name' => 'bool',
                'contents' => (boolean) false
            ],
            [
                'name' => 'bool2',
                'contents' => (boolean) true
            ],
            [
                'name' => 'float',
                'contents' => (float) 1.1
            ]
        ], 'boundary');
        $this->assertEquals(
            "--boundary\r\nContent-Disposition: form-data; name=\"int\"\r\nContent-Length: 1\r\n\r\n"
            . "1\r\n--boundary\r\nContent-Disposition: form-data; name=\"bool\"\r\n\r\n\r\n--boundary"
            . "\r\nContent-Disposition: form-data; name=\"bool2\"\r\nContent-Length: 1\r\n\r\n"
            . "1\r\n--boundary\r\nContent-Disposition: form-data; name=\"float\"\r\nContent-Length: 3"
            . "\r\n\r\n1.1\r\n--boundary--\r\n", (string) $b);
    }

    public function testSerializesFiles()
    {
        $f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), [
            'getMetadata' => function () {
                return '/foo/bar.txt';
            }
        ]);

        $f2 = Psr7\FnStream::decorate(Psr7\stream_for('baz'), [
            'getMetadata' => function () {
                return '/foo/baz.jpg';
            }
        ]);

        $f3 = Psr7\FnStream::decorate(Psr7\stream_for('bar'), [
            'getMetadata' => function () {
                return '/foo/bar.gif';
            }
        ]);

        $b = new MultipartStream([
            [
                'name'     => 'foo',
                'contents' => $f1
            ],
            [
                'name' => 'qux',
                'contents' => $f2
            ],
            [
                'name'     => 'qux',
                'contents' => $f3
            ],
        ], 'boundary');

        $expected = <<<EOT
--boundary
Content-Disposition: form-data; name="foo"; filename="bar.txt"
Content-Length: 3
Content-Type: text/plain

foo
--boundary
Content-Disposition: form-data; name="qux"; filename="baz.jpg"
Content-Length: 3
Content-Type: image/jpeg

baz
--boundary
Content-Disposition: form-data; name="qux"; filename="bar.gif"
Content-Length: 3
Content-Type: image/gif

bar
--boundary--

EOT;

        $this->assertEquals($expected, str_replace("\r", '', $b));
    }

    public function testSerializesFilesWithCustomHeaders()
    {
        $f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), [
            'getMetadata' => function () {
                return '/foo/bar.txt';
            }
        ]);

        $b = new MultipartStream([
            [
                'name' => 'foo',
                'contents' => $f1,
                'headers'  => [
                    'x-foo' => 'bar',
                    'content-disposition' => 'custom'
                ]
            ]
        ], 'boundary');

        $expected = <<<EOT
--boundary
x-foo: bar
content-disposition: custom
Content-Length: 3
Content-Type: text/plain

foo
--boundary--

EOT;

        $this->assertEquals($expected, str_replace("\r", '', $b));
    }

    public function testSerializesFilesWithCustomHeadersAndMultipleValues()
    {
        $f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), [
            'getMetadata' => function () {
                return '/foo/bar.txt';
            }
        ]);

        $f2 = Psr7\FnStream::decorate(Psr7\stream_for('baz'), [
            'getMetadata' => function () {
                return '/foo/baz.jpg';
            }
        ]);

        $b = new MultipartStream([
            [
                'name'     => 'foo',
                'contents' => $f1,
                'headers'  => [
                    'x-foo' => 'bar',
                    'content-disposition' => 'custom'
                ]
            ],
            [
                'name'     => 'foo',
                'contents' => $f2,
                'headers'  => ['cOntenT-Type' => 'custom'],
            ]
        ], 'boundary');

        $expected = <<<EOT
--boundary
x-foo: bar
content-disposition: custom
Content-Length: 3
Content-Type: text/plain

foo
--boundary
cOntenT-Type: custom
Content-Disposition: form-data; name="foo"; filename="baz.jpg"
Content-Length: 3

baz
--boundary--

EOT;

        $this->assertEquals($expected, str_replace("\r", '', $b));
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\NoSeekStream;

/**
 * @covers GuzzleHttp\Psr7\NoSeekStream
 * @covers GuzzleHttp\Psr7\StreamDecoratorTrait
 */
class NoSeekStreamTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Cannot seek a NoSeekStream
     */
    public function testCannotSeek()
    {
        $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
            ->setMethods(['isSeekable', 'seek'])
            ->getMockForAbstractClass();
        $s->expects($this->never())->method('seek');
        $s->expects($this->never())->method('isSeekable');
        $wrapped = new NoSeekStream($s);
        $this->assertFalse($wrapped->isSeekable());
        $wrapped->seek(2);
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Cannot write to a non-writable stream
     */
    public function testHandlesClose()
    {
        $s = Psr7\stream_for('foo');
        $wrapped = new NoSeekStream($s);
        $wrapped->close();
        $wrapped->write('foo');
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\LimitStream;
use GuzzleHttp\Psr7\PumpStream;
use GuzzleHttp\Psr7;

class PumpStreamTest extends \PHPUnit_Framework_TestCase
{
    public function testHasMetadataAndSize()
    {
        $p = new PumpStream(function () {}, [
            'metadata' => ['foo' => 'bar'],
            'size'     => 100
        ]);

        $this->assertEquals('bar', $p->getMetadata('foo'));
        $this->assertEquals(['foo' => 'bar'], $p->getMetadata());
        $this->assertEquals(100, $p->getSize());
    }

    public function testCanReadFromCallable()
    {
        $p = Psr7\stream_for(function ($size) {
            return 'a';
        });
        $this->assertEquals('a', $p->read(1));
        $this->assertEquals(1, $p->tell());
        $this->assertEquals('aaaaa', $p->read(5));
        $this->assertEquals(6, $p->tell());
    }

    public function testStoresExcessDataInBuffer()
    {
        $called = [];
        $p = Psr7\stream_for(function ($size) use (&$called) {
            $called[] = $size;
            return 'abcdef';
        });
        $this->assertEquals('a', $p->read(1));
        $this->assertEquals('b', $p->read(1));
        $this->assertEquals('cdef', $p->read(4));
        $this->assertEquals('abcdefabc', $p->read(9));
        $this->assertEquals([1, 9, 3], $called);
    }

    public function testInifiniteStreamWrappedInLimitStream()
    {
        $p = Psr7\stream_for(function () { return 'a'; });
        $s = new LimitStream($p, 5);
        $this->assertEquals('aaaaa', (string) $s);
    }

    public function testDescribesCapabilities()
    {
        $p = Psr7\stream_for(function () {});
        $this->assertTrue($p->isReadable());
        $this->assertFalse($p->isSeekable());
        $this->assertFalse($p->isWritable());
        $this->assertNull($p->getSize());
        $this->assertEquals('', $p->getContents());
        $this->assertEquals('', (string) $p);
        $p->close();
        $this->assertEquals('', $p->read(10));
        $this->assertTrue($p->eof());

        try {
            $this->assertFalse($p->write('aa'));
            $this->fail();
        } catch (\RuntimeException $e) {}
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Uri;

/**
 * @covers GuzzleHttp\Psr7\Request
 */
class RequestTest extends \PHPUnit_Framework_TestCase
{
    public function testRequestUriMayBeString()
    {
        $r = new Request('GET', '/');
        $this->assertEquals('/', (string) $r->getUri());
    }

    public function testRequestUriMayBeUri()
    {
        $uri = new Uri('/');
        $r = new Request('GET', $uri);
        $this->assertSame($uri, $r->getUri());
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testValidateRequestUri()
    {
        new Request('GET', '///');
    }

    public function testCanConstructWithBody()
    {
        $r = new Request('GET', '/', [], 'baz');
        $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
        $this->assertEquals('baz', (string) $r->getBody());
    }

    public function testNullBody()
    {
        $r = new Request('GET', '/', [], null);
        $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
        $this->assertSame('', (string) $r->getBody());
    }

    public function testFalseyBody()
    {
        $r = new Request('GET', '/', [], '0');
        $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
        $this->assertSame('0', (string) $r->getBody());
    }

    public function testConstructorDoesNotReadStreamBody()
    {
        $streamIsRead = false;
        $body = Psr7\FnStream::decorate(Psr7\stream_for(''), [
            '__toString' => function () use (&$streamIsRead) {
                $streamIsRead = true;
                return '';
            }
        ]);

        $r = new Request('GET', '/', [], $body);
        $this->assertFalse($streamIsRead);
        $this->assertSame($body, $r->getBody());
    }

    public function testCapitalizesMethod()
    {
        $r = new Request('get', '/');
        $this->assertEquals('GET', $r->getMethod());
    }

    public function testCapitalizesWithMethod()
    {
        $r = new Request('GET', '/');
        $this->assertEquals('PUT', $r->withMethod('put')->getMethod());
    }

    public function testWithUri()
    {
        $r1 = new Request('GET', '/');
        $u1 = $r1->getUri();
        $u2 = new Uri('http://www.example.com');
        $r2 = $r1->withUri($u2);
        $this->assertNotSame($r1, $r2);
        $this->assertSame($u2, $r2->getUri());
        $this->assertSame($u1, $r1->getUri());
    }

    public function testSameInstanceWhenSameUri()
    {
        $r1 = new Request('GET', 'http://foo.com');
        $r2 = $r1->withUri($r1->getUri());
        $this->assertSame($r1, $r2);
    }

    public function testWithRequestTarget()
    {
        $r1 = new Request('GET', '/');
        $r2 = $r1->withRequestTarget('*');
        $this->assertEquals('*', $r2->getRequestTarget());
        $this->assertEquals('/', $r1->getRequestTarget());
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testRequestTargetDoesNotAllowSpaces()
    {
        $r1 = new Request('GET', '/');
        $r1->withRequestTarget('/foo bar');
    }

    public function testRequestTargetDefaultsToSlash()
    {
        $r1 = new Request('GET', '');
        $this->assertEquals('/', $r1->getRequestTarget());
        $r2 = new Request('GET', '*');
        $this->assertEquals('*', $r2->getRequestTarget());
        $r3 = new Request('GET', 'http://foo.com/bar baz/');
        $this->assertEquals('/bar%20baz/', $r3->getRequestTarget());
    }

    public function testBuildsRequestTarget()
    {
        $r1 = new Request('GET', 'http://foo.com/baz?bar=bam');
        $this->assertEquals('/baz?bar=bam', $r1->getRequestTarget());
    }

    public function testBuildsRequestTargetWithFalseyQuery()
    {
        $r1 = new Request('GET', 'http://foo.com/baz?0');
        $this->assertEquals('/baz?0', $r1->getRequestTarget());
    }

    public function testHostIsAddedFirst()
    {
        $r = new Request('GET', 'http://foo.com/baz?bar=bam', ['Foo' => 'Bar']);
        $this->assertEquals([
            'Host' => ['foo.com'],
            'Foo'  => ['Bar']
        ], $r->getHeaders());
    }

    public function testCanGetHeaderAsCsv()
    {
        $r = new Request('GET', 'http://foo.com/baz?bar=bam', [
            'Foo' => ['a', 'b', 'c']
        ]);
        $this->assertEquals('a, b, c', $r->getHeaderLine('Foo'));
        $this->assertEquals('', $r->getHeaderLine('Bar'));
    }

    public function testHostIsNotOverwrittenWhenPreservingHost()
    {
        $r = new Request('GET', 'http://foo.com/baz?bar=bam', ['Host' => 'a.com']);
        $this->assertEquals(['Host' => ['a.com']], $r->getHeaders());
        $r2 = $r->withUri(new Uri('http://www.foo.com/bar'), true);
        $this->assertEquals('a.com', $r2->getHeaderLine('Host'));
    }

    public function testOverridesHostWithUri()
    {
        $r = new Request('GET', 'http://foo.com/baz?bar=bam');
        $this->assertEquals(['Host' => ['foo.com']], $r->getHeaders());
        $r2 = $r->withUri(new Uri('http://www.baz.com/bar'));
        $this->assertEquals('www.baz.com', $r2->getHeaderLine('Host'));
    }

    public function testAggregatesHeaders()
    {
        $r = new Request('GET', '', [
            'ZOO' => 'zoobar',
            'zoo' => ['foobar', 'zoobar']
        ]);
        $this->assertEquals(['ZOO' => ['zoobar', 'foobar', 'zoobar']], $r->getHeaders());
        $this->assertEquals('zoobar, foobar, zoobar', $r->getHeaderLine('zoo'));
    }

    public function testAddsPortToHeader()
    {
        $r = new Request('GET', 'http://foo.com:8124/bar');
        $this->assertEquals('foo.com:8124', $r->getHeaderLine('host'));
    }

    public function testAddsPortToHeaderAndReplacePreviousPort()
    {
        $r = new Request('GET', 'http://foo.com:8124/bar');
        $r = $r->withUri(new Uri('http://foo.com:8125/bar'));
        $this->assertEquals('foo.com:8125', $r->getHeaderLine('host'));
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Response;

/**
 * @covers GuzzleHttp\Psr7\MessageTrait
 * @covers GuzzleHttp\Psr7\Response
 */
class ResponseTest extends \PHPUnit_Framework_TestCase
{
    public function testDefaultConstructor()
    {
        $r = new Response();
        $this->assertSame(200, $r->getStatusCode());
        $this->assertSame('1.1', $r->getProtocolVersion());
        $this->assertSame('OK', $r->getReasonPhrase());
        $this->assertSame([], $r->getHeaders());
        $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
        $this->assertSame('', (string) $r->getBody());
    }

    public function testCanConstructWithStatusCode()
    {
        $r = new Response(404);
        $this->assertSame(404, $r->getStatusCode());
        $this->assertSame('Not Found', $r->getReasonPhrase());
    }

    public function testConstructorDoesNotReadStreamBody()
    {
        $streamIsRead = false;
        $body = Psr7\FnStream::decorate(Psr7\stream_for(''), [
            '__toString' => function () use (&$streamIsRead) {
                $streamIsRead = true;
                return '';
            }
        ]);

        $r = new Response(200, [], $body);
        $this->assertFalse($streamIsRead);
        $this->assertSame($body, $r->getBody());
    }

    public function testStatusCanBeNumericString()
    {
        $r = new Response('404');
        $r2 = $r->withStatus('201');
        $this->assertSame(404, $r->getStatusCode());
        $this->assertSame('Not Found', $r->getReasonPhrase());
        $this->assertSame(201, $r2->getStatusCode());
        $this->assertSame('Created', $r2->getReasonPhrase());
    }

    public function testCanConstructWithHeaders()
    {
        $r = new Response(200, ['Foo' => 'Bar']);
        $this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
        $this->assertSame('Bar', $r->getHeaderLine('Foo'));
        $this->assertSame(['Bar'], $r->getHeader('Foo'));
    }

    public function testCanConstructWithHeadersAsArray()
    {
        $r = new Response(200, [
            'Foo' => ['baz', 'bar']
        ]);
        $this->assertSame(['Foo' => ['baz', 'bar']], $r->getHeaders());
        $this->assertSame('baz, bar', $r->getHeaderLine('Foo'));
        $this->assertSame(['baz', 'bar'], $r->getHeader('Foo'));
    }

    public function testCanConstructWithBody()
    {
        $r = new Response(200, [], 'baz');
        $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
        $this->assertSame('baz', (string) $r->getBody());
    }

    public function testNullBody()
    {
        $r = new Response(200, [], null);
        $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
        $this->assertSame('', (string) $r->getBody());
    }

    public function testFalseyBody()
    {
        $r = new Response(200, [], '0');
        $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
        $this->assertSame('0', (string) $r->getBody());
    }

    public function testCanConstructWithReason()
    {
        $r = new Response(200, [], null, '1.1', 'bar');
        $this->assertSame('bar', $r->getReasonPhrase());

        $r = new Response(200, [], null, '1.1', '0');
        $this->assertSame('0', $r->getReasonPhrase(), 'Falsey reason works');
    }

    public function testCanConstructWithProtocolVersion()
    {
        $r = new Response(200, [], null, '1000');
        $this->assertSame('1000', $r->getProtocolVersion());
    }

    public function testWithStatusCodeAndNoReason()
    {
        $r = (new Response())->withStatus(201);
        $this->assertSame(201, $r->getStatusCode());
        $this->assertSame('Created', $r->getReasonPhrase());
    }

    public function testWithStatusCodeAndReason()
    {
        $r = (new Response())->withStatus(201, 'Foo');
        $this->assertSame(201, $r->getStatusCode());
        $this->assertSame('Foo', $r->getReasonPhrase());

        $r = (new Response())->withStatus(201, '0');
        $this->assertSame(201, $r->getStatusCode());
        $this->assertSame('0', $r->getReasonPhrase(), 'Falsey reason works');
    }

    public function testWithProtocolVersion()
    {
        $r = (new Response())->withProtocolVersion('1000');
        $this->assertSame('1000', $r->getProtocolVersion());
    }

    public function testSameInstanceWhenSameProtocol()
    {
        $r = new Response();
        $this->assertSame($r, $r->withProtocolVersion('1.1'));
    }

    public function testWithBody()
    {
        $b = Psr7\stream_for('0');
        $r = (new Response())->withBody($b);
        $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
        $this->assertSame('0', (string) $r->getBody());
    }

    public function testSameInstanceWhenSameBody()
    {
        $r = new Response();
        $b = $r->getBody();
        $this->assertSame($r, $r->withBody($b));
    }

    public function testWithHeader()
    {
        $r = new Response(200, ['Foo' => 'Bar']);
        $r2 = $r->withHeader('baZ', 'Bam');
        $this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
        $this->assertSame(['Foo' => ['Bar'], 'baZ' => ['Bam']], $r2->getHeaders());
        $this->assertSame('Bam', $r2->getHeaderLine('baz'));
        $this->assertSame(['Bam'], $r2->getHeader('baz'));
    }

    public function testWithHeaderAsArray()
    {
        $r = new Response(200, ['Foo' => 'Bar']);
        $r2 = $r->withHeader('baZ', ['Bam', 'Bar']);
        $this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
        $this->assertSame(['Foo' => ['Bar'], 'baZ' => ['Bam', 'Bar']], $r2->getHeaders());
        $this->assertSame('Bam, Bar', $r2->getHeaderLine('baz'));
        $this->assertSame(['Bam', 'Bar'], $r2->getHeader('baz'));
    }

    public function testWithHeaderReplacesDifferentCase()
    {
        $r = new Response(200, ['Foo' => 'Bar']);
        $r2 = $r->withHeader('foO', 'Bam');
        $this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
        $this->assertSame(['foO' => ['Bam']], $r2->getHeaders());
        $this->assertSame('Bam', $r2->getHeaderLine('foo'));
        $this->assertSame(['Bam'], $r2->getHeader('foo'));
    }

    public function testWithAddedHeader()
    {
        $r = new Response(200, ['Foo' => 'Bar']);
        $r2 = $r->withAddedHeader('foO', 'Baz');
        $this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
        $this->assertSame(['Foo' => ['Bar', 'Baz']], $r2->getHeaders());
        $this->assertSame('Bar, Baz', $r2->getHeaderLine('foo'));
        $this->assertSame(['Bar', 'Baz'], $r2->getHeader('foo'));
    }

    public function testWithAddedHeaderAsArray()
    {
        $r = new Response(200, ['Foo' => 'Bar']);
        $r2 = $r->withAddedHeader('foO', ['Baz', 'Bam']);
        $this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
        $this->assertSame(['Foo' => ['Bar', 'Baz', 'Bam']], $r2->getHeaders());
        $this->assertSame('Bar, Baz, Bam', $r2->getHeaderLine('foo'));
        $this->assertSame(['Bar', 'Baz', 'Bam'], $r2->getHeader('foo'));
    }

    public function testWithAddedHeaderThatDoesNotExist()
    {
        $r = new Response(200, ['Foo' => 'Bar']);
        $r2 = $r->withAddedHeader('nEw', 'Baz');
        $this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
        $this->assertSame(['Foo' => ['Bar'], 'nEw' => ['Baz']], $r2->getHeaders());
        $this->assertSame('Baz', $r2->getHeaderLine('new'));
        $this->assertSame(['Baz'], $r2->getHeader('new'));
    }

    public function testWithoutHeaderThatExists()
    {
        $r = new Response(200, ['Foo' => 'Bar', 'Baz' => 'Bam']);
        $r2 = $r->withoutHeader('foO');
        $this->assertTrue($r->hasHeader('foo'));
        $this->assertSame(['Foo' => ['Bar'], 'Baz' => ['Bam']], $r->getHeaders());
        $this->assertFalse($r2->hasHeader('foo'));
        $this->assertSame(['Baz' => ['Bam']], $r2->getHeaders());
    }

    public function testWithoutHeaderThatDoesNotExist()
    {
        $r = new Response(200, ['Baz' => 'Bam']);
        $r2 = $r->withoutHeader('foO');
        $this->assertSame($r, $r2);
        $this->assertFalse($r2->hasHeader('foo'));
        $this->assertSame(['Baz' => ['Bam']], $r2->getHeaders());
    }

    public function testSameInstanceWhenRemovingMissingHeader()
    {
        $r = new Response();
        $this->assertSame($r, $r->withoutHeader('foo'));
    }

    public function testHeaderValuesAreTrimmed()
    {
        $r1 = new Response(200, ['OWS' => " \t \tFoo\t \t "]);
        $r2 = (new Response())->withHeader('OWS', " \t \tFoo\t \t ");
        $r3 = (new Response())->withAddedHeader('OWS', " \t \tFoo\t \t ");;

        foreach ([$r1, $r2, $r3] as $r) {
            $this->assertSame(['OWS' => ['Foo']], $r->getHeaders());
            $this->assertSame('Foo', $r->getHeaderLine('OWS'));
            $this->assertSame(['Foo'], $r->getHeader('OWS'));
        }
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\ServerRequest;
use GuzzleHttp\Psr7\UploadedFile;
use GuzzleHttp\Psr7\Uri;

/**
 * @covers GuzzleHttp\Psr7\ServerRequest
 */
class ServerRequestTest extends \PHPUnit_Framework_TestCase
{
    public function dataNormalizeFiles()
    {
        return [
            'Single file' => [
                [
                    'file' => [
                        'name' => 'MyFile.txt',
                        'type' => 'text/plain',
                        'tmp_name' => '/tmp/php/php1h4j1o',
                        'error' => '0',
                        'size' => '123'
                    ]
                ],
                [
                    'file' => new UploadedFile(
                        '/tmp/php/php1h4j1o',
                        123,
                        UPLOAD_ERR_OK,
                        'MyFile.txt',
                        'text/plain'
                    )
                ]
            ],
            'Empty file' => [
                [
                    'image_file' => [
                        'name' => '',
                        'type' => '',
                        'tmp_name' => '',
                        'error' => '4',
                        'size' => '0'
                    ]
                ],
                [
                    'image_file' => new UploadedFile(
                        '',
                        0,
                        UPLOAD_ERR_NO_FILE,
                        '',
                        ''
                    )
                ]
            ],
            'Already Converted' => [
                [
                    'file' => new UploadedFile(
                        '/tmp/php/php1h4j1o',
                        123,
                        UPLOAD_ERR_OK,
                        'MyFile.txt',
                        'text/plain'
                    )
                ],
                [
                    'file' => new UploadedFile(
                        '/tmp/php/php1h4j1o',
                        123,
                        UPLOAD_ERR_OK,
                        'MyFile.txt',
                        'text/plain'
                    )
                ]
            ],
            'Already Converted array' => [
                [
                    'file' => [
                        new UploadedFile(
                            '/tmp/php/php1h4j1o',
                            123,
                            UPLOAD_ERR_OK,
                            'MyFile.txt',
                            'text/plain'
                        ),
                        new UploadedFile(
                            '',
                            0,
                            UPLOAD_ERR_NO_FILE,
                            '',
                            ''
                        )
                    ],
                ],
                [
                    'file' => [
                        new UploadedFile(
                            '/tmp/php/php1h4j1o',
                            123,
                            UPLOAD_ERR_OK,
                            'MyFile.txt',
                            'text/plain'
                        ),
                        new UploadedFile(
                            '',
                            0,
                            UPLOAD_ERR_NO_FILE,
                            '',
                            ''
                        )
                    ],
                ]
            ],
            'Multiple files' => [
                [
                    'text_file' => [
                        'name' => 'MyFile.txt',
                        'type' => 'text/plain',
                        'tmp_name' => '/tmp/php/php1h4j1o',
                        'error' => '0',
                        'size' => '123'
                    ],
                    'image_file' => [
                        'name' => '',
                        'type' => '',
                        'tmp_name' => '',
                        'error' => '4',
                        'size' => '0'
                    ]
                ],
                [
                    'text_file' => new UploadedFile(
                        '/tmp/php/php1h4j1o',
                        123,
                        UPLOAD_ERR_OK,
                        'MyFile.txt',
                        'text/plain'
                    ),
                    'image_file' => new UploadedFile(
                        '',
                        0,
                        UPLOAD_ERR_NO_FILE,
                        '',
                        ''
                    )
                ]
            ],
            'Nested files' => [
                [
                    'file' => [
                        'name' => [
                            0 => 'MyFile.txt',
                            1 => 'Image.png',
                        ],
                        'type' => [
                            0 => 'text/plain',
                            1 => 'image/png',
                        ],
                        'tmp_name' => [
                            0 => '/tmp/php/hp9hskjhf',
                            1 => '/tmp/php/php1h4j1o',
                        ],
                        'error' => [
                            0 => '0',
                            1 => '0',
                        ],
                        'size' => [
                            0 => '123',
                            1 => '7349',
                        ],
                    ],
                    'nested' => [
                        'name' => [
                            'other' => 'Flag.txt',
                            'test' => [
                                0 => 'Stuff.txt',
                                1 => '',
                            ],
                        ],
                        'type' => [
                            'other' => 'text/plain',
                            'test' => [
                                0 => 'text/plain',
                                1 => '',
                            ],
                        ],
                        'tmp_name' => [
                            'other' => '/tmp/php/hp9hskjhf',
                            'test' => [
                                0 => '/tmp/php/asifu2gp3',
                                1 => '',
                            ],
                        ],
                        'error' => [
                            'other' => '0',
                            'test' => [
                                0 => '0',
                                1 => '4',
                            ],
                        ],
                        'size' => [
                            'other' => '421',
                            'test' => [
                                0 => '32',
                                1 => '0',
                            ]
                        ]
                    ],
                ],
                [
                    'file' => [
                        0 => new UploadedFile(
                            '/tmp/php/hp9hskjhf',
                            123,
                            UPLOAD_ERR_OK,
                            'MyFile.txt',
                            'text/plain'
                        ),
                        1 => new UploadedFile(
                            '/tmp/php/php1h4j1o',
                            7349,
                            UPLOAD_ERR_OK,
                            'Image.png',
                            'image/png'
                        ),
                    ],
                    'nested' => [
                        'other' => new UploadedFile(
                            '/tmp/php/hp9hskjhf',
                            421,
                            UPLOAD_ERR_OK,
                            'Flag.txt',
                            'text/plain'
                        ),
                        'test' => [
                            0 => new UploadedFile(
                                '/tmp/php/asifu2gp3',
                                32,
                                UPLOAD_ERR_OK,
                                'Stuff.txt',
                                'text/plain'
                            ),
                            1 => new UploadedFile(
                                '',
                                0,
                                UPLOAD_ERR_NO_FILE,
                                '',
                                ''
                            ),
                        ]
                    ]
                ]
            ]
        ];
    }

    /**
     * @dataProvider dataNormalizeFiles
     */
    public function testNormalizeFiles($files, $expected)
    {
        $result = ServerRequest::normalizeFiles($files);

        $this->assertEquals($expected, $result);
    }

    public function testNormalizeFilesRaisesException()
    {
        $this->setExpectedException('InvalidArgumentException', 'Invalid value in files specification');

        ServerRequest::normalizeFiles(['test' => 'something']);
    }

    public function dataGetUriFromGlobals()
    {
        $server = [
            'PHP_SELF' => '/blog/article.php',
            'GATEWAY_INTERFACE' => 'CGI/1.1',
            'SERVER_ADDR' => 'Server IP: 217.112.82.20',
            'SERVER_NAME' => 'www.blakesimpson.co.uk',
            'SERVER_SOFTWARE' => 'Apache/2.2.15 (Win32) JRun/4.0 PHP/5.2.13',
            'SERVER_PROTOCOL' => 'HTTP/1.0',
            'REQUEST_METHOD' => 'POST',
            'REQUEST_TIME' => 'Request start time: 1280149029',
            'QUERY_STRING' => 'id=10&user=foo',
            'DOCUMENT_ROOT' => '/path/to/your/server/root/',
            'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
            'HTTP_ACCEPT_ENCODING' => 'gzip,deflate',
            'HTTP_ACCEPT_LANGUAGE' => 'en-gb,en;q=0.5',
            'HTTP_CONNECTION' => 'keep-alive',
            'HTTP_HOST' => 'www.blakesimpson.co.uk',
            'HTTP_REFERER' => 'http://previous.url.com',
            'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)',
            'HTTPS' => '1',
            'REMOTE_ADDR' => '193.60.168.69',
            'REMOTE_HOST' => 'Client server\'s host name',
            'REMOTE_PORT' => '5390',
            'SCRIPT_FILENAME' => '/path/to/this/script.php',
            'SERVER_ADMIN' => 'webmaster@blakesimpson.co.uk',
            'SERVER_PORT' => '80',
            'SERVER_SIGNATURE' => 'Version signature: 5.123',
            'SCRIPT_NAME' => '/blog/article.php',
            'REQUEST_URI' => '/blog/article.php?id=10&user=foo',
        ];

        return [
            'Normal request' => [
                'http://www.blakesimpson.co.uk/blog/article.php?id=10&user=foo',
                $server,
            ],
            'Secure request' => [
                'https://www.blakesimpson.co.uk/blog/article.php?id=10&user=foo',
                array_merge($server, ['HTTPS' => 'on', 'SERVER_PORT' => '443']),
            ],
            'HTTP_HOST missing' => [
                'http://www.blakesimpson.co.uk/blog/article.php?id=10&user=foo',
                array_merge($server, ['HTTP_HOST' => null]),
            ],
            'No query String' => [
                'http://www.blakesimpson.co.uk/blog/article.php',
                array_merge($server, ['REQUEST_URI' => '/blog/article.php', 'QUERY_STRING' => '']),
            ],
            'Different port' => [
                'http://www.blakesimpson.co.uk:8324/blog/article.php?id=10&user=foo',
                array_merge($server, ['SERVER_PORT' => '8324']),
            ],
            'Empty server variable' => [
                '',
                [],
            ],
        ];
    }

    /**
     * @dataProvider dataGetUriFromGlobals
     */
    public function testGetUriFromGlobals($expected, $serverParams)
    {
        $_SERVER = $serverParams;

        $this->assertEquals(new Uri($expected), ServerRequest::getUriFromGlobals());
    }

    public function testFromGlobals()
    {
        $_SERVER = [
            'PHP_SELF' => '/blog/article.php',
            'GATEWAY_INTERFACE' => 'CGI/1.1',
            'SERVER_ADDR' => 'Server IP: 217.112.82.20',
            'SERVER_NAME' => 'www.blakesimpson.co.uk',
            'SERVER_SOFTWARE' => 'Apache/2.2.15 (Win32) JRun/4.0 PHP/5.2.13',
            'SERVER_PROTOCOL' => 'HTTP/1.0',
            'REQUEST_METHOD' => 'POST',
            'REQUEST_TIME' => 'Request start time: 1280149029',
            'QUERY_STRING' => 'id=10&user=foo',
            'DOCUMENT_ROOT' => '/path/to/your/server/root/',
            'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
            'HTTP_ACCEPT_ENCODING' => 'gzip,deflate',
            'HTTP_ACCEPT_LANGUAGE' => 'en-gb,en;q=0.5',
            'HTTP_CONNECTION' => 'keep-alive',
            'HTTP_HOST' => 'www.blakesimpson.co.uk',
            'HTTP_REFERER' => 'http://previous.url.com',
            'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)',
            'HTTPS' => '1',
            'REMOTE_ADDR' => '193.60.168.69',
            'REMOTE_HOST' => 'Client server\'s host name',
            'REMOTE_PORT' => '5390',
            'SCRIPT_FILENAME' => '/path/to/this/script.php',
            'SERVER_ADMIN' => 'webmaster@blakesimpson.co.uk',
            'SERVER_PORT' => '80',
            'SERVER_SIGNATURE' => 'Version signature: 5.123',
            'SCRIPT_NAME' => '/blog/article.php',
            'REQUEST_URI' => '/blog/article.php?id=10&user=foo',
        ];

        $_COOKIE = [
            'logged-in' => 'yes!'
        ];

        $_POST = [
            'name' => 'Pesho',
            'email' => 'pesho@example.com',
        ];

        $_GET = [
            'id' => 10,
            'user' => 'foo',
        ];

        $_FILES = [
            'file' => [
                'name' => 'MyFile.txt',
                'type' => 'text/plain',
                'tmp_name' => '/tmp/php/php1h4j1o',
                'error' => UPLOAD_ERR_OK,
                'size' => 123,
            ]
        ];

        $server = ServerRequest::fromGlobals();

        $this->assertEquals('POST', $server->getMethod());
        $this->assertEquals(['Host' => ['www.blakesimpson.co.uk']], $server->getHeaders());
        $this->assertEquals('', (string) $server->getBody());
        $this->assertEquals('1.0', $server->getProtocolVersion());
        $this->assertEquals($_COOKIE, $server->getCookieParams());
        $this->assertEquals($_POST, $server->getParsedBody());
        $this->assertEquals($_GET, $server->getQueryParams());

        $this->assertEquals(
            new Uri('http://www.blakesimpson.co.uk/blog/article.php?id=10&user=foo'),
            $server->getUri()
        );

        $expectedFiles = [
            'file' => new UploadedFile(
                '/tmp/php/php1h4j1o',
                123,
                UPLOAD_ERR_OK,
                'MyFile.txt',
                'text/plain'
            ),
        ];

        $this->assertEquals($expectedFiles, $server->getUploadedFiles());
    }

    public function testUploadedFiles()
    {
        $request1 = new ServerRequest('GET', '/');

        $files = [
            'file' => new UploadedFile('test', 123, UPLOAD_ERR_OK)
        ];

        $request2 = $request1->withUploadedFiles($files);

        $this->assertNotSame($request2, $request1);
        $this->assertSame([], $request1->getUploadedFiles());
        $this->assertSame($files, $request2->getUploadedFiles());
    }

    public function testServerParams()
    {
        $params = ['name' => 'value'];

        $request = new ServerRequest('GET', '/', [], null, '1.1', $params);
        $this->assertSame($params, $request->getServerParams());
    }

    public function testCookieParams()
    {
        $request1 = new ServerRequest('GET', '/');

        $params = ['name' => 'value'];

        $request2 = $request1->withCookieParams($params);

        $this->assertNotSame($request2, $request1);
        $this->assertEmpty($request1->getCookieParams());
        $this->assertSame($params, $request2->getCookieParams());
    }

    public function testQueryParams()
    {
        $request1 = new ServerRequest('GET', '/');

        $params = ['name' => 'value'];

        $request2 = $request1->withQueryParams($params);

        $this->assertNotSame($request2, $request1);
        $this->assertEmpty($request1->getQueryParams());
        $this->assertSame($params, $request2->getQueryParams());
    }

    public function testParsedBody()
    {
        $request1 = new ServerRequest('GET', '/');

        $params = ['name' => 'value'];

        $request2 = $request1->withParsedBody($params);

        $this->assertNotSame($request2, $request1);
        $this->assertEmpty($request1->getParsedBody());
        $this->assertSame($params, $request2->getParsedBody());
    }

    public function testAttributes()
    {
        $request1 = new ServerRequest('GET', '/');

        $request2 = $request1->withAttribute('name', 'value');
        $request3 = $request2->withAttribute('other', 'otherValue');
        $request4 = $request3->withoutAttribute('other');
        $request5 = $request3->withoutAttribute('unknown');

        $this->assertNotSame($request2, $request1);
        $this->assertNotSame($request3, $request2);
        $this->assertNotSame($request4, $request3);
        $this->assertNotSame($request5, $request4);

        $this->assertEmpty($request1->getAttributes());
        $this->assertEmpty($request1->getAttribute('name'));
        $this->assertEquals(
            'something',
            $request1->getAttribute('name', 'something'),
            'Should return the default value'
        );

        $this->assertEquals('value', $request2->getAttribute('name'));
        $this->assertEquals(['name' => 'value'], $request2->getAttributes());
        $this->assertEquals(['name' => 'value', 'other' => 'otherValue'], $request3->getAttributes());
        $this->assertEquals(['name' => 'value'], $request4->getAttributes());
    }

    public function testNullAttribute()
    {
        $request = (new ServerRequest('GET', '/'))->withAttribute('name', null);

        $this->assertSame(['name' => null], $request->getAttributes());
        $this->assertNull($request->getAttribute('name', 'different-default'));

        $requestWithoutAttribute = $request->withoutAttribute('name');

        $this->assertSame([], $requestWithoutAttribute->getAttributes());
        $this->assertSame('different-default', $requestWithoutAttribute->getAttribute('name', 'different-default'));
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use Psr\Http\Message\StreamInterface;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\StreamDecoratorTrait;

class Str implements StreamInterface
{
    use StreamDecoratorTrait;
}

/**
 * @covers GuzzleHttp\Psr7\StreamDecoratorTrait
 */
class StreamDecoratorTraitTest extends \PHPUnit_Framework_TestCase
{
    private $a;
    private $b;
    private $c;

    public function setUp()
    {
        $this->c = fopen('php://temp', 'r+');
        fwrite($this->c, 'foo');
        fseek($this->c, 0);
        $this->a = Psr7\stream_for($this->c);
        $this->b = new Str($this->a);
    }

    public function testCatchesExceptionsWhenCastingToString()
    {
        $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
            ->setMethods(['read'])
            ->getMockForAbstractClass();
        $s->expects($this->once())
            ->method('read')
            ->will($this->throwException(new \Exception('foo')));
        $msg = '';
        set_error_handler(function ($errNo, $str) use (&$msg) { $msg = $str; });
        echo new Str($s);
        restore_error_handler();
        $this->assertContains('foo', $msg);
    }

    public function testToString()
    {
        $this->assertEquals('foo', (string) $this->b);
    }

    public function testHasSize()
    {
        $this->assertEquals(3, $this->b->getSize());
    }

    public function testReads()
    {
        $this->assertEquals('foo', $this->b->read(10));
    }

    public function testCheckMethods()
    {
        $this->assertEquals($this->a->isReadable(), $this->b->isReadable());
        $this->assertEquals($this->a->isWritable(), $this->b->isWritable());
        $this->assertEquals($this->a->isSeekable(), $this->b->isSeekable());
    }

    public function testSeeksAndTells()
    {
        $this->b->seek(1);
        $this->assertEquals(1, $this->a->tell());
        $this->assertEquals(1, $this->b->tell());
        $this->b->seek(0);
        $this->assertEquals(0, $this->a->tell());
        $this->assertEquals(0, $this->b->tell());
        $this->b->seek(0, SEEK_END);
        $this->assertEquals(3, $this->a->tell());
        $this->assertEquals(3, $this->b->tell());
    }

    public function testGetsContents()
    {
        $this->assertEquals('foo', $this->b->getContents());
        $this->assertEquals('', $this->b->getContents());
        $this->b->seek(1);
        $this->assertEquals('oo', $this->b->getContents(1));
    }

    public function testCloses()
    {
        $this->b->close();
        $this->assertFalse(is_resource($this->c));
    }

    public function testDetaches()
    {
        $this->b->detach();
        $this->assertFalse($this->b->isReadable());
    }

    public function testWrapsMetadata()
    {
        $this->assertSame($this->b->getMetadata(), $this->a->getMetadata());
        $this->assertSame($this->b->getMetadata('uri'), $this->a->getMetadata('uri'));
    }

    public function testWrapsWrites()
    {
        $this->b->seek(0, SEEK_END);
        $this->b->write('foo');
        $this->assertEquals('foofoo', (string) $this->a);
    }

    /**
     * @expectedException \UnexpectedValueException
     */
    public function testThrowsWithInvalidGetter()
    {
        $this->b->foo;
    }

    /**
     * @expectedException \BadMethodCallException
     */
    public function testThrowsWhenGetterNotImplemented()
    {
        $s = new BadStream();
        $s->stream;
    }
}

class BadStream
{
    use StreamDecoratorTrait;

    public function __construct() {}
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\NoSeekStream;
use GuzzleHttp\Psr7\Stream;

/**
 * @covers GuzzleHttp\Psr7\Stream
 */
class StreamTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @expectedException \InvalidArgumentException
     */
    public function testConstructorThrowsExceptionOnInvalidArgument()
    {
        new Stream(true);
    }

    public function testConstructorInitializesProperties()
    {
        $handle = fopen('php://temp', 'r+');
        fwrite($handle, 'data');
        $stream = new Stream($handle);
        $this->assertTrue($stream->isReadable());
        $this->assertTrue($stream->isWritable());
        $this->assertTrue($stream->isSeekable());
        $this->assertEquals('php://temp', $stream->getMetadata('uri'));
        $this->assertInternalType('array', $stream->getMetadata());
        $this->assertEquals(4, $stream->getSize());
        $this->assertFalse($stream->eof());
        $stream->close();
    }

    public function testStreamClosesHandleOnDestruct()
    {
        $handle = fopen('php://temp', 'r');
        $stream = new Stream($handle);
        unset($stream);
        $this->assertFalse(is_resource($handle));
    }

    public function testConvertsToString()
    {
        $handle = fopen('php://temp', 'w+');
        fwrite($handle, 'data');
        $stream = new Stream($handle);
        $this->assertEquals('data', (string) $stream);
        $this->assertEquals('data', (string) $stream);
        $stream->close();
    }

    public function testGetsContents()
    {
        $handle = fopen('php://temp', 'w+');
        fwrite($handle, 'data');
        $stream = new Stream($handle);
        $this->assertEquals('', $stream->getContents());
        $stream->seek(0);
        $this->assertEquals('data', $stream->getContents());
        $this->assertEquals('', $stream->getContents());
    }

    public function testChecksEof()
    {
        $handle = fopen('php://temp', 'w+');
        fwrite($handle, 'data');
        $stream = new Stream($handle);
        $this->assertFalse($stream->eof());
        $stream->read(4);
        $this->assertTrue($stream->eof());
        $stream->close();
    }

    public function testGetSize()
    {
        $size = filesize(__FILE__);
        $handle = fopen(__FILE__, 'r');
        $stream = new Stream($handle);
        $this->assertEquals($size, $stream->getSize());
        // Load from cache
        $this->assertEquals($size, $stream->getSize());
        $stream->close();
    }

    public function testEnsuresSizeIsConsistent()
    {
        $h = fopen('php://temp', 'w+');
        $this->assertEquals(3, fwrite($h, 'foo'));
        $stream = new Stream($h);
        $this->assertEquals(3, $stream->getSize());
        $this->assertEquals(4, $stream->write('test'));
        $this->assertEquals(7, $stream->getSize());
        $this->assertEquals(7, $stream->getSize());
        $stream->close();
    }

    public function testProvidesStreamPosition()
    {
        $handle = fopen('php://temp', 'w+');
        $stream = new Stream($handle);
        $this->assertEquals(0, $stream->tell());
        $stream->write('foo');
        $this->assertEquals(3, $stream->tell());
        $stream->seek(1);
        $this->assertEquals(1, $stream->tell());
        $this->assertSame(ftell($handle), $stream->tell());
        $stream->close();
    }

    public function testCanDetachStream()
    {
        $r = fopen('php://temp', 'w+');
        $stream = new Stream($r);
        $stream->write('foo');
        $this->assertTrue($stream->isReadable());
        $this->assertSame($r, $stream->detach());
        $stream->detach();

        $this->assertFalse($stream->isReadable());
        $this->assertFalse($stream->isWritable());
        $this->assertFalse($stream->isSeekable());

        $throws = function (callable $fn) use ($stream) {
            try {
                $fn($stream);
                $this->fail();
            } catch (\Exception $e) {}
        };

        $throws(function ($stream) { $stream->read(10); });
        $throws(function ($stream) { $stream->write('bar'); });
        $throws(function ($stream) { $stream->seek(10); });
        $throws(function ($stream) { $stream->tell(); });
        $throws(function ($stream) { $stream->eof(); });
        $throws(function ($stream) { $stream->getSize(); });
        $throws(function ($stream) { $stream->getContents(); });
        $this->assertSame('', (string) $stream);
        $stream->close();
    }

    public function testCloseClearProperties()
    {
        $handle = fopen('php://temp', 'r+');
        $stream = new Stream($handle);
        $stream->close();

        $this->assertFalse($stream->isSeekable());
        $this->assertFalse($stream->isReadable());
        $this->assertFalse($stream->isWritable());
        $this->assertNull($stream->getSize());
        $this->assertEmpty($stream->getMetadata());
    }

    public function testDoesNotThrowInToString()
    {
        $s = \GuzzleHttp\Psr7\stream_for('foo');
        $s = new NoSeekStream($s);
        $this->assertEquals('foo', (string) $s);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\StreamWrapper;
use GuzzleHttp\Psr7;

/**
 * @covers GuzzleHttp\Psr7\StreamWrapper
 */
class StreamWrapperTest extends \PHPUnit_Framework_TestCase
{
    public function testResource()
    {
        $stream = Psr7\stream_for('foo');
        $handle = StreamWrapper::getResource($stream);
        $this->assertSame('foo', fread($handle, 3));
        $this->assertSame(3, ftell($handle));
        $this->assertSame(3, fwrite($handle, 'bar'));
        $this->assertSame(0, fseek($handle, 0));
        $this->assertSame('foobar', fread($handle, 6));
        $this->assertSame('', fread($handle, 1));
        $this->assertTrue(feof($handle));

        $stBlksize  = defined('PHP_WINDOWS_VERSION_BUILD') ? -1 : 0;

        // This fails on HHVM for some reason
        if (!defined('HHVM_VERSION')) {
            $this->assertEquals([
                'dev'     => 0,
                'ino'     => 0,
                'mode'    => 33206,
                'nlink'   => 0,
                'uid'     => 0,
                'gid'     => 0,
                'rdev'    => 0,
                'size'    => 6,
                'atime'   => 0,
                'mtime'   => 0,
                'ctime'   => 0,
                'blksize' => $stBlksize,
                'blocks'  => $stBlksize,
                0         => 0,
                1         => 0,
                2         => 33206,
                3         => 0,
                4         => 0,
                5         => 0,
                6         => 0,
                7         => 6,
                8         => 0,
                9         => 0,
                10        => 0,
                11        => $stBlksize,
                12        => $stBlksize,
            ], fstat($handle));
        }

        $this->assertTrue(fclose($handle));
        $this->assertSame('foobar', (string) $stream);
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testValidatesStream()
    {
        $stream = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
            ->setMethods(['isReadable', 'isWritable'])
            ->getMockForAbstractClass();
        $stream->expects($this->once())
            ->method('isReadable')
            ->will($this->returnValue(false));
        $stream->expects($this->once())
            ->method('isWritable')
            ->will($this->returnValue(false));
        StreamWrapper::getResource($stream);
    }

    /**
     * @expectedException \PHPUnit_Framework_Error_Warning
     */
    public function testReturnsFalseWhenStreamDoesNotExist()
    {
        fopen('guzzle://foo', 'r');
    }

    public function testCanOpenReadonlyStream()
    {
        $stream = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
            ->setMethods(['isReadable', 'isWritable'])
            ->getMockForAbstractClass();
        $stream->expects($this->once())
            ->method('isReadable')
            ->will($this->returnValue(false));
        $stream->expects($this->once())
            ->method('isWritable')
            ->will($this->returnValue(true));
        $r = StreamWrapper::getResource($stream);
        $this->assertInternalType('resource', $r);
        fclose($r);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use ReflectionProperty;
use GuzzleHttp\Psr7\Stream;
use GuzzleHttp\Psr7\UploadedFile;

/**
 * @covers GuzzleHttp\Psr7\UploadedFile
 */
class UploadedFileTest extends \PHPUnit_Framework_TestCase
{
    protected $cleanup;

    public function setUp()
    {
        $this->cleanup = [];
    }

    public function tearDown()
    {
        foreach ($this->cleanup as $file) {
            if (is_scalar($file) && file_exists($file)) {
                unlink($file);
            }
        }
    }

    public function invalidStreams()
    {
        return [
            'null'         => [null],
            'true'         => [true],
            'false'        => [false],
            'int'          => [1],
            'float'        => [1.1],
            'array'        => [['filename']],
            'object'       => [(object) ['filename']],
        ];
    }

    /**
     * @dataProvider invalidStreams
     */
    public function testRaisesExceptionOnInvalidStreamOrFile($streamOrFile)
    {
        $this->setExpectedException('InvalidArgumentException');

        new UploadedFile($streamOrFile, 0, UPLOAD_ERR_OK);
    }

    public function invalidSizes()
    {
        return [
            'null'   => [null],
            'float'  => [1.1],
            'array'  => [[1]],
            'object' => [(object) [1]],
        ];
    }

    /**
     * @dataProvider invalidSizes
     */
    public function testRaisesExceptionOnInvalidSize($size)
    {
        $this->setExpectedException('InvalidArgumentException', 'size');

        new UploadedFile(fopen('php://temp', 'wb+'), $size, UPLOAD_ERR_OK);
    }

    public function invalidErrorStatuses()
    {
        return [
            'null'     => [null],
            'true'     => [true],
            'false'    => [false],
            'float'    => [1.1],
            'string'   => ['1'],
            'array'    => [[1]],
            'object'   => [(object) [1]],
            'negative' => [-1],
            'too-big'  => [9],
        ];
    }

    /**
     * @dataProvider invalidErrorStatuses
     */
    public function testRaisesExceptionOnInvalidErrorStatus($status)
    {
        $this->setExpectedException('InvalidArgumentException', 'status');

        new UploadedFile(fopen('php://temp', 'wb+'), 0, $status);
    }

    public function invalidFilenamesAndMediaTypes()
    {
        return [
            'true'   => [true],
            'false'  => [false],
            'int'    => [1],
            'float'  => [1.1],
            'array'  => [['string']],
            'object' => [(object) ['string']],
        ];
    }

    /**
     * @dataProvider invalidFilenamesAndMediaTypes
     */
    public function testRaisesExceptionOnInvalidClientFilename($filename)
    {
        $this->setExpectedException('InvalidArgumentException', 'filename');

        new UploadedFile(fopen('php://temp', 'wb+'), 0, UPLOAD_ERR_OK, $filename);
    }

    /**
     * @dataProvider invalidFilenamesAndMediaTypes
     */
    public function testRaisesExceptionOnInvalidClientMediaType($mediaType)
    {
        $this->setExpectedException('InvalidArgumentException', 'media type');

        new UploadedFile(fopen('php://temp', 'wb+'), 0, UPLOAD_ERR_OK, 'foobar.baz', $mediaType);
    }

    public function testGetStreamReturnsOriginalStreamObject()
    {
        $stream = new Stream(fopen('php://temp', 'r'));
        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);

        $this->assertSame($stream, $upload->getStream());
    }

    public function testGetStreamReturnsWrappedPhpStream()
    {
        $stream = fopen('php://temp', 'wb+');
        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
        $uploadStream = $upload->getStream()->detach();

        $this->assertSame($stream, $uploadStream);
    }

    public function testGetStreamReturnsStreamForFile()
    {
        $this->cleanup[] = $stream = tempnam(sys_get_temp_dir(), 'stream_file');
        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
        $uploadStream = $upload->getStream();
        $r = new ReflectionProperty($uploadStream, 'filename');
        $r->setAccessible(true);

        $this->assertSame($stream, $r->getValue($uploadStream));
    }

    public function testSuccessful()
    {
        $stream = \GuzzleHttp\Psr7\stream_for('Foo bar!');
        $upload = new UploadedFile($stream, $stream->getSize(), UPLOAD_ERR_OK, 'filename.txt', 'text/plain');

        $this->assertEquals($stream->getSize(), $upload->getSize());
        $this->assertEquals('filename.txt', $upload->getClientFilename());
        $this->assertEquals('text/plain', $upload->getClientMediaType());

        $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'successful');
        $upload->moveTo($to);
        $this->assertFileExists($to);
        $this->assertEquals($stream->__toString(), file_get_contents($to));
    }

    public function invalidMovePaths()
    {
        return [
            'null'   => [null],
            'true'   => [true],
            'false'  => [false],
            'int'    => [1],
            'float'  => [1.1],
            'empty'  => [''],
            'array'  => [['filename']],
            'object' => [(object) ['filename']],
        ];
    }

    /**
     * @dataProvider invalidMovePaths
     */
    public function testMoveRaisesExceptionForInvalidPath($path)
    {
        $stream = \GuzzleHttp\Psr7\stream_for('Foo bar!');
        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);

        $this->cleanup[] = $path;

        $this->setExpectedException('InvalidArgumentException', 'path');
        $upload->moveTo($path);
    }

    public function testMoveCannotBeCalledMoreThanOnce()
    {
        $stream = \GuzzleHttp\Psr7\stream_for('Foo bar!');
        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);

        $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'diac');
        $upload->moveTo($to);
        $this->assertTrue(file_exists($to));

        $this->setExpectedException('RuntimeException', 'moved');
        $upload->moveTo($to);
    }

    public function testCannotRetrieveStreamAfterMove()
    {
        $stream = \GuzzleHttp\Psr7\stream_for('Foo bar!');
        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);

        $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'diac');
        $upload->moveTo($to);
        $this->assertFileExists($to);

        $this->setExpectedException('RuntimeException', 'moved');
        $upload->getStream();
    }

    public function nonOkErrorStatus()
    {
        return [
            'UPLOAD_ERR_INI_SIZE'   => [ UPLOAD_ERR_INI_SIZE ],
            'UPLOAD_ERR_FORM_SIZE'  => [ UPLOAD_ERR_FORM_SIZE ],
            'UPLOAD_ERR_PARTIAL'    => [ UPLOAD_ERR_PARTIAL ],
            'UPLOAD_ERR_NO_FILE'    => [ UPLOAD_ERR_NO_FILE ],
            'UPLOAD_ERR_NO_TMP_DIR' => [ UPLOAD_ERR_NO_TMP_DIR ],
            'UPLOAD_ERR_CANT_WRITE' => [ UPLOAD_ERR_CANT_WRITE ],
            'UPLOAD_ERR_EXTENSION'  => [ UPLOAD_ERR_EXTENSION ],
        ];
    }

    /**
     * @dataProvider nonOkErrorStatus
     */
    public function testConstructorDoesNotRaiseExceptionForInvalidStreamWhenErrorStatusPresent($status)
    {
        $uploadedFile = new UploadedFile('not ok', 0, $status);
        $this->assertSame($status, $uploadedFile->getError());
    }

    /**
     * @dataProvider nonOkErrorStatus
     */
    public function testMoveToRaisesExceptionWhenErrorStatusPresent($status)
    {
        $uploadedFile = new UploadedFile('not ok', 0, $status);
        $this->setExpectedException('RuntimeException', 'upload error');
        $uploadedFile->moveTo(__DIR__ . '/' . uniqid());
    }

    /**
     * @dataProvider nonOkErrorStatus
     */
    public function testGetStreamRaisesExceptionWhenErrorStatusPresent($status)
    {
        $uploadedFile = new UploadedFile('not ok', 0, $status);
        $this->setExpectedException('RuntimeException', 'upload error');
        $stream = $uploadedFile->getStream();
    }

    public function testMoveToCreatesStreamIfOnlyAFilenameWasProvided()
    {
        $this->cleanup[] = $from = tempnam(sys_get_temp_dir(), 'copy_from');
        $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'copy_to');

        copy(__FILE__, $from);

        $uploadedFile = new UploadedFile($from, 100, UPLOAD_ERR_OK, basename($from), 'text/plain');
        $uploadedFile->moveTo($to);

        $this->assertFileEquals(__FILE__, $to);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\Uri;

/**
 * @covers GuzzleHttp\Psr7\Uri
 */
class UriTest extends \PHPUnit_Framework_TestCase
{
    const RFC3986_BASE = 'http://a/b/c/d;p?q';

    public function testParsesProvidedUri()
    {
        $uri = new Uri('https://user:pass@example.com:8080/path/123?q=abc#test');

        $this->assertSame('https', $uri->getScheme());
        $this->assertSame('user:pass@example.com:8080', $uri->getAuthority());
        $this->assertSame('user:pass', $uri->getUserInfo());
        $this->assertSame('example.com', $uri->getHost());
        $this->assertSame(8080, $uri->getPort());
        $this->assertSame('/path/123', $uri->getPath());
        $this->assertSame('q=abc', $uri->getQuery());
        $this->assertSame('test', $uri->getFragment());
        $this->assertSame('https://user:pass@example.com:8080/path/123?q=abc#test', (string) $uri);
    }

    public function testCanTransformAndRetrievePartsIndividually()
    {
        $uri = (new Uri())
            ->withScheme('https')
            ->withUserInfo('user', 'pass')
            ->withHost('example.com')
            ->withPort(8080)
            ->withPath('/path/123')
            ->withQuery('q=abc')
            ->withFragment('test');

        $this->assertSame('https', $uri->getScheme());
        $this->assertSame('user:pass@example.com:8080', $uri->getAuthority());
        $this->assertSame('user:pass', $uri->getUserInfo());
        $this->assertSame('example.com', $uri->getHost());
        $this->assertSame(8080, $uri->getPort());
        $this->assertSame('/path/123', $uri->getPath());
        $this->assertSame('q=abc', $uri->getQuery());
        $this->assertSame('test', $uri->getFragment());
        $this->assertSame('https://user:pass@example.com:8080/path/123?q=abc#test', (string) $uri);
    }

    /**
     * @dataProvider getValidUris
     */
    public function testValidUrisStayValid($input)
    {
        $uri = new Uri($input);

        $this->assertSame($input, (string) $uri);
    }

    /**
     * @dataProvider getValidUris
     */
    public function testFromParts($input)
    {
        $uri = Uri::fromParts(parse_url($input));

        $this->assertSame($input, (string) $uri);
    }

    public function getValidUris()
    {
        return [
            ['urn:path-rootless'],
            ['urn:path:with:colon'],
            ['urn:/path-absolute'],
            ['urn:/'],
            // only scheme with empty path
            ['urn:'],
            // only path
            ['/'],
            ['relative/'],
            ['0'],
            // same document reference
            [''],
            // network path without scheme
            ['//example.org'],
            ['//example.org/'],
            ['//example.org?q#h'],
            // only query
            ['?q'],
            ['?q=abc&foo=bar'],
            // only fragment
            ['#fragment'],
            // dot segments are not removed automatically
            ['./foo/../bar'],
        ];
    }

    /**
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Unable to parse URI
     * @dataProvider getInvalidUris
     */
    public function testInvalidUrisThrowException($invalidUri)
    {
        new Uri($invalidUri);
    }

    public function getInvalidUris()
    {
        return [
            // parse_url() requires the host component which makes sense for http(s)
            // but not when the scheme is not known or different. So '//' or '///' is
            // currently invalid as well but should not according to RFC 3986.
            ['http://'],
            ['urn://host:with:colon'], // host cannot contain ":"
        ];
    }

    /**
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Invalid port: 100000. Must be between 1 and 65535
     */
    public function testPortMustBeValid()
    {
        (new Uri())->withPort(100000);
    }

    /**
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Invalid port: 0. Must be between 1 and 65535
     */
    public function testWithPortCannotBeZero()
    {
        (new Uri())->withPort(0);
    }

    /**
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Unable to parse URI
     */
    public function testParseUriPortCannotBeZero()
    {
        new Uri('//example.com:0');
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testSchemeMustHaveCorrectType()
    {
        (new Uri())->withScheme([]);
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testHostMustHaveCorrectType()
    {
        (new Uri())->withHost([]);
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testPathMustHaveCorrectType()
    {
        (new Uri())->withPath([]);
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testQueryMustHaveCorrectType()
    {
        (new Uri())->withQuery([]);
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testFragmentMustHaveCorrectType()
    {
        (new Uri())->withFragment([]);
    }

    public function testCanParseFalseyUriParts()
    {
        $uri = new Uri('0://0:0@0/0?0#0');

        $this->assertSame('0', $uri->getScheme());
        $this->assertSame('0:0@0', $uri->getAuthority());
        $this->assertSame('0:0', $uri->getUserInfo());
        $this->assertSame('0', $uri->getHost());
        $this->assertSame('/0', $uri->getPath());
        $this->assertSame('0', $uri->getQuery());
        $this->assertSame('0', $uri->getFragment());
        $this->assertSame('0://0:0@0/0?0#0', (string) $uri);
    }

    public function testCanConstructFalseyUriParts()
    {
        $uri = (new Uri())
            ->withScheme('0')
            ->withUserInfo('0', '0')
            ->withHost('0')
            ->withPath('/0')
            ->withQuery('0')
            ->withFragment('0');

        $this->assertSame('0', $uri->getScheme());
        $this->assertSame('0:0@0', $uri->getAuthority());
        $this->assertSame('0:0', $uri->getUserInfo());
        $this->assertSame('0', $uri->getHost());
        $this->assertSame('/0', $uri->getPath());
        $this->assertSame('0', $uri->getQuery());
        $this->assertSame('0', $uri->getFragment());
        $this->assertSame('0://0:0@0/0?0#0', (string) $uri);
    }

    /**
     * @dataProvider getResolveTestCases
     */
    public function testResolvesUris($base, $rel, $expected)
    {
        $uri = new Uri($base);
        $actual = Uri::resolve($uri, $rel);
        $this->assertSame($expected, (string) $actual);
    }

    public function getResolveTestCases()
    {
        return [
            [self::RFC3986_BASE, 'g:h',           'g:h'],
            [self::RFC3986_BASE, 'g',             'http://a/b/c/g'],
            [self::RFC3986_BASE, './g',           'http://a/b/c/g'],
            [self::RFC3986_BASE, 'g/',            'http://a/b/c/g/'],
            [self::RFC3986_BASE, '/g',            'http://a/g'],
            [self::RFC3986_BASE, '//g',           'http://g'],
            [self::RFC3986_BASE, '?y',            'http://a/b/c/d;p?y'],
            [self::RFC3986_BASE, 'g?y',           'http://a/b/c/g?y'],
            [self::RFC3986_BASE, '#s',            'http://a/b/c/d;p?q#s'],
            [self::RFC3986_BASE, 'g#s',           'http://a/b/c/g#s'],
            [self::RFC3986_BASE, 'g?y#s',         'http://a/b/c/g?y#s'],
            [self::RFC3986_BASE, ';x',            'http://a/b/c/;x'],
            [self::RFC3986_BASE, 'g;x',           'http://a/b/c/g;x'],
            [self::RFC3986_BASE, 'g;x?y#s',       'http://a/b/c/g;x?y#s'],
            [self::RFC3986_BASE, '',              self::RFC3986_BASE],
            [self::RFC3986_BASE, '.',             'http://a/b/c/'],
            [self::RFC3986_BASE, './',            'http://a/b/c/'],
            [self::RFC3986_BASE, '..',            'http://a/b/'],
            [self::RFC3986_BASE, '../',           'http://a/b/'],
            [self::RFC3986_BASE, '../g',          'http://a/b/g'],
            [self::RFC3986_BASE, '../..',         'http://a/'],
            [self::RFC3986_BASE, '../../',        'http://a/'],
            [self::RFC3986_BASE, '../../g',       'http://a/g'],
            [self::RFC3986_BASE, '../../../g',    'http://a/g'],
            [self::RFC3986_BASE, '../../../../g', 'http://a/g'],
            [self::RFC3986_BASE, '/./g',          'http://a/g'],
            [self::RFC3986_BASE, '/../g',         'http://a/g'],
            [self::RFC3986_BASE, 'g.',            'http://a/b/c/g.'],
            [self::RFC3986_BASE, '.g',            'http://a/b/c/.g'],
            [self::RFC3986_BASE, 'g..',           'http://a/b/c/g..'],
            [self::RFC3986_BASE, '..g',           'http://a/b/c/..g'],
            [self::RFC3986_BASE, './../g',        'http://a/b/g'],
            [self::RFC3986_BASE, 'foo////g',      'http://a/b/c/foo////g'],
            [self::RFC3986_BASE, './g/.',         'http://a/b/c/g/'],
            [self::RFC3986_BASE, 'g/./h',         'http://a/b/c/g/h'],
            [self::RFC3986_BASE, 'g/../h',        'http://a/b/c/h'],
            [self::RFC3986_BASE, 'g;x=1/./y',     'http://a/b/c/g;x=1/y'],
            [self::RFC3986_BASE, 'g;x=1/../y',    'http://a/b/c/y'],
            // dot-segments in the query or fragment
            [self::RFC3986_BASE, 'g?y/./x',       'http://a/b/c/g?y/./x'],
            [self::RFC3986_BASE, 'g?y/../x',      'http://a/b/c/g?y/../x'],
            [self::RFC3986_BASE, 'g#s/./x',       'http://a/b/c/g#s/./x'],
            [self::RFC3986_BASE, 'g#s/../x',      'http://a/b/c/g#s/../x'],
            [self::RFC3986_BASE, 'g#s/../x',      'http://a/b/c/g#s/../x'],
            [self::RFC3986_BASE, '?y#s',          'http://a/b/c/d;p?y#s'],
            ['http://a/b/c/d;p?q#s', '?y',        'http://a/b/c/d;p?y'],
            ['http://u@a/b/c/d;p?q', '.',         'http://u@a/b/c/'],
            ['http://u:p@a/b/c/d;p?q', '.',       'http://u:p@a/b/c/'],
            ['http://a/b/c/d/', 'e',              'http://a/b/c/d/e'],
            ['urn:no-slash', 'e',                 'urn:e'],
            // falsey relative parts
            [self::RFC3986_BASE, '//0',           'http://0'],
            [self::RFC3986_BASE, '0',             'http://a/b/c/0'],
            [self::RFC3986_BASE, '?0',            'http://a/b/c/d;p?0'],
            [self::RFC3986_BASE, '#0',            'http://a/b/c/d;p?q#0'],
        ];
    }

    public function testAddAndRemoveQueryValues()
    {
        $uri = new Uri();
        $uri = Uri::withQueryValue($uri, 'a', 'b');
        $uri = Uri::withQueryValue($uri, 'c', 'd');
        $uri = Uri::withQueryValue($uri, 'e', null);
        $this->assertSame('a=b&c=d&e', $uri->getQuery());

        $uri = Uri::withoutQueryValue($uri, 'c');
        $this->assertSame('a=b&e', $uri->getQuery());
        $uri = Uri::withoutQueryValue($uri, 'e');
        $this->assertSame('a=b', $uri->getQuery());
        $uri = Uri::withoutQueryValue($uri, 'a');
        $this->assertSame('', $uri->getQuery());
    }

    public function testWithQueryValueReplacesSameKeys()
    {
        $uri = new Uri();
        $uri = Uri::withQueryValue($uri, 'a', 'b');
        $uri = Uri::withQueryValue($uri, 'c', 'd');
        $uri = Uri::withQueryValue($uri, 'a', 'e');
        $this->assertSame('c=d&a=e', $uri->getQuery());
    }

    public function testWithoutQueryValueRemovesAllSameKeys()
    {
        $uri = (new Uri())->withQuery('a=b&c=d&a=e');
        $uri = Uri::withoutQueryValue($uri, 'a');
        $this->assertSame('c=d', $uri->getQuery());
    }

    public function testRemoveNonExistingQueryValue()
    {
        $uri = new Uri();
        $uri = Uri::withQueryValue($uri, 'a', 'b');
        $uri = Uri::withoutQueryValue($uri, 'c');
        $this->assertSame('a=b', $uri->getQuery());
    }

    public function testWithQueryValueHandlesEncoding()
    {
        $uri = new Uri();
        $uri = Uri::withQueryValue($uri, 'E=mc^2', 'ein&stein');
        $this->assertSame('E%3Dmc%5E2=ein%26stein', $uri->getQuery(), 'Decoded key/value get encoded');

        $uri = new Uri();
        $uri = Uri::withQueryValue($uri, 'E%3Dmc%5e2', 'ein%26stein');
        $this->assertSame('E%3Dmc%5e2=ein%26stein', $uri->getQuery(), 'Encoded key/value do not get double-encoded');
    }

    public function testWithoutQueryValueHandlesEncoding()
    {
        // It also tests that the case of the percent-encoding does not matter,
        // i.e. both lowercase "%3d" and uppercase "%5E" can be removed.
        $uri = (new Uri())->withQuery('E%3dmc%5E2=einstein&foo=bar');
        $uri = Uri::withoutQueryValue($uri, 'E=mc^2');
        $this->assertSame('foo=bar', $uri->getQuery(), 'Handles key in decoded form');

        $uri = (new Uri())->withQuery('E%3dmc%5E2=einstein&foo=bar');
        $uri = Uri::withoutQueryValue($uri, 'E%3Dmc%5e2');
        $this->assertSame('foo=bar', $uri->getQuery(), 'Handles key in encoded form');
    }

    public function testSchemeIsNormalizedToLowercase()
    {
        $uri = new Uri('HTTP://example.com');

        $this->assertSame('http', $uri->getScheme());
        $this->assertSame('http://example.com', (string) $uri);

        $uri = (new Uri('//example.com'))->withScheme('HTTP');

        $this->assertSame('http', $uri->getScheme());
        $this->assertSame('http://example.com', (string) $uri);
    }

    public function testHostIsNormalizedToLowercase()
    {
        $uri = new Uri('//eXaMpLe.CoM');

        $this->assertSame('example.com', $uri->getHost());
        $this->assertSame('//example.com', (string) $uri);

        $uri = (new Uri())->withHost('eXaMpLe.CoM');

        $this->assertSame('example.com', $uri->getHost());
        $this->assertSame('//example.com', (string) $uri);
    }

    public function testPortIsNullIfStandardPortForScheme()
    {
        // HTTPS standard port
        $uri = new Uri('https://example.com:443');
        $this->assertNull($uri->getPort());
        $this->assertSame('example.com', $uri->getAuthority());

        $uri = (new Uri('https://example.com'))->withPort(443);
        $this->assertNull($uri->getPort());
        $this->assertSame('example.com', $uri->getAuthority());

        // HTTP standard port
        $uri = new Uri('http://example.com:80');
        $this->assertNull($uri->getPort());
        $this->assertSame('example.com', $uri->getAuthority());

        $uri = (new Uri('http://example.com'))->withPort(80);
        $this->assertNull($uri->getPort());
        $this->assertSame('example.com', $uri->getAuthority());
    }

    public function testPortIsReturnedIfSchemeUnknown()
    {
        $uri = (new Uri('//example.com'))->withPort(80);

        $this->assertSame(80, $uri->getPort());
        $this->assertSame('example.com:80', $uri->getAuthority());
    }

    public function testStandardPortIsNullIfSchemeChanges()
    {
        $uri = new Uri('http://example.com:443');
        $this->assertSame('http', $uri->getScheme());
        $this->assertSame(443, $uri->getPort());

        $uri = $uri->withScheme('https');
        $this->assertNull($uri->getPort());
    }

    public function testPortPassedAsStringIsCastedToInt()
    {
        $uri = (new Uri('//example.com'))->withPort('8080');

        $this->assertSame(8080, $uri->getPort(), 'Port is returned as integer');
        $this->assertSame('example.com:8080', $uri->getAuthority());
    }

    public function testPortCanBeRemoved()
    {
        $uri = (new Uri('http://example.com:8080'))->withPort(null);

        $this->assertNull($uri->getPort());
        $this->assertSame('http://example.com', (string) $uri);
    }

    public function testAuthorityWithUserInfoButWithoutHost()
    {
        $uri = (new Uri())->withUserInfo('user', 'pass');

        $this->assertSame('user:pass', $uri->getUserInfo());
        $this->assertSame('', $uri->getAuthority());
    }

    public function uriComponentsEncodingProvider()
    {
        $unreserved = 'a-zA-Z0-9.-_~!$&\'()*+,;=:@';

        return [
            // Percent encode spaces
            ['/pa th?q=va lue#frag ment', '/pa%20th', 'q=va%20lue', 'frag%20ment', '/pa%20th?q=va%20lue#frag%20ment'],
            // Percent encode multibyte
            ['/€?€#€', '/%E2%82%AC', '%E2%82%AC', '%E2%82%AC', '/%E2%82%AC?%E2%82%AC#%E2%82%AC'],
            // Don't encode something that's already encoded
            ['/pa%20th?q=va%20lue#frag%20ment', '/pa%20th', 'q=va%20lue', 'frag%20ment', '/pa%20th?q=va%20lue#frag%20ment'],
            // Percent encode invalid percent encodings
            ['/pa%2-th?q=va%2-lue#frag%2-ment', '/pa%252-th', 'q=va%252-lue', 'frag%252-ment', '/pa%252-th?q=va%252-lue#frag%252-ment'],
            // Don't encode path segments
            ['/pa/th//two?q=va/lue#frag/ment', '/pa/th//two', 'q=va/lue', 'frag/ment', '/pa/th//two?q=va/lue#frag/ment'],
            // Don't encode unreserved chars or sub-delimiters
            ["/$unreserved?$unreserved#$unreserved", "/$unreserved", $unreserved, $unreserved, "/$unreserved?$unreserved#$unreserved"],
            // Encoded unreserved chars are not decoded
            ['/p%61th?q=v%61lue#fr%61gment', '/p%61th', 'q=v%61lue', 'fr%61gment', '/p%61th?q=v%61lue#fr%61gment'],
        ];
    }

    /**
     * @dataProvider uriComponentsEncodingProvider
     */
    public function testUriComponentsGetEncodedProperly($input, $path, $query, $fragment, $output)
    {
        $uri = new Uri($input);
        $this->assertSame($path, $uri->getPath());
        $this->assertSame($query, $uri->getQuery());
        $this->assertSame($fragment, $uri->getFragment());
        $this->assertSame($output, (string) $uri);
    }

    public function testWithPathEncodesProperly()
    {
        $uri = (new Uri())->withPath('/baz?#€/b%61r');
        // Query and fragment delimiters and multibyte chars are encoded.
        $this->assertSame('/baz%3F%23%E2%82%AC/b%61r', $uri->getPath());
        $this->assertSame('/baz%3F%23%E2%82%AC/b%61r', (string) $uri);
    }

    public function testWithQueryEncodesProperly()
    {
        $uri = (new Uri())->withQuery('?=#&€=/&b%61r');
        // A query starting with a "?" is valid and must not be magically removed. Otherwise it would be impossible to
        // construct such an URI. Also the "?" and "/" does not need to be encoded in the query.
        $this->assertSame('?=%23&%E2%82%AC=/&b%61r', $uri->getQuery());
        $this->assertSame('??=%23&%E2%82%AC=/&b%61r', (string) $uri);
    }

    public function testWithFragmentEncodesProperly()
    {
        $uri = (new Uri())->withFragment('#€?/b%61r');
        // A fragment starting with a "#" is valid and must not be magically removed. Otherwise it would be impossible to
        // construct such an URI. Also the "?" and "/" does not need to be encoded in the fragment.
        $this->assertSame('%23%E2%82%AC?/b%61r', $uri->getFragment());
        $this->assertSame('#%23%E2%82%AC?/b%61r', (string) $uri);
    }

    public function testAllowsForRelativeUri()
    {
        $uri = (new Uri)->withPath('foo');
        $this->assertSame('foo', $uri->getPath());
        $this->assertSame('foo', (string) $uri);
    }

    public function testAddsSlashForRelativeUriStringWithHost()
    {
        // If the path is rootless and an authority is present, the path MUST
        // be prefixed by "/".
        $uri = (new Uri)->withPath('foo')->withHost('example.com');
        $this->assertSame('foo', $uri->getPath());
        // concatenating a relative path with a host doesn't work: "//example.comfoo" would be wrong
        $this->assertSame('//example.com/foo', (string) $uri);
    }

    public function testRemoveExtraSlashesWihoutHost()
    {
        // If the path is starting with more than one "/" and no authority is
        // present, the starting slashes MUST be reduced to one.
        $uri = (new Uri)->withPath('//foo');
        $this->assertSame('//foo', $uri->getPath());
        // URI "//foo" would be interpreted as network reference and thus change the original path to the host
        $this->assertSame('/foo', (string) $uri);
    }

    public function testDefaultReturnValuesOfGetters()
    {
        $uri = new Uri();

        $this->assertSame('', $uri->getScheme());
        $this->assertSame('', $uri->getAuthority());
        $this->assertSame('', $uri->getUserInfo());
        $this->assertSame('', $uri->getHost());
        $this->assertNull($uri->getPort());
        $this->assertSame('', $uri->getPath());
        $this->assertSame('', $uri->getQuery());
        $this->assertSame('', $uri->getFragment());
    }

    public function testImmutability()
    {
        $uri = new Uri();

        $this->assertNotSame($uri, $uri->withScheme('https'));
        $this->assertNotSame($uri, $uri->withUserInfo('user', 'pass'));
        $this->assertNotSame($uri, $uri->withHost('example.com'));
        $this->assertNotSame($uri, $uri->withPort(8080));
        $this->assertNotSame($uri, $uri->withPath('/path/123'));
        $this->assertNotSame($uri, $uri->withQuery('q=abc'));
        $this->assertNotSame($uri, $uri->withFragment('test'));
    }

    public function testExtendingClassesInstantiates()
    {
        // The non-standard port triggers a cascade of private methods which
        //  should not use late static binding to access private static members.
        // If they do, this will fatal.
        $this->assertInstanceOf(
            '\GuzzleHttp\Tests\Psr7\ExtendingClassTest',
            new ExtendingClassTest('http://h:9/')
        );
    }
}

class ExtendingClassTest extends \GuzzleHttp\Psr7\Uri
{
}
<?php
namespace Mailgun\Connection\Exceptions;

class GenericHTTPError extends \Exception
{
	protected $httpResponseCode;
	protected $httpResponseBody;
	
	public function __construct($message=null, $response_code=null, $response_body=null, $code=0, \Exception $previous=null) {
		parent::__construct($message, $code, $previous);
		
		$this->httpResponseCode = $response_code;
		$this->httpResponseBody = $response_body;
	}
	
	public function getHttpResponseCode() {
		return $this->httpResponseCode;
	}
	
	public function getHttpResponseBody() {
		return $this->httpResponseBody;
	}
}

?>
<?php
namespace Mailgun\Connection\Exceptions;

class InvalidCredentials extends \Exception{}
<?php
namespace Mailgun\Connection\Exceptions;

class MissingEndpoint extends \Exception{}
<?php
namespace Mailgun\Connection\Exceptions;

class MissingRequiredParameters extends \Exception{}
<?php
namespace Mailgun\Connection\Exceptions;

class NoDomainsConfigured extends \Exception{}
<?PHP

namespace Mailgun\Connection;

use GuzzleHttp\Psr7\MultipartStream;
use GuzzleHttp\Psr7\Request;
use Http\Client\HttpClient;
use Http\Discovery\HttpClientDiscovery;
use Mailgun\Connection\Exceptions\GenericHTTPError;
use Mailgun\Connection\Exceptions\InvalidCredentials;
use Mailgun\Connection\Exceptions\MissingRequiredParameters;
use Mailgun\Connection\Exceptions\MissingEndpoint;
use Mailgun\Constants\Api;
use Mailgun\Constants\ExceptionMessages;
use Psr\Http\Message\ResponseInterface;

/**
 * This class is a wrapper for the Guzzle (HTTP Client Library).
 */
class RestClient
{
    /**
     * Your API key
     * @var string
     */
    private $apiKey;

    /**
     * @var HttpClient
     */
    protected $httpClient;

    /**
     * @var string
     */
    protected $apiHost;

    /**
     * The version of the API to use
     * @var string
     */
    protected $apiVersion = 'v2';

    /**
     * If we should use SSL or not
     * @var bool
     */
    protected $sslEnabled = true;

    /**
     * @param string      $apiKey
     * @param string      $apiHost
     * @param HttpClient  $httpClient
     */
    public function __construct($apiKey, $apiHost, HttpClient $httpClient = null)
    {
        $this->apiKey = $apiKey;
        $this->apiHost = $apiHost;
        $this->httpClient = $httpClient;
    }

    /**
     * @param string $method
     * @param string $uri
     * @param array  $body
     * @param array  $files
     * @param array  $headers
     *
     * @return \stdClass
     *
     * @throws GenericHTTPError
     * @throws InvalidCredentials
     * @throws MissingEndpoint
     * @throws MissingRequiredParameters
     */
    protected function send($method, $uri, $body = null, $files = [], array $headers = [])
    {
        $headers['User-Agent'] = Api::SDK_USER_AGENT.'/'.Api::SDK_VERSION;
        $headers['Authorization'] = 'Basic '.base64_encode(sprintf('%s:%s', Api::API_USER, $this->apiKey));

        if (!empty($files)) {
            $body = new MultipartStream($files);
            $headers['Content-Type'] = 'multipart/form-data; boundary='.$body->getBoundary();
        }

        $request = new Request($method, $this->getApiUrl($uri), $headers, $body);
        $response = $this->getHttpClient()->sendRequest($request);

        return $this->responseHandler($response);
    }

    /**
     * @param string $endpointUrl
     * @param array  $postData
     * @param array  $files
     *
     * @return \stdClass
     *
     * @throws GenericHTTPError
     * @throws InvalidCredentials
     * @throws MissingEndpoint
     * @throws MissingRequiredParameters
     */
    public function post($endpointUrl, $postData = array(), $files = array())
    {
        $postFiles = [];

        $fields = ['message', 'attachment', 'inline'];
        foreach ($fields as $fieldName) {
            if (isset($files[$fieldName])) {
                if (is_array($files[$fieldName])) {
                    foreach ($files[$fieldName] as $file) {
                        $postFiles[] = $this->prepareFile($fieldName, $file);
                    }
                } else {
                    $postFiles[] = $this->prepareFile($fieldName, $files[$fieldName]);
                }
            }
        }

        $postDataMultipart = [];
        foreach ($postData as $key => $value) {
            if (is_array($value)) {
                foreach ($value as $subValue) {
                    $postDataMultipart[] = [
                        'name' => $key,
                        'contents' => $subValue,
                    ];
                }
            } else {
                $postDataMultipart[] = [
                    'name' => $key,
                    'contents' => $value,
                ];
            }
        }

        return $this->send('POST', $endpointUrl, [], array_merge($postDataMultipart, $postFiles));
    }

    /**
     * @param string $endpointUrl
     * @param array  $queryString
     *
     * @return \stdClass
     *
     * @throws GenericHTTPError
     * @throws InvalidCredentials
     * @throws MissingEndpoint
     * @throws MissingRequiredParameters
     */
    public function get($endpointUrl, $queryString = array())
    {
        return $this->send('GET', $endpointUrl.'?'.http_build_query($queryString));
    }

    /**
     * @param string $endpointUrl
     *
     * @return \stdClass
     *
     * @throws GenericHTTPError
     * @throws InvalidCredentials
     * @throws MissingEndpoint
     * @throws MissingRequiredParameters
     */
    public function delete($endpointUrl)
    {
        return $this->send('DELETE', $endpointUrl);
    }

    /**
     * @param string $endpointUrl
     * @param array  $putData
     *
     * @return \stdClass
     *
     * @throws GenericHTTPError
     * @throws InvalidCredentials
     * @throws MissingEndpoint
     * @throws MissingRequiredParameters
     */
    public function put($endpointUrl, $putData)
    {
        return $this->send('PUT', $endpointUrl, $putData);
    }

    /**
     * @param ResponseInterface $responseObj
     *
     * @return \stdClass
     *
     * @throws GenericHTTPError
     * @throws InvalidCredentials
     * @throws MissingEndpoint
     * @throws MissingRequiredParameters
     */
    public function responseHandler(ResponseInterface $responseObj)
    {
        $httpResponseCode = $responseObj->getStatusCode();
        if ($httpResponseCode === 200) {
            $data = (string) $responseObj->getBody();
            $jsonResponseData = json_decode($data, false);
            $result = new \stdClass();
            // return response data as json if possible, raw if not
            $result->http_response_body = $data && $jsonResponseData === null ? $data : $jsonResponseData;
        } elseif ($httpResponseCode == 400) {
            throw new MissingRequiredParameters(ExceptionMessages::EXCEPTION_MISSING_REQUIRED_PARAMETERS.$this->getResponseExceptionMessage($responseObj));
        } elseif ($httpResponseCode == 401) {
            throw new InvalidCredentials(ExceptionMessages::EXCEPTION_INVALID_CREDENTIALS);
        } elseif ($httpResponseCode == 404) {
            throw new MissingEndpoint(ExceptionMessages::EXCEPTION_MISSING_ENDPOINT.$this->getResponseExceptionMessage($responseObj));
        } else {
            throw new GenericHTTPError(ExceptionMessages::EXCEPTION_GENERIC_HTTP_ERROR, $httpResponseCode, $responseObj->getBody());
        }
        $result->http_response_code = $httpResponseCode;

        return $result;
    }

    /**
     * @param ResponseInterface $responseObj
     *
     * @return string
     */
    protected function getResponseExceptionMessage(ResponseInterface $responseObj)
    {
        $body = (string) $responseObj->getBody();
        $response = json_decode($body);
        if (json_last_error() == JSON_ERROR_NONE && isset($response->message)) {
            return ' '.$response->message;
        }
    }

    /**
     * Prepare a file for the postBody.
     *
     * @param string       $fieldName
     * @param string|array $filePath
     */
    protected function prepareFile($fieldName, $filePath)
    {
        $filename = null;
        // Backward compatibility code
        if (is_array($filePath)) {
            $filename = $filePath['remoteName'];
            $filePath = $filePath['filePath'];
        }

        // Remove leading @ symbol
        if (strpos($filePath, '@') === 0) {
            $filePath = substr($filePath, 1);
        }

        return [
            'name' => $fieldName,
            'contents' => fopen($filePath, 'r'),
            'filename' => $filename,
        ];
    }


    /**
     *
     * @return HttpClient
     */
    protected function getHttpClient()
    {
        if ($this->httpClient === null) {
            $this->httpClient = HttpClientDiscovery::find();
        }

        return $this->httpClient;
    }

    /**
     * @param $uri
     *
     * @return string
     */
    private function getApiUrl($uri)
    {
        return $this->generateEndpoint($this->apiHost, $this->apiVersion, $this->sslEnabled).$uri;
    }


    /**
     * @param string $apiEndpoint
     * @param string $apiVersion
     * @param bool   $ssl
     *
     * @return string
     */
    private function generateEndpoint($apiEndpoint, $apiVersion, $ssl)
    {
        if (!$ssl) {
            return 'http://'.$apiEndpoint.'/'.$apiVersion.'/';
        } else {
            return 'https://'.$apiEndpoint.'/'.$apiVersion.'/';
        }
    }

    /**
     * @param string $apiVersion
     *
     * @return RestClient
     */
    public function setApiVersion($apiVersion)
    {
        $this->apiVersion = $apiVersion;

        return $this;
    }

    /**
     * @param boolean $sslEnabled
     *
     * @return RestClient
     */
    public function setSslEnabled($sslEnabled)
    {
        $this->sslEnabled = $sslEnabled;

        return $this;
    }
}
<?php


namespace Mailgun\Constants;


class Api {
    const API_USER = "api";
    const SDK_VERSION = "1.7";
    const SDK_USER_AGENT = "mailgun-sdk-php";
    const RECIPIENT_COUNT_LIMIT = 1000;
    const CAMPAIGN_ID_LIMIT = 3;
    const TAG_LIMIT = 3;
    const DEFAULT_TIME_ZONE = "UTC";
}
 <?php


namespace Mailgun\Constants;


class ExceptionMessages {
    const EXCEPTION_INVALID_CREDENTIALS = "Your credentials are incorrect.";
    const EXCEPTION_GENERIC_HTTP_ERROR = "An HTTP Error has occurred! Check your network connection and try again.";
    const EXCEPTION_MISSING_REQUIRED_PARAMETERS = "The parameters passed to the API were invalid. Check your inputs!";
    const EXCEPTION_MISSING_REQUIRED_MIME_PARAMETERS = "The parameters passed to the API were invalid. Check your inputs!";
    const EXCEPTION_MISSING_ENDPOINT = "The endpoint you've tried to access does not exist. Check your URL.";
    const TOO_MANY_RECIPIENTS = "You've exceeded the maximum recipient count (1,000) on the to field with autosend disabled.";
    const INVALID_PARAMETER_NON_ARRAY = "The parameter you've passed in position 2 must be an array.";
    const INVALID_PARAMETER_ATTACHMENT = "Attachments must be passed with an \"@\" preceding the file path. Web resources not supported.";
    const INVALID_PARAMETER_INLINE = "Inline images must be passed with an \"@\" preceding the file path. Web resources not supported.";
    const TOO_MANY_PARAMETERS_CAMPAIGNS = "You've exceeded the maximum (3) campaigns for a single message.";
    const TOO_MANY_PARAMETERS_TAGS = "You've exceeded the maximum (3) tags for a single message.";
    const TOO_MANY_PARAMETERS_RECIPIENT = "You've exceeded the maximum recipient count (1,000) on the to field with autosend disabled.";
}
 <?PHP

namespace Mailgun\Lists;

use Mailgun\Messages\Exceptions\InvalidParameter;
use Mailgun\Messages\Exceptions\TooManyParameters;
use Mailgun\Messages\Expcetions\InvalidParameterType;

/**
 * This class is used for creating a unique hash for
 * mailing list subscription double-opt in requests.
 *
 * @link https://github.com/mailgun/mailgun-php/blob/master/src/Mailgun/Lists/README.md
 */
class OptInHandler{

    /**
     * @param string $mailingList
     * @param string $secretAppId
     * @param string $recipientAddress
     * @return string
     */
	public function generateHash($mailingList, $secretAppId, $recipientAddress){
		$innerPayload = array('r' => $recipientAddress, 'l' => $mailingList);
		$encodedInnerPayload = base64_encode(json_encode($innerPayload));

		$innerHash = hash_hmac("sha1", $encodedInnerPayload, $secretAppId);
		$outerPayload = array('h' => $innerHash, 'p' => $encodedInnerPayload);

		return urlencode(base64_encode(json_encode($outerPayload)));
	}

    /**
     * @param string $secretAppId
     * @param string $uniqueHash
     * @return array|bool
     */
	public function validateHash($secretAppId, $uniqueHash){
		$decodedOuterPayload = json_decode(base64_decode(urldecode($uniqueHash)), true);

		$decodedHash = $decodedOuterPayload['h'];
		$innerPayload = $decodedOuterPayload['p'];

		$decodedInnerPayload = json_decode(base64_decode($innerPayload), true);
		$computedInnerHash = hash_hmac("sha1", $innerPayload, $secretAppId);

		if($computedInnerHash == $decodedHash){
			return array('recipientAddress' => $decodedInnerPayload['r'], 'mailingList' => $decodedInnerPayload['l']);
		}

		return false;
	}
}
<?PHP

namespace Mailgun;

use Http\Client\HttpClient;
use Mailgun\Constants\ExceptionMessages;
use Mailgun\Messages\Exceptions;
use Mailgun\Connection\RestClient;
use Mailgun\Messages\BatchMessage;
use Mailgun\Lists\OptInHandler;
use Mailgun\Messages\MessageBuilder;

/**
 * This class is the base class for the Mailgun SDK.
 * See the official documentation (link below) for usage instructions.
 *
 * @link https://github.com/mailgun/mailgun-php/blob/master/README.md
 */
class Mailgun{

    /**
     * @var RestClient
     */
    protected $restClient;

    /**
     * @var null|string
     */
    protected $apiKey;

    /**
     * @param string|null $apiKey
     * @param HttpClient $httpClient
     * @param string $apiEndpoint
     */
    public function __construct(
        $apiKey = null,
        HttpClient $httpClient = null,
        $apiEndpoint = 'api.mailgun.net'
    ) {
        $this->apiKey = $apiKey;
        $this->restClient = new RestClient($apiKey, $apiEndpoint, $httpClient);
    }

    /**
     *  This function allows the sending of a fully formed message OR a custom
     *  MIME string. If sending MIME, the string must be passed in to the 3rd
     *  position of the function call.
     *
     * @param string $workingDomain
     * @param array $postData
     * @param array $postFiles
     * @throws Exceptions\MissingRequiredMIMEParameters
     */
    public function sendMessage($workingDomain, $postData, $postFiles = array()){
        if(is_array($postFiles)){
            return $this->post("$workingDomain/messages", $postData, $postFiles);
        }
        else if(is_string($postFiles)){

            $tempFile = tempnam(sys_get_temp_dir(), "MG_TMP_MIME");
            $fileHandle = fopen($tempFile, "w");
            fwrite($fileHandle, $postFiles);

            $result = $this->post("$workingDomain/messages.mime", $postData, array("message" => $tempFile));
            fclose($fileHandle);
            unlink($tempFile);
            return $result;
        }
        else{
            throw new Exceptions\MissingRequiredMIMEParameters(ExceptionMessages::EXCEPTION_MISSING_REQUIRED_MIME_PARAMETERS);
        }
    }

    /**
     * This function checks the signature in a POST request to see if it is
     * authentic.
     *
     * Pass an array of parameters.  If you pass nothing, $_POST will be
     * used instead.
     *
     * If this function returns FALSE, you must not process the request.
     * You should reject the request with status code 403 Forbidden.
     *
     * @param array|null $postData
     * @return bool
     */
    public function verifyWebhookSignature($postData = NULL) {
        if(is_null($postData)) {
            $postData = $_POST;
        }
        $hmac = hash_hmac('sha256', "{$postData["timestamp"]}{$postData["token"]}", $this->apiKey);
        $sig = $postData['signature'];
        if(function_exists('hash_equals')) {
            // hash_equals is constant time, but will not be introduced until PHP 5.6
            return hash_equals($hmac, $sig);
        }
        else {
            return ($hmac == $sig);
        }
    }

    /**
     * @param string $endpointUrl
     * @param array $postData
     * @param array $files
     * @return \stdClass
     */
    public function post($endpointUrl, $postData = array(), $files = array()){
        return $this->restClient->post($endpointUrl, $postData, $files);
    }

    /**
     * @param string $endpointUrl
     * @param array $queryString
     * @return \stdClass
     */
    public function get($endpointUrl, $queryString = array()){
        return $this->restClient->get($endpointUrl, $queryString);
    }

    /**
     * @param string $endpointUrl
     * @return \stdClass
     */
    public function delete($endpointUrl){
        return $this->restClient->delete($endpointUrl);
    }

    /**
     * @param string $endpointUrl
     * @param array $putData
     * @return \stdClass
     */
    public function put($endpointUrl, $putData){
        return $this->restClient->put($endpointUrl, $putData);
    }

    /**
     * @param string $apiVersion
     *
     * @return Mailgun
     */
    public function setApiVersion($apiVersion)
    {
        $this->restClient->setApiVersion($apiVersion);

        return $this;
    }

    /**
     * @param boolean $sslEnabled
     *
     * @return Mailgun
     */
    public function setSslEnabled($sslEnabled)
    {
        $this->restClient->setSslEnabled($sslEnabled);

        return $this;
    }

    /**
     * @return MessageBuilder
     */
    public function MessageBuilder(){
        return new MessageBuilder();
    }

    /**
     * @return OptInHandler
     */
    public function OptInHandler(){
        return new OptInHandler();
    }

    /**
     * @param string $workingDomain
     * @param bool $autoSend
     * @return BatchMessage
     */
    public function BatchMessage($workingDomain, $autoSend = true){
        return new BatchMessage($this->restClient, $workingDomain, $autoSend);
    }
}
<?PHP

namespace Mailgun\Messages;

use Mailgun\Constants\Api;
use Mailgun\Constants\ExceptionMessages;
use Mailgun\Messages\Exceptions\TooManyParameters;
use Mailgun\Messages\Exceptions\MissingRequiredMIMEParameters;

/**
 * This class is used for batch sending. See the official documentation (link below)
 * for usage instructions.
 *
 * @link https://github.com/mailgun/mailgun-php/blob/master/src/Mailgun/Messages/README.md
 */
class BatchMessage extends MessageBuilder{

    /**
     * @var array
     */
	private $batchRecipientAttributes;

    /**
     * @var boolean
     */
	private $autoSend;

    /**
     * @var \Mailgun\Connection\RestClient
     */
	private $restClient;

    /**
     * @var string
     */
	private $workingDomain;

    /**
     * @var array
     */
	private $messageIds = array();

    /**
     * @param \Mailgun\Connection\RestClient $restClient
     * @param string $workingDomain
     * @param boolean $autoSend
     */
	public function __construct($restClient, $workingDomain, $autoSend){
		$this->batchRecipientAttributes = array();
		$this->autoSend = $autoSend;
		$this->restClient = $restClient;
		$this->workingDomain = $workingDomain;
		$this->endpointUrl = $workingDomain . "/messages";
	}

    /**
     * @param string $headerName
     * @param string $address
     * @param array $variables
     * @throws MissingRequiredMIMEParameters
     * @throws TooManyParameters
     */
	protected function addRecipient($headerName, $address, $variables){
		if(array_key_exists($headerName, $this->counters['recipients'])){
			if($this->counters['recipients'][$headerName] == Api::RECIPIENT_COUNT_LIMIT){
				if($this->autoSend == false){
					throw new TooManyParameters(ExceptionMessages::TOO_MANY_RECIPIENTS);
				}
				$this->sendMessage();
			}
		}

		$compiledAddress = $this->parseAddress($address, $variables);

		if(isset($this->message[$headerName])){
			array_push($this->message[$headerName], $compiledAddress);
		}
		elseif($headerName == "h:reply-to"){
			$this->message[$headerName] = $compiledAddress;
		}
		else{
			$this->message[$headerName] = array($compiledAddress);
		}

		if(array_key_exists($headerName, $this->counters['recipients'])){
			$this->counters['recipients'][$headerName] += 1;
			if(!array_key_exists("id", $variables)){
				$variables['id'] = $this->counters['recipients'][$headerName];
			}
		}
		$this->batchRecipientAttributes["$address"] = $variables;
	}

    /**
     * @param array $message
     * @param array $files
     * @throws MissingRequiredMIMEParameters
     */
	public function sendMessage($message = array(), $files = array()){
		if(count($message) < 1){
			$message = $this->message;
			$files = $this->files;
		}
		if(!array_key_exists("from", $message)){
			throw new MissingRequiredMIMEParameters(ExceptionMessages::EXCEPTION_MISSING_REQUIRED_MIME_PARAMETERS);
		}
		elseif(!array_key_exists("to", $message)){
			throw new MissingRequiredMIMEParameters(ExceptionMessages::EXCEPTION_MISSING_REQUIRED_MIME_PARAMETERS);
		}
		elseif(!array_key_exists("subject", $message)){
			throw new MissingRequiredMIMEParameters(ExceptionMessages::EXCEPTION_MISSING_REQUIRED_MIME_PARAMETERS);
		}
		elseif((!array_key_exists("text", $message) && !array_key_exists("html", $message))){
			throw new MissingRequiredMIMEParameters(ExceptionMessages::EXCEPTION_MISSING_REQUIRED_MIME_PARAMETERS);
		}
		else{
			$message["recipient-variables"] = json_encode($this->batchRecipientAttributes);
			$response = $this->restClient->post($this->endpointUrl, $message, $files);
			$this->batchRecipientAttributes = array();
			$this->counters['recipients']['to'] = 0;
			$this->counters['recipients']['cc'] = 0;
			$this->counters['recipients']['bcc'] = 0;
			unset($this->message["to"]);
			array_push($this->messageIds, $response->http_response_body->id);
		}
	}

    /**
     * @throws MissingRequiredMIMEParameters
     */
	public function finalize(){
		$this->sendMessage();
	}

    /**
     * @return string[]
     */
	public function getMessageIds(){
		return $this->messageIds;
	}
}
<?php
namespace Mailgun\Messages\Exceptions;

class InvalidParameter extends \Exception{}
<?php
namespace Mailgun\Messages\Exceptions;

class InvalidParameterType extends \Exception{}
<?php
namespace Mailgun\Messages\Exceptions;

class MissingRequiredMIMEParameters extends \Exception{}
<?php
namespace Mailgun\Messages\Exceptions;

class TooManyParameters extends \Exception{}
<?PHP

namespace Mailgun\Messages;

use Mailgun\Constants\Api;
use Mailgun\Constants\ExceptionMessages;
use Mailgun\Messages\Exceptions\InvalidParameter;
use Mailgun\Messages\Exceptions\TooManyParameters;

/**
 * This class is used for composing a properly formed
 * message object. Dealing with arrays can be cumbersome,
 * this class makes the process easier. See the official
 * documentation (link below) for usage instructions.
 *
 * @link https://github.com/mailgun/mailgun-php/blob/master/src/Mailgun/Messages/README.md
 */
class MessageBuilder
{
    /**
     * @var array
     */
    protected $message = array();

    /**
     * @var array
     */
    protected $variables = array();

    /**
     * @var array
     */
    protected $files = array();

    /**
     * @var array
     */
    protected $counters = array(
        'recipients' => array(
            'to'  => 0,
            'cc'  => 0,
            'bcc' => 0
        ),
        'attributes' => array(
            'attachment'    => 0,
            'campaign_id'   => 0,
            'custom_option' => 0,
            'tag'           => 0
        )
    );

    /**
     * @param array $params
     * @param string $key
     * @param mixed $default
     * @return mixed
     */
    protected function safeGet($params, $key, $default)
    {
        if (array_key_exists($key, $params)) {
            return $params[$key];
        }

        return $default;
    }

    /**
     * @param array $params
     * @return mixed|string
     */
    protected function getFullName($params)
    {
        if (array_key_exists("first", $params)) {
            $first = $this->safeGet($params, "first", "");
            $last  = $this->safeGet($params, "last", "");

            return trim("$first $last");
        }

        return $this->safeGet($params, "full_name", "");
    }

    /**
     * @param string $address
     * @param array $variables
     * @return string
     */
    protected function parseAddress($address, $variables)
    {
        if (!is_array($variables)) {
            return $address;
        }
        $fullName = $this->getFullName($variables);
        if ($fullName != null) {
            return "'$fullName' <$address>";
        }

        return $address;
    }

    /**
     * @param string $headerName
     * @param string $address
     * @param array $variables
     */
    protected function addRecipient($headerName, $address, $variables)
    {
        $compiledAddress = $this->parseAddress($address, $variables);

        if (isset($this->message[$headerName])) {
            array_push($this->message[$headerName], $compiledAddress);
        } elseif ($headerName == "h:reply-to") {
            $this->message[$headerName] = $compiledAddress;
        } else {
            $this->message[$headerName] = array($compiledAddress);
        }
        if (array_key_exists($headerName, $this->counters['recipients'])) {
            $this->counters['recipients'][$headerName] += 1;
        }
    }

    /**
     * @param string $address
     * @param array|null $variables
     * @return mixed
     * @throws TooManyParameters
     */
    public function addToRecipient($address, $variables = null)
    {
        if ($this->counters['recipients']['to'] > Api::RECIPIENT_COUNT_LIMIT) {
            throw new TooManyParameters(ExceptionMessages::TOO_MANY_PARAMETERS_RECIPIENT);
        }
        $this->addRecipient("to", $address, $variables);

        return end($this->message['to']);
    }

    /**
     * @param string $address
     * @param array|null $variables
     * @return mixed
     * @throws TooManyParameters
     */
    public function addCcRecipient($address, $variables = null)
    {
        if ($this->counters['recipients']['cc'] > Api::RECIPIENT_COUNT_LIMIT) {
            throw new TooManyParameters(ExceptionMessages::TOO_MANY_PARAMETERS_RECIPIENT);
        }
        $this->addRecipient("cc", $address, $variables);

        return end($this->message['cc']);
    }

    /**
     * @param string $address
     * @param array|null $variables
     * @return mixed
     * @throws TooManyParameters
     */
    public function addBccRecipient($address, $variables = null)
    {
        if ($this->counters['recipients']['bcc'] > Api::RECIPIENT_COUNT_LIMIT) {
            throw new TooManyParameters(ExceptionMessages::TOO_MANY_PARAMETERS_RECIPIENT);
        }
        $this->addRecipient("bcc", $address, $variables);

        return end($this->message['bcc']);
    }

    /**
     * @param string $address
     * @param array|null $variables
     * @return mixed
     */
    public function setFromAddress($address, $variables = null)
    {
        $this->addRecipient("from", $address, $variables);

        return $this->message['from'];
    }

    /**
     * @param string $address
     * @param array|null $variables
     * @return mixed
     */
    public function setReplyToAddress($address, $variables = null)
    {
        $this->addRecipient("h:reply-to", $address, $variables);

        return $this->message['h:reply-to'];
    }

    /**
     * @param string $subject
     * @return mixed
     */
    public function setSubject($subject = "")
    {
        if ($subject == null || $subject == "") {
            $subject = " ";
        }
        $this->message['subject'] = $subject;

        return $this->message['subject'];
    }

    /**
     * @param string $headerName
     * @param mixed $headerData
     * @return mixed
     */
    public function addCustomHeader($headerName, $headerData)
    {
        if (!preg_match("/^h:/i", $headerName)) {
            $headerName = "h:" . $headerName;
        }
        $this->message[$headerName] = array($headerData);

        return $this->message[$headerName];
    }

    /**
     * @param string $textBody
     * @return string
     */
    public function setTextBody($textBody)
    {
        if ($textBody == null || $textBody == "") {
            $textBody = " ";
        }
        $this->message['text'] = $textBody;

        return $this->message['text'];
    }

    /**
     * @param string $htmlBody
     * @return string
     */
    public function setHtmlBody($htmlBody)
    {
        if ($htmlBody == null || $htmlBody == "") {
            $htmlBody = " ";
        }
        $this->message['html'] = $htmlBody;

        return $this->message['html'];
    }

    /**
     * @param string $attachmentPath
     * @param string|null $attachmentName
     * @return bool
     */
    public function addAttachment($attachmentPath, $attachmentName = null)
    {
        if (isset($this->files["attachment"])) {
            $attachment = array(
                'filePath'   => $attachmentPath,
                'remoteName' => $attachmentName
            );
            array_push($this->files["attachment"], $attachment);
        } else {
            $this->files["attachment"] = array(
                array(
                    'filePath'   => $attachmentPath,
                    'remoteName' => $attachmentName
                )
            );
        }

        return true;
    }

    /**
     * @param string $inlineImagePath
     * @param string|null $inlineImageName
     * @throws InvalidParameter
     */
    public function addInlineImage($inlineImagePath, $inlineImageName = null)
    {
        if (preg_match("/^@/", $inlineImagePath)) {
            if (isset($this->files['inline'])) {
                $inlineAttachment = array(
                    'filePath'   => $inlineImagePath,
                    'remoteName' => $inlineImageName
                );
                array_push($this->files['inline'], $inlineAttachment);
            } else {
                $this->files['inline'] = array(
                    array(
                        'filePath'   => $inlineImagePath,
                        'remoteName' => $inlineImageName
                    )
                );
            }

            return true;
        } else {
            throw new InvalidParameter(ExceptionMessages::INVALID_PARAMETER_INLINE);
        }
    }

    /**
     * @param boolean $testMode
     * @return string
     */
    public function setTestMode($testMode)
    {
        if (filter_var($testMode, FILTER_VALIDATE_BOOLEAN)) {
            $testMode = "yes";
        } else {
            $testMode = "no";
        }
        $this->message['o:testmode'] = $testMode;

        return $this->message['o:testmode'];
    }

    /**
     * @param string|int $campaignId
     * @return string|int
     * @throws TooManyParameters
     */
    public function addCampaignId($campaignId)
    {
        if ($this->counters['attributes']['campaign_id'] < Api::CAMPAIGN_ID_LIMIT) {
            if (isset($this->message['o:campaign'])) {
                array_push($this->message['o:campaign'], $campaignId);
            } else {
                $this->message['o:campaign'] = array($campaignId);
            }
            $this->counters['attributes']['campaign_id'] += 1;

            return $this->message['o:campaign'];
        } else {
            throw new TooManyParameters(ExceptionMessages::TOO_MANY_PARAMETERS_CAMPAIGNS);
        }
    }

    /**
     * @param string $tag
     * @throws TooManyParameters
     */
    public function addTag($tag)
    {
        if ($this->counters['attributes']['tag'] < Api::TAG_LIMIT) {
            if (isset($this->message['o:tag'])) {
                array_push($this->message['o:tag'], $tag);
            } else {
                $this->message['o:tag'] = array($tag);
            }
            $this->counters['attributes']['tag'] += 1;

            return $this->message['o:tag'];
        } else {
            throw new TooManyParameters(ExceptionMessages::TOO_MANY_PARAMETERS_TAGS);
        }
    }

    /**
     * @param boolean $enabled
     * @return mixed
     */
    public function setDkim($enabled)
    {
        if (filter_var($enabled, FILTER_VALIDATE_BOOLEAN)) {
            $enabled = "yes";
        } else {
            $enabled = "no";
        }
        $this->message["o:dkim"] = $enabled;

        return $this->message["o:dkim"];
    }

    /**
     * @param boolean $enabled
     * @return string
     */
    public function setOpenTracking($enabled)
    {
        if (filter_var($enabled, FILTER_VALIDATE_BOOLEAN)) {
            $enabled = "yes";
        } else {
            $enabled = "no";
        }
        $this->message['o:tracking-opens'] = $enabled;

        return $this->message['o:tracking-opens'];
    }

    /**
     * @param boolean $enabled
     * @return string
     */
    public function setClickTracking($enabled)
    {
        if (filter_var($enabled, FILTER_VALIDATE_BOOLEAN)) {
            $enabled = "yes";
        } elseif ($enabled == "html") {
            $enabled = "html";
        } else {
            $enabled = "no";
        }
        $this->message['o:tracking-clicks'] = $enabled;

        return $this->message['o:tracking-clicks'];
    }

    /**
     * @param string $timeDate
     * @param string|null $timeZone
     * @return string
     */
    public function setDeliveryTime($timeDate, $timeZone = null)
    {
        if (isset($timeZone)) {
            $timeZoneObj = new \DateTimeZone("$timeZone");
        } else {
            $timeZoneObj = new \DateTimeZone(Api::DEFAULT_TIME_ZONE);
        }

        $dateTimeObj                     = new \DateTime($timeDate, $timeZoneObj);
        $formattedTimeDate               = $dateTimeObj->format(\DateTime::RFC2822);
        $this->message['o:deliverytime'] = $formattedTimeDate;

        return $this->message['o:deliverytime'];
    }

    /**
     * @param string $customName
     * @param mixed $data
     */
    public function addCustomData($customName, $data)
    {
        $this->message['v:' . $customName] = json_encode($data);
    }

    /**
     * @param string $parameterName
     * @param mixed $data
     * @return mixed
     */
    public function addCustomParameter($parameterName, $data)
    {
        if (isset($this->message[$parameterName])) {
            array_push($this->message[$parameterName], $data);

            return $this->message[$parameterName];
        } else {
            $this->message[$parameterName] = array($data);

            return $this->message[$parameterName];
        }
    }

    /**
     * @param array $message
     */
    public function setMessage($message)
    {
        $this->message = $message;
    }

    /**
     * @return array
     */
    public function getMessage()
    {
        return $this->message;
    }

    /**
     * @return array
     */
    public function getFiles()
    {
        return $this->files;
    }
}
<?php

//Grab the composer Autoloader!
$autoloader = require dirname(__DIR__) . '/vendor/autoload.php';

<?php

namespace Http\Discovery;

use Puli\Discovery\Api\Discovery;

/**
 * Registry that based find results on class existence.
 *
 * @author David de Boer <david@ddeboer.nl>
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
abstract class ClassDiscovery
{
    /**
     * @var GeneratedPuliFactory
     */
    private static $puliFactory;

    /**
     * @var Discovery
     */
    private static $puliDiscovery;

    /**
     * @return GeneratedPuliFactory
     */
    public static function getPuliFactory()
    {
        if (null === self::$puliFactory) {
            if (!defined('PULI_FACTORY_CLASS')) {
                throw new \RuntimeException('Puli Factory is not available');
            }

            $puliFactoryClass = PULI_FACTORY_CLASS;

            if (!class_exists($puliFactoryClass)) {
                throw new \RuntimeException('Puli Factory class does not exist');
            }

            self::$puliFactory = new $puliFactoryClass();
        }

        return self::$puliFactory;
    }

    /**
     * Sets the Puli factory.
     *
     * @param object $puliFactory
     */
    public static function setPuliFactory($puliFactory)
    {
        if (!is_callable([$puliFactory, 'createRepository']) || !is_callable([$puliFactory, 'createDiscovery'])) {
            throw new \InvalidArgumentException('The Puli Factory must expose a repository and a discovery');
        }

        self::$puliFactory = $puliFactory;
        self::$puliDiscovery = null;
    }

    /**
     * Resets the factory.
     */
    public static function resetPuliFactory()
    {
        self::$puliFactory = null;
        self::$puliDiscovery = null;
    }

    /**
     * Returns the Puli discovery layer.
     *
     * @return Discovery
     */
    public static function getPuliDiscovery()
    {
        if (!isset(self::$puliDiscovery)) {
            $factory = self::getPuliFactory();
            $repository = $factory->createRepository();

            self::$puliDiscovery = $factory->createDiscovery($repository);
        }

        return self::$puliDiscovery;
    }

    /**
     * Finds a class.
     *
     * @param $type
     *
     * @return string
     *
     * @throws NotFoundException
     */
    public static function findOneByType($type)
    {
        $bindings = self::getPuliDiscovery()->findBindings($type);

        foreach ($bindings as $binding) {
            if ($binding->hasParameterValue('depends')) {
                $dependency = $binding->getParameterValue('depends');

                if (!self::evaluateCondition($dependency)) {
                    continue;
                }
            }

            // TODO: check class binding
            return $binding->getClassName();
        }

        throw new NotFoundException(sprintf('Resource of type "%s" not found', $type));
    }

    /**
     * Finds a resource.
     *
     * @return object
     */
    public static function find()
    {
        throw new \LogicException('Not implemented');
    }

    /**
     * Evaulates conditions to boolean.
     *
     * @param mixed $condition
     *
     * @return bool
     */
    protected static function evaluateCondition($condition)
    {
        if (is_string($condition)) {
            // Should be extended for functions, extensions???
            return class_exists($condition);
        } elseif (is_callable($condition)) {
            return $condition();
        } elseif (is_bool($condition)) {
            return $condition;
        } elseif (is_array($condition)) {
            $evaluatedCondition = true;

            // Immediately stop execution if the condition is false
            for ($i = 0; $i < count($condition) && false !== $evaluatedCondition; ++$i) {
                $evaluatedCondition &= static::evaluateCondition($condition[$i]);
            }

            return $evaluatedCondition;
        }

        return false;
    }
}
<?php

namespace Http\Discovery;

use Http\Client\HttpAsyncClient;

/**
 * Finds an HTTP Asynchronous Client.
 *
 * @author Joel Wurtz <joel.wurtz@gmail.com>
 */
final class HttpAsyncClientDiscovery extends ClassDiscovery
{
    /**
     * Finds an HTTP Async Client.
     *
     * @return HttpAsyncClient
     */
    public static function find()
    {
        $asyncClient = static::findOneByType('Http\Client\HttpAsyncClient');

        return new $asyncClient();
    }
}
<?php

namespace Http\Discovery;

use Http\Client\HttpClient;

/**
 * Finds an HTTP Client.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
final class HttpClientDiscovery extends ClassDiscovery
{
    /**
     * Finds an HTTP Client.
     *
     * @return HttpClient
     */
    public static function find()
    {
        $client = static::findOneByType('Http\Client\HttpClient');

        return new $client();
    }
}
<?php

namespace Http\Discovery;

use Http\Message\MessageFactory;

/**
 * Finds a Message Factory.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
final class MessageFactoryDiscovery extends ClassDiscovery
{
    /**
     * Finds a Message Factory.
     *
     * @return MessageFactory
     */
    public static function find()
    {
        try {
            $messageFactory = static::findOneByType('Http\Message\MessageFactory');

            return new $messageFactory();
        } catch (NotFoundException $e) {
            throw new NotFoundException(
                'No factories found. To use Guzzle or Diactoros factories install php-http/message and the chosen message implementation.',
                0,
                $e
            );
        }
    }
}
<?php

namespace Http\Discovery;

/**
 * Thrown when a discovery does not find any matches.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
final class NotFoundException extends \RuntimeException
{
}
<?php

namespace Http\Discovery;

use Http\Message\StreamFactory;

/**
 * Finds a Stream Factory.
 *
 * @author Михаил Красильников <m.krasilnikov@yandex.ru>
 */
final class StreamFactoryDiscovery extends ClassDiscovery
{
    /**
     * Finds a Stream Factory.
     *
     * @return StreamFactory
     */
    public static function find()
    {
        try {
            $streamFactory = static::findOneByType('Http\Message\StreamFactory');

            return new $streamFactory();
        } catch (NotFoundException $e) {
            throw new NotFoundException(
                'No factories found. To use Guzzle or Diactoros factories install php-http/message and the chosen message implementation.',
                0,
                $e
            );
        }
    }
}
<?php

namespace Http\Discovery;

use Http\Message\UriFactory;

/**
 * Finds a URI Factory.
 *
 * @author David de Boer <david@ddeboer.nl>
 */
final class UriFactoryDiscovery extends ClassDiscovery
{
    /**
     * Finds a URI Factory.
     *
     * @return UriFactory
     */
    public static function find()
    {
        try {
            $uriFactory = static::findOneByType('Http\Message\UriFactory');

            return new $uriFactory();
        } catch (NotFoundException $e) {
            throw new NotFoundException(
                'No factories found. To use Guzzle or Diactoros factories install php-http/message and the chosen message implementation.',
                0,
                $e
            );
        }
    }
}
<?php

namespace Http\Adapter\Guzzle6;

use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
use Psr\Http\Message\RequestInterface;

/**
 * HTTP Adapter for Guzzle 6.
 *
 * @author David de Boer <david@ddeboer.nl>
 */
class Client implements HttpClient, HttpAsyncClient
{
    /**
     * @var ClientInterface
     */
    private $client;

    /**
     * @param ClientInterface|null $client
     */
    public function __construct(ClientInterface $client = null)
    {
        if (!$client) {
            $client = static::buildClient();
        }

        $this->client = $client;
    }

    /**
     * Factory method to create the guzzle 6 adapter with custom configuration for guzzle.
     *
     * @param array $config Configuration to create guzzle with.
     *
     * @return Client
     */
    public static function createWithConfig(array $config)
    {
        return new self(static::buildClient($config));
    }

    /**
     * {@inheritdoc}
     */
    public function sendRequest(RequestInterface $request)
    {
        $promise = $this->sendAsyncRequest($request);

        return $promise->wait();
    }

    /**
     * {@inheritdoc}
     */
    public function sendAsyncRequest(RequestInterface $request)
    {
        $promise = $this->client->sendAsync($request);

        return new Promise($promise, $request);
    }

    /**
     * Build the guzzle client instance.
     *
     * @param array $config Additional configuration
     *
     * @return GuzzleClient
     */
    private static function buildClient(array $config = [])
    {
        $handlerStack = new HandlerStack(\GuzzleHttp\choose_handler());
        $handlerStack->push(Middleware::prepareBody(), 'prepare_body');
        $config = array_merge(['handler' => $handlerStack], $config);

        return new GuzzleClient($config);
    }
}
<?php

namespace Http\Adapter\Guzzle6;

use GuzzleHttp\Exception as GuzzleExceptions;
use GuzzleHttp\Promise\PromiseInterface;
use Http\Client\Exception as HttplugException;
use Http\Promise\Promise as HttpPromise;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Wrapper around Guzzle promises.
 *
 * @author Joel Wurtz <joel.wurtz@gmail.com>
 */
class Promise implements HttpPromise
{
    /**
     * @var PromiseInterface
     */
    private $promise;

    /**
     * @var string State of the promise
     */
    private $state;

    /**
     * @var ResponseInterface
     */
    private $response;

    /**
     * @var HttplugException
     */
    private $exception;

    /**
     * @var RequestInterface
     */
    private $request;

    /**
     * @param PromiseInterface $promise
     * @param RequestInterface $request
     */
    public function __construct(PromiseInterface $promise, RequestInterface $request)
    {
        $this->request = $request;
        $this->state = self::PENDING;
        $this->promise = $promise->then(function ($response) {
            $this->response = $response;
            $this->state = self::FULFILLED;

            return $response;
        }, function ($reason) use ($request) {
            $this->state = self::REJECTED;

            if ($reason instanceof HttplugException) {
                $this->exception = $reason;
            } elseif ($reason instanceof GuzzleExceptions\GuzzleException) {
                $this->exception = $this->handleException($reason, $request);
            } elseif ($reason instanceof \Exception) {
                $this->exception = new \RuntimeException('Invalid exception returned from Guzzle6', 0, $reason);
            } else {
                $this->exception = new \UnexpectedValueException('Reason returned from Guzzle6 must be an Exception', 0, $reason);
            }

            throw $this->exception;
        });
    }

    /**
     * {@inheritdoc}
     */
    public function then(callable $onFulfilled = null, callable $onRejected = null)
    {
        return new static($this->promise->then($onFulfilled, $onRejected), $this->request);
    }

    /**
     * {@inheritdoc}
     */
    public function getState()
    {
        return $this->state;
    }

    /**
     * {@inheritdoc}
     */
    public function wait($unwrap = true)
    {
        $this->promise->wait(false);

        if ($unwrap) {
            if ($this->getState() == self::REJECTED) {
                throw $this->exception;
            }

            return $this->response;
        }
    }

    /**
     * Converts a Guzzle exception into an Httplug exception.
     *
     * @param GuzzleExceptions\GuzzleException $exception
     * @param RequestInterface                 $request
     *
     * @return HttplugException
     */
    private function handleException(GuzzleExceptions\GuzzleException $exception, RequestInterface $request)
    {
        if ($exception instanceof GuzzleExceptions\SeekException) {
            return new HttplugException\RequestException($exception->getMessage(), $request, $exception);
        }

        if ($exception instanceof GuzzleExceptions\ConnectException) {
            return new HttplugException\NetworkException($exception->getMessage(), $exception->getRequest(), $exception);
        }

        if ($exception instanceof GuzzleExceptions\RequestException) {
            // Make sure we have a response for the HttpException
            if ($exception->hasResponse()) {
                return new HttplugException\HttpException(
                    $exception->getMessage(),
                    $exception->getRequest(),
                    $exception->getResponse(),
                    $exception
                );
            }

            return new HttplugException\RequestException($exception->getMessage(), $exception->getRequest(), $exception);
        }

        return new HttplugException\TransferException($exception->getMessage(), 0, $exception);
    }
}
<?php

namespace Http\Client\Exception;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Thrown when a response was received but the request itself failed.
 *
 * In addition to the request, this exception always provides access to the response object.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
class HttpException extends RequestException
{
    /**
     * @var ResponseInterface
     */
    protected $response;

    /**
     * @param string            $message
     * @param RequestInterface  $request
     * @param ResponseInterface $response
     * @param \Exception|null   $previous
     */
    public function __construct(
        $message,
        RequestInterface $request,
        ResponseInterface $response,
        \Exception $previous = null
    ) {
        parent::__construct($message, $request, $previous);

        $this->response = $response;
        $this->code = $response->getStatusCode();
    }

    /**
     * Returns the response.
     *
     * @return ResponseInterface
     */
    public function getResponse()
    {
        return $this->response;
    }

    /**
     * Factory method to create a new exception with a normalized error message.
     *
     * @param RequestInterface  $request
     * @param ResponseInterface $response
     * @param \Exception|null   $previous
     *
     * @return HttpException
     */
    public static function create(
        RequestInterface $request,
        ResponseInterface $response,
        \Exception $previous = null
    ) {
        $message = sprintf(
            '[url] %s [http method] %s [status code] %s [reason phrase] %s',
            $request->getRequestTarget(),
            $request->getMethod(),
            $response->getStatusCode(),
            $response->getReasonPhrase()
        );

        return new self($message, $request, $response, $previous);
    }
}
<?php

namespace Http\Client\Exception;

/**
 * Thrown when the request cannot be completed because of network issues.
 *
 * There is no response object as this exception is thrown when no response has been received.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
class NetworkException extends RequestException
{
}
<?php

namespace Http\Client\Exception;

use Psr\Http\Message\RequestInterface;

/**
 * Exception for when a request failed, providing access to the failed request.
 *
 * This could be due to an invalid request, or one of the extending exceptions
 * for network errors or HTTP error responses.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
class RequestException extends TransferException
{
    /**
     * @var RequestInterface
     */
    private $request;

    /**
     * @param string           $message
     * @param RequestInterface $request
     * @param \Exception|null  $previous
     */
    public function __construct($message, RequestInterface $request, \Exception $previous = null)
    {
        $this->request = $request;

        parent::__construct($message, 0, $previous);
    }

    /**
     * Returns the request.
     *
     * @return RequestInterface
     */
    public function getRequest()
    {
        return $this->request;
    }
}
<?php

namespace Http\Client\Exception;

use Http\Client\Exception;

/**
 * Base exception for transfer related exceptions.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
class TransferException extends \RuntimeException implements Exception
{
}
<?php

namespace Http\Client;

/**
 * Every HTTP Client related Exception must implement this interface.
 *
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
interface Exception
{
}
<?php

namespace Http\Client;

use Http\Promise\Promise;
use Psr\Http\Message\RequestInterface;

/**
 * Sends a PSR-7 Request in an asynchronous way by returning a Promise.
 *
 * @author Joel Wurtz <joel.wurtz@gmail.com>
 */
interface HttpAsyncClient
{
    /**
     * Sends a PSR-7 request in an asynchronous way.
     *
     * Exceptions related to processing the request are available from the returned Promise.
     *
     * @param RequestInterface $request
     *
     * @return Promise Resolves a PSR-7 Response or fails with an Http\Client\Exception.
     *
     * @throws \Exception If processing the request is impossible (eg. bad configuration).
     */
    public function sendAsyncRequest(RequestInterface $request);
}
<?php

namespace Http\Client;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Sends a PSR-7 Request and returns a PSR-7 response.
 *
 * @author GeLo <geloen.eric@gmail.com>
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 * @author David Buchmann <mail@davidbu.ch>
 */
interface HttpClient
{
    /**
     * Sends a PSR-7 request.
     *
     * @param RequestInterface $request
     *
     * @return ResponseInterface
     *
     * @throws \Http\Client\Exception If an error happens during processing the request.
     * @throws \Exception             If processing the request is impossible (eg. bad configuration).
     */
    public function sendRequest(RequestInterface $request);
}
<?php

namespace Http\Promise;

/**
 * A promise already fulfilled.
 *
 * @author Joel Wurtz <joel.wurtz@gmail.com>
 */
final class FulfilledPromise implements Promise
{
    /**
     * @var mixed
     */
    private $result;

    /**
     * @param $result
     */
    public function __construct($result)
    {
        $this->result = $result;
    }

    /**
     * {@inheritdoc}
     */
    public function then(callable $onFulfilled = null, callable $onRejected = null)
    {
        if (null === $onFulfilled) {
            return $this;
        }

        try {
            return new self($onFulfilled($this->result));
        } catch (\Exception $e) {
            return new RejectedPromise($e);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getState()
    {
        return Promise::FULFILLED;
    }

    /**
     * {@inheritdoc}
     */
    public function wait($unwrap = true)
    {
        if ($unwrap) {
            return $this->result;
        }
    }
}
<?php

namespace Http\Promise;

/**
 * Promise represents a value that may not be available yet, but will be resolved at some point in future.
 * It acts like a proxy to the actual value.
 *
 * This interface is an extension of the promises/a+ specification.
 *
 * @see https://promisesaplus.com/
 *
 * @author Joel Wurtz <joel.wurtz@gmail.com>
 * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
 */
interface Promise
{
    /**
     * Promise has not been fulfilled or rejected.
     */
    const PENDING = 'pending';

    /**
     * Promise has been fulfilled.
     */
    const FULFILLED = 'fulfilled';

    /**
     * Promise has been rejected.
     */
    const REJECTED = 'rejected';

    /**
     * Adds behavior for when the promise is resolved or rejected (response will be available, or error happens).
     *
     * If you do not care about one of the cases, you can set the corresponding callable to null
     * The callback will be called when the value arrived and never more than once.
     *
     * @param callable $onFulfilled Called when a response will be available.
     * @param callable $onRejected  Called when an exception occurs.
     *
     * @return Promise A new resolved promise with value of the executed callback (onFulfilled / onRejected).
     */
    public function then(callable $onFulfilled = null, callable $onRejected = null);

    /**
     * Returns the state of the promise, one of PENDING, FULFILLED or REJECTED.
     *
     * @return string
     */
    public function getState();

    /**
     * Wait for the promise to be fulfilled or rejected.
     *
     * When this method returns, the request has been resolved and if callables have been
     * specified, the appropriate one has terminated.
     *
     * When $unwrap is true (the default), the response is returned, or the exception thrown
     * on failure. Otherwise, nothing is returned or thrown.
     *
     * @param bool $unwrap Whether to return resolved value / throw reason or not
     *
     * @return mixed Resolved value, null if $unwrap is set to false
     *
     * @throws \Exception The rejection reason if $unwrap is set to true and the request failed.
     */
    public function wait($unwrap = true);
}
<?php

namespace Http\Promise;

/**
 * A rejected promise.
 *
 * @author Joel Wurtz <joel.wurtz@gmail.com>
 */
final class RejectedPromise implements Promise
{
    /**
     * @var \Exception
     */
    private $exception;

    /**
     * @param \Exception $exception
     */
    public function __construct(\Exception $exception)
    {
        $this->exception = $exception;
    }

    /**
     * {@inheritdoc}
     */
    public function then(callable $onFulfilled = null, callable $onRejected = null)
    {
        if (null === $onRejected) {
            return $this;
        }

        try {
            return new FulfilledPromise($onRejected($this->exception));
        } catch (\Exception $e) {
            return new self($e);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getState()
    {
        return Promise::REJECTED;
    }

    /**
     * {@inheritdoc}
     */
    public function wait($unwrap = true)
    {
        if ($unwrap) {
            throw $this->exception;
        }
    }
}
<?php

namespace Psr\Http\Message;

/**
 * HTTP messages consist of requests from a client to a server and responses
 * from a server to a client. This interface defines the methods common to
 * each.
 *
 * Messages are considered immutable; all methods that might change state MUST
 * be implemented such that they retain the internal state of the current
 * message and return an instance that contains the changed state.
 *
 * @link http://www.ietf.org/rfc/rfc7230.txt
 * @link http://www.ietf.org/rfc/rfc7231.txt
 */
interface MessageInterface
{
    /**
     * Retrieves the HTTP protocol version as a string.
     *
     * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
     *
     * @return string HTTP protocol version.
     */
    public function getProtocolVersion();

    /**
     * Return an instance with the specified HTTP protocol version.
     *
     * The version string MUST contain only the HTTP version number (e.g.,
     * "1.1", "1.0").
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * new protocol version.
     *
     * @param string $version HTTP protocol version
     * @return self
     */
    public function withProtocolVersion($version);

    /**
     * Retrieves all message header values.
     *
     * The keys represent the header name as it will be sent over the wire, and
     * each value is an array of strings associated with the header.
     *
     *     // Represent the headers as a string
     *     foreach ($message->getHeaders() as $name => $values) {
     *         echo $name . ": " . implode(", ", $values);
     *     }
     *
     *     // Emit headers iteratively:
     *     foreach ($message->getHeaders() as $name => $values) {
     *         foreach ($values as $value) {
     *             header(sprintf('%s: %s', $name, $value), false);
     *         }
     *     }
     *
     * While header names are not case-sensitive, getHeaders() will preserve the
     * exact case in which headers were originally specified.
     *
     * @return array Returns an associative array of the message's headers. Each
     *     key MUST be a header name, and each value MUST be an array of strings
     *     for that header.
     */
    public function getHeaders();

    /**
     * Checks if a header exists by the given case-insensitive name.
     *
     * @param string $name Case-insensitive header field name.
     * @return bool Returns true if any header names match the given header
     *     name using a case-insensitive string comparison. Returns false if
     *     no matching header name is found in the message.
     */
    public function hasHeader($name);

    /**
     * Retrieves a message header value by the given case-insensitive name.
     *
     * This method returns an array of all the header values of the given
     * case-insensitive header name.
     *
     * If the header does not appear in the message, this method MUST return an
     * empty array.
     *
     * @param string $name Case-insensitive header field name.
     * @return string[] An array of string values as provided for the given
     *    header. If the header does not appear in the message, this method MUST
     *    return an empty array.
     */
    public function getHeader($name);

    /**
     * Retrieves a comma-separated string of the values for a single header.
     *
     * This method returns all of the header values of the given
     * case-insensitive header name as a string concatenated together using
     * a comma.
     *
     * NOTE: Not all header values may be appropriately represented using
     * comma concatenation. For such headers, use getHeader() instead
     * and supply your own delimiter when concatenating.
     *
     * If the header does not appear in the message, this method MUST return
     * an empty string.
     *
     * @param string $name Case-insensitive header field name.
     * @return string A string of values as provided for the given header
     *    concatenated together using a comma. If the header does not appear in
     *    the message, this method MUST return an empty string.
     */
    public function getHeaderLine($name);

    /**
     * Return an instance with the provided value replacing the specified header.
     *
     * While header names are case-insensitive, the casing of the header will
     * be preserved by this function, and returned from getHeaders().
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * new and/or updated header and value.
     *
     * @param string $name Case-insensitive header field name.
     * @param string|string[] $value Header value(s).
     * @return self
     * @throws \InvalidArgumentException for invalid header names or values.
     */
    public function withHeader($name, $value);

    /**
     * Return an instance with the specified header appended with the given value.
     *
     * Existing values for the specified header will be maintained. The new
     * value(s) will be appended to the existing list. If the header did not
     * exist previously, it will be added.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * new header and/or value.
     *
     * @param string $name Case-insensitive header field name to add.
     * @param string|string[] $value Header value(s).
     * @return self
     * @throws \InvalidArgumentException for invalid header names or values.
     */
    public function withAddedHeader($name, $value);

    /**
     * Return an instance without the specified header.
     *
     * Header resolution MUST be done without case-sensitivity.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that removes
     * the named header.
     *
     * @param string $name Case-insensitive header field name to remove.
     * @return self
     */
    public function withoutHeader($name);

    /**
     * Gets the body of the message.
     *
     * @return StreamInterface Returns the body as a stream.
     */
    public function getBody();

    /**
     * Return an instance with the specified message body.
     *
     * The body MUST be a StreamInterface object.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return a new instance that has the
     * new body stream.
     *
     * @param StreamInterface $body Body.
     * @return self
     * @throws \InvalidArgumentException When the body is not valid.
     */
    public function withBody(StreamInterface $body);
}
<?php

namespace Psr\Http\Message;

/**
 * Representation of an outgoing, client-side request.
 *
 * Per the HTTP specification, this interface includes properties for
 * each of the following:
 *
 * - Protocol version
 * - HTTP method
 * - URI
 * - Headers
 * - Message body
 *
 * During construction, implementations MUST attempt to set the Host header from
 * a provided URI if no Host header is provided.
 *
 * Requests are considered immutable; all methods that might change state MUST
 * be implemented such that they retain the internal state of the current
 * message and return an instance that contains the changed state.
 */
interface RequestInterface extends MessageInterface
{
    /**
     * Retrieves the message's request target.
     *
     * Retrieves the message's request-target either as it will appear (for
     * clients), as it appeared at request (for servers), or as it was
     * specified for the instance (see withRequestTarget()).
     *
     * In most cases, this will be the origin-form of the composed URI,
     * unless a value was provided to the concrete implementation (see
     * withRequestTarget() below).
     *
     * If no URI is available, and no request-target has been specifically
     * provided, this method MUST return the string "/".
     *
     * @return string
     */
    public function getRequestTarget();

    /**
     * Return an instance with the specific request-target.
     *
     * If the request needs a non-origin-form request-target — e.g., for
     * specifying an absolute-form, authority-form, or asterisk-form —
     * this method may be used to create an instance with the specified
     * request-target, verbatim.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * changed request target.
     *
     * @link http://tools.ietf.org/html/rfc7230#section-2.7 (for the various
     *     request-target forms allowed in request messages)
     * @param mixed $requestTarget
     * @return self
     */
    public function withRequestTarget($requestTarget);

    /**
     * Retrieves the HTTP method of the request.
     *
     * @return string Returns the request method.
     */
    public function getMethod();

    /**
     * Return an instance with the provided HTTP method.
     *
     * While HTTP method names are typically all uppercase characters, HTTP
     * method names are case-sensitive and thus implementations SHOULD NOT
     * modify the given string.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * changed request method.
     *
     * @param string $method Case-sensitive method.
     * @return self
     * @throws \InvalidArgumentException for invalid HTTP methods.
     */
    public function withMethod($method);

    /**
     * Retrieves the URI instance.
     *
     * This method MUST return a UriInterface instance.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-4.3
     * @return UriInterface Returns a UriInterface instance
     *     representing the URI of the request.
     */
    public function getUri();

    /**
     * Returns an instance with the provided URI.
     *
     * This method MUST update the Host header of the returned request by
     * default if the URI contains a host component. If the URI does not
     * contain a host component, any pre-existing Host header MUST be carried
     * over to the returned request.
     *
     * You can opt-in to preserving the original state of the Host header by
     * setting `$preserveHost` to `true`. When `$preserveHost` is set to
     * `true`, this method interacts with the Host header in the following ways:
     *
     * - If the the Host header is missing or empty, and the new URI contains
     *   a host component, this method MUST update the Host header in the returned
     *   request.
     * - If the Host header is missing or empty, and the new URI does not contain a
     *   host component, this method MUST NOT update the Host header in the returned
     *   request.
     * - If a Host header is present and non-empty, this method MUST NOT update
     *   the Host header in the returned request.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * new UriInterface instance.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-4.3
     * @param UriInterface $uri New request URI to use.
     * @param bool $preserveHost Preserve the original state of the Host header.
     * @return self
     */
    public function withUri(UriInterface $uri, $preserveHost = false);
}
<?php

namespace Psr\Http\Message;

/**
 * Representation of an outgoing, server-side response.
 *
 * Per the HTTP specification, this interface includes properties for
 * each of the following:
 *
 * - Protocol version
 * - Status code and reason phrase
 * - Headers
 * - Message body
 *
 * Responses are considered immutable; all methods that might change state MUST
 * be implemented such that they retain the internal state of the current
 * message and return an instance that contains the changed state.
 */
interface ResponseInterface extends MessageInterface
{
    /**
     * Gets the response status code.
     *
     * The status code is a 3-digit integer result code of the server's attempt
     * to understand and satisfy the request.
     *
     * @return int Status code.
     */
    public function getStatusCode();

    /**
     * Return an instance with the specified status code and, optionally, reason phrase.
     *
     * If no reason phrase is specified, implementations MAY choose to default
     * to the RFC 7231 or IANA recommended reason phrase for the response's
     * status code.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated status and reason phrase.
     *
     * @link http://tools.ietf.org/html/rfc7231#section-6
     * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
     * @param int $code The 3-digit integer result code to set.
     * @param string $reasonPhrase The reason phrase to use with the
     *     provided status code; if none is provided, implementations MAY
     *     use the defaults as suggested in the HTTP specification.
     * @return self
     * @throws \InvalidArgumentException For invalid status code arguments.
     */
    public function withStatus($code, $reasonPhrase = '');

    /**
     * Gets the response reason phrase associated with the status code.
     *
     * Because a reason phrase is not a required element in a response
     * status line, the reason phrase value MAY be null. Implementations MAY
     * choose to return the default RFC 7231 recommended reason phrase (or those
     * listed in the IANA HTTP Status Code Registry) for the response's
     * status code.
     *
     * @link http://tools.ietf.org/html/rfc7231#section-6
     * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
     * @return string Reason phrase; must return an empty string if none present.
     */
    public function getReasonPhrase();
}
<?php

namespace Psr\Http\Message;

/**
 * Representation of an incoming, server-side HTTP request.
 *
 * Per the HTTP specification, this interface includes properties for
 * each of the following:
 *
 * - Protocol version
 * - HTTP method
 * - URI
 * - Headers
 * - Message body
 *
 * Additionally, it encapsulates all data as it has arrived to the
 * application from the CGI and/or PHP environment, including:
 *
 * - The values represented in $_SERVER.
 * - Any cookies provided (generally via $_COOKIE)
 * - Query string arguments (generally via $_GET, or as parsed via parse_str())
 * - Upload files, if any (as represented by $_FILES)
 * - Deserialized body parameters (generally from $_POST)
 *
 * $_SERVER values MUST be treated as immutable, as they represent application
 * state at the time of request; as such, no methods are provided to allow
 * modification of those values. The other values provide such methods, as they
 * can be restored from $_SERVER or the request body, and may need treatment
 * during the application (e.g., body parameters may be deserialized based on
 * content type).
 *
 * Additionally, this interface recognizes the utility of introspecting a
 * request to derive and match additional parameters (e.g., via URI path
 * matching, decrypting cookie values, deserializing non-form-encoded body
 * content, matching authorization headers to users, etc). These parameters
 * are stored in an "attributes" property.
 *
 * Requests are considered immutable; all methods that might change state MUST
 * be implemented such that they retain the internal state of the current
 * message and return an instance that contains the changed state.
 */
interface ServerRequestInterface extends RequestInterface
{
    /**
     * Retrieve server parameters.
     *
     * Retrieves data related to the incoming request environment,
     * typically derived from PHP's $_SERVER superglobal. The data IS NOT
     * REQUIRED to originate from $_SERVER.
     *
     * @return array
     */
    public function getServerParams();

    /**
     * Retrieve cookies.
     *
     * Retrieves cookies sent by the client to the server.
     *
     * The data MUST be compatible with the structure of the $_COOKIE
     * superglobal.
     *
     * @return array
     */
    public function getCookieParams();

    /**
     * Return an instance with the specified cookies.
     *
     * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
     * be compatible with the structure of $_COOKIE. Typically, this data will
     * be injected at instantiation.
     *
     * This method MUST NOT update the related Cookie header of the request
     * instance, nor related values in the server params.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated cookie values.
     *
     * @param array $cookies Array of key/value pairs representing cookies.
     * @return self
     */
    public function withCookieParams(array $cookies);

    /**
     * Retrieve query string arguments.
     *
     * Retrieves the deserialized query string arguments, if any.
     *
     * Note: the query params might not be in sync with the URI or server
     * params. If you need to ensure you are only getting the original
     * values, you may need to parse the query string from `getUri()->getQuery()`
     * or from the `QUERY_STRING` server param.
     *
     * @return array
     */
    public function getQueryParams();

    /**
     * Return an instance with the specified query string arguments.
     *
     * These values SHOULD remain immutable over the course of the incoming
     * request. They MAY be injected during instantiation, such as from PHP's
     * $_GET superglobal, or MAY be derived from some other value such as the
     * URI. In cases where the arguments are parsed from the URI, the data
     * MUST be compatible with what PHP's parse_str() would return for
     * purposes of how duplicate query parameters are handled, and how nested
     * sets are handled.
     *
     * Setting query string arguments MUST NOT change the URI stored by the
     * request, nor the values in the server params.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated query string arguments.
     *
     * @param array $query Array of query string arguments, typically from
     *     $_GET.
     * @return self
     */
    public function withQueryParams(array $query);

    /**
     * Retrieve normalized file upload data.
     *
     * This method returns upload metadata in a normalized tree, with each leaf
     * an instance of Psr\Http\Message\UploadedFileInterface.
     *
     * These values MAY be prepared from $_FILES or the message body during
     * instantiation, or MAY be injected via withUploadedFiles().
     *
     * @return array An array tree of UploadedFileInterface instances; an empty
     *     array MUST be returned if no data is present.
     */
    public function getUploadedFiles();

    /**
     * Create a new instance with the specified uploaded files.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated body parameters.
     *
     * @param array An array tree of UploadedFileInterface instances.
     * @return self
     * @throws \InvalidArgumentException if an invalid structure is provided.
     */
    public function withUploadedFiles(array $uploadedFiles);

    /**
     * Retrieve any parameters provided in the request body.
     *
     * If the request Content-Type is either application/x-www-form-urlencoded
     * or multipart/form-data, and the request method is POST, this method MUST
     * return the contents of $_POST.
     *
     * Otherwise, this method may return any results of deserializing
     * the request body content; as parsing returns structured content, the
     * potential types MUST be arrays or objects only. A null value indicates
     * the absence of body content.
     *
     * @return null|array|object The deserialized body parameters, if any.
     *     These will typically be an array or object.
     */
    public function getParsedBody();

    /**
     * Return an instance with the specified body parameters.
     *
     * These MAY be injected during instantiation.
     *
     * If the request Content-Type is either application/x-www-form-urlencoded
     * or multipart/form-data, and the request method is POST, use this method
     * ONLY to inject the contents of $_POST.
     *
     * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
     * deserializing the request body content. Deserialization/parsing returns
     * structured data, and, as such, this method ONLY accepts arrays or objects,
     * or a null value if nothing was available to parse.
     *
     * As an example, if content negotiation determines that the request data
     * is a JSON payload, this method could be used to create a request
     * instance with the deserialized parameters.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated body parameters.
     *
     * @param null|array|object $data The deserialized body data. This will
     *     typically be in an array or object.
     * @return self
     * @throws \InvalidArgumentException if an unsupported argument type is
     *     provided.
     */
    public function withParsedBody($data);

    /**
     * Retrieve attributes derived from the request.
     *
     * The request "attributes" may be used to allow injection of any
     * parameters derived from the request: e.g., the results of path
     * match operations; the results of decrypting cookies; the results of
     * deserializing non-form-encoded message bodies; etc. Attributes
     * will be application and request specific, and CAN be mutable.
     *
     * @return array Attributes derived from the request.
     */
    public function getAttributes();

    /**
     * Retrieve a single derived request attribute.
     *
     * Retrieves a single derived request attribute as described in
     * getAttributes(). If the attribute has not been previously set, returns
     * the default value as provided.
     *
     * This method obviates the need for a hasAttribute() method, as it allows
     * specifying a default value to return if the attribute is not found.
     *
     * @see getAttributes()
     * @param string $name The attribute name.
     * @param mixed $default Default value to return if the attribute does not exist.
     * @return mixed
     */
    public function getAttribute($name, $default = null);

    /**
     * Return an instance with the specified derived request attribute.
     *
     * This method allows setting a single derived request attribute as
     * described in getAttributes().
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated attribute.
     *
     * @see getAttributes()
     * @param string $name The attribute name.
     * @param mixed $value The value of the attribute.
     * @return self
     */
    public function withAttribute($name, $value);

    /**
     * Return an instance that removes the specified derived request attribute.
     *
     * This method allows removing a single derived request attribute as
     * described in getAttributes().
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that removes
     * the attribute.
     *
     * @see getAttributes()
     * @param string $name The attribute name.
     * @return self
     */
    public function withoutAttribute($name);
}
<?php

namespace Psr\Http\Message;

/**
 * Describes a data stream.
 *
 * Typically, an instance will wrap a PHP stream; this interface provides
 * a wrapper around the most common operations, including serialization of
 * the entire stream to a string.
 */
interface StreamInterface
{
    /**
     * Reads all data from the stream into a string, from the beginning to end.
     *
     * This method MUST attempt to seek to the beginning of the stream before
     * reading data and read the stream until the end is reached.
     *
     * Warning: This could attempt to load a large amount of data into memory.
     *
     * This method MUST NOT raise an exception in order to conform with PHP's
     * string casting operations.
     *
     * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
     * @return string
     */
    public function __toString();

    /**
     * Closes the stream and any underlying resources.
     *
     * @return void
     */
    public function close();

    /**
     * Separates any underlying resources from the stream.
     *
     * After the stream has been detached, the stream is in an unusable state.
     *
     * @return resource|null Underlying PHP stream, if any
     */
    public function detach();

    /**
     * Get the size of the stream if known.
     *
     * @return int|null Returns the size in bytes if known, or null if unknown.
     */
    public function getSize();

    /**
     * Returns the current position of the file read/write pointer
     *
     * @return int Position of the file pointer
     * @throws \RuntimeException on error.
     */
    public function tell();

    /**
     * Returns true if the stream is at the end of the stream.
     *
     * @return bool
     */
    public function eof();

    /**
     * Returns whether or not the stream is seekable.
     *
     * @return bool
     */
    public function isSeekable();

    /**
     * Seek to a position in the stream.
     *
     * @link http://www.php.net/manual/en/function.fseek.php
     * @param int $offset Stream offset
     * @param int $whence Specifies how the cursor position will be calculated
     *     based on the seek offset. Valid values are identical to the built-in
     *     PHP $whence values for `fseek()`.  SEEK_SET: Set position equal to
     *     offset bytes SEEK_CUR: Set position to current location plus offset
     *     SEEK_END: Set position to end-of-stream plus offset.
     * @throws \RuntimeException on failure.
     */
    public function seek($offset, $whence = SEEK_SET);

    /**
     * Seek to the beginning of the stream.
     *
     * If the stream is not seekable, this method will raise an exception;
     * otherwise, it will perform a seek(0).
     *
     * @see seek()
     * @link http://www.php.net/manual/en/function.fseek.php
     * @throws \RuntimeException on failure.
     */
    public function rewind();

    /**
     * Returns whether or not the stream is writable.
     *
     * @return bool
     */
    public function isWritable();

    /**
     * Write data to the stream.
     *
     * @param string $string The string that is to be written.
     * @return int Returns the number of bytes written to the stream.
     * @throws \RuntimeException on failure.
     */
    public function write($string);

    /**
     * Returns whether or not the stream is readable.
     *
     * @return bool
     */
    public function isReadable();

    /**
     * Read data from the stream.
     *
     * @param int $length Read up to $length bytes from the object and return
     *     them. Fewer than $length bytes may be returned if underlying stream
     *     call returns fewer bytes.
     * @return string Returns the data read from the stream, or an empty string
     *     if no bytes are available.
     * @throws \RuntimeException if an error occurs.
     */
    public function read($length);

    /**
     * Returns the remaining contents in a string
     *
     * @return string
     * @throws \RuntimeException if unable to read or an error occurs while
     *     reading.
     */
    public function getContents();

    /**
     * Get stream metadata as an associative array or retrieve a specific key.
     *
     * The keys returned are identical to the keys returned from PHP's
     * stream_get_meta_data() function.
     *
     * @link http://php.net/manual/en/function.stream-get-meta-data.php
     * @param string $key Specific metadata to retrieve.
     * @return array|mixed|null Returns an associative array if no key is
     *     provided. Returns a specific key value if a key is provided and the
     *     value is found, or null if the key is not found.
     */
    public function getMetadata($key = null);
}
<?php

namespace Psr\Http\Message;

/**
 * Value object representing a file uploaded through an HTTP request.
 *
 * Instances of this interface are considered immutable; all methods that
 * might change state MUST be implemented such that they retain the internal
 * state of the current instance and return an instance that contains the
 * changed state.
 */
interface UploadedFileInterface
{
    /**
     * Retrieve a stream representing the uploaded file.
     *
     * This method MUST return a StreamInterface instance, representing the
     * uploaded file. The purpose of this method is to allow utilizing native PHP
     * stream functionality to manipulate the file upload, such as
     * stream_copy_to_stream() (though the result will need to be decorated in a
     * native PHP stream wrapper to work with such functions).
     *
     * If the moveTo() method has been called previously, this method MUST raise
     * an exception.
     *
     * @return StreamInterface Stream representation of the uploaded file.
     * @throws \RuntimeException in cases when no stream is available or can be
     *     created.
     */
    public function getStream();

    /**
     * Move the uploaded file to a new location.
     *
     * Use this method as an alternative to move_uploaded_file(). This method is
     * guaranteed to work in both SAPI and non-SAPI environments.
     * Implementations must determine which environment they are in, and use the
     * appropriate method (move_uploaded_file(), rename(), or a stream
     * operation) to perform the operation.
     *
     * $targetPath may be an absolute path, or a relative path. If it is a
     * relative path, resolution should be the same as used by PHP's rename()
     * function.
     *
     * The original file or stream MUST be removed on completion.
     *
     * If this method is called more than once, any subsequent calls MUST raise
     * an exception.
     *
     * When used in an SAPI environment where $_FILES is populated, when writing
     * files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be
     * used to ensure permissions and upload status are verified correctly.
     *
     * If you wish to move to a stream, use getStream(), as SAPI operations
     * cannot guarantee writing to stream destinations.
     *
     * @see http://php.net/is_uploaded_file
     * @see http://php.net/move_uploaded_file
     * @param string $targetPath Path to which to move the uploaded file.
     * @throws \InvalidArgumentException if the $path specified is invalid.
     * @throws \RuntimeException on any error during the move operation, or on
     *     the second or subsequent call to the method.
     */
    public function moveTo($targetPath);
    
    /**
     * Retrieve the file size.
     *
     * Implementations SHOULD return the value stored in the "size" key of
     * the file in the $_FILES array if available, as PHP calculates this based
     * on the actual size transmitted.
     *
     * @return int|null The file size in bytes or null if unknown.
     */
    public function getSize();
    
    /**
     * Retrieve the error associated with the uploaded file.
     *
     * The return value MUST be one of PHP's UPLOAD_ERR_XXX constants.
     *
     * If the file was uploaded successfully, this method MUST return
     * UPLOAD_ERR_OK.
     *
     * Implementations SHOULD return the value stored in the "error" key of
     * the file in the $_FILES array.
     *
     * @see http://php.net/manual/en/features.file-upload.errors.php
     * @return int One of PHP's UPLOAD_ERR_XXX constants.
     */
    public function getError();
    
    /**
     * Retrieve the filename sent by the client.
     *
     * Do not trust the value returned by this method. A client could send
     * a malicious filename with the intention to corrupt or hack your
     * application.
     *
     * Implementations SHOULD return the value stored in the "name" key of
     * the file in the $_FILES array.
     *
     * @return string|null The filename sent by the client or null if none
     *     was provided.
     */
    public function getClientFilename();
    
    /**
     * Retrieve the media type sent by the client.
     *
     * Do not trust the value returned by this method. A client could send
     * a malicious media type with the intention to corrupt or hack your
     * application.
     *
     * Implementations SHOULD return the value stored in the "type" key of
     * the file in the $_FILES array.
     *
     * @return string|null The media type sent by the client or null if none
     *     was provided.
     */
    public function getClientMediaType();
}
<?php
namespace Psr\Http\Message;

/**
 * Value object representing a URI.
 *
 * This interface is meant to represent URIs according to RFC 3986 and to
 * provide methods for most common operations. Additional functionality for
 * working with URIs can be provided on top of the interface or externally.
 * Its primary use is for HTTP requests, but may also be used in other
 * contexts.
 *
 * Instances of this interface are considered immutable; all methods that
 * might change state MUST be implemented such that they retain the internal
 * state of the current instance and return an instance that contains the
 * changed state.
 *
 * Typically the Host header will be also be present in the request message.
 * For server-side requests, the scheme will typically be discoverable in the
 * server parameters.
 *
 * @link http://tools.ietf.org/html/rfc3986 (the URI specification)
 */
interface UriInterface
{
    /**
     * Retrieve the scheme component of the URI.
     *
     * If no scheme is present, this method MUST return an empty string.
     *
     * The value returned MUST be normalized to lowercase, per RFC 3986
     * Section 3.1.
     *
     * The trailing ":" character is not part of the scheme and MUST NOT be
     * added.
     *
     * @see https://tools.ietf.org/html/rfc3986#section-3.1
     * @return string The URI scheme.
     */
    public function getScheme();

    /**
     * Retrieve the authority component of the URI.
     *
     * If no authority information is present, this method MUST return an empty
     * string.
     *
     * The authority syntax of the URI is:
     *
     * <pre>
     * [user-info@]host[:port]
     * </pre>
     *
     * If the port component is not set or is the standard port for the current
     * scheme, it SHOULD NOT be included.
     *
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
     * @return string The URI authority, in "[user-info@]host[:port]" format.
     */
    public function getAuthority();

    /**
     * Retrieve the user information component of the URI.
     *
     * If no user information is present, this method MUST return an empty
     * string.
     *
     * If a user is present in the URI, this will return that value;
     * additionally, if the password is also present, it will be appended to the
     * user value, with a colon (":") separating the values.
     *
     * The trailing "@" character is not part of the user information and MUST
     * NOT be added.
     *
     * @return string The URI user information, in "username[:password]" format.
     */
    public function getUserInfo();

    /**
     * Retrieve the host component of the URI.
     *
     * If no host is present, this method MUST return an empty string.
     *
     * The value returned MUST be normalized to lowercase, per RFC 3986
     * Section 3.2.2.
     *
     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
     * @return string The URI host.
     */
    public function getHost();

    /**
     * Retrieve the port component of the URI.
     *
     * If a port is present, and it is non-standard for the current scheme,
     * this method MUST return it as an integer. If the port is the standard port
     * used with the current scheme, this method SHOULD return null.
     *
     * If no port is present, and no scheme is present, this method MUST return
     * a null value.
     *
     * If no port is present, but a scheme is present, this method MAY return
     * the standard port for that scheme, but SHOULD return null.
     *
     * @return null|int The URI port.
     */
    public function getPort();

    /**
     * Retrieve the path component of the URI.
     *
     * The path can either be empty or absolute (starting with a slash) or
     * rootless (not starting with a slash). Implementations MUST support all
     * three syntaxes.
     *
     * Normally, the empty path "" and absolute path "/" are considered equal as
     * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
     * do this normalization because in contexts with a trimmed base path, e.g.
     * the front controller, this difference becomes significant. It's the task
     * of the user to handle both "" and "/".
     *
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
     * any characters. To determine what characters to encode, please refer to
     * RFC 3986, Sections 2 and 3.3.
     *
     * As an example, if the value should include a slash ("/") not intended as
     * delimiter between path segments, that value MUST be passed in encoded
     * form (e.g., "%2F") to the instance.
     *
     * @see https://tools.ietf.org/html/rfc3986#section-2
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
     * @return string The URI path.
     */
    public function getPath();

    /**
     * Retrieve the query string of the URI.
     *
     * If no query string is present, this method MUST return an empty string.
     *
     * The leading "?" character is not part of the query and MUST NOT be
     * added.
     *
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
     * any characters. To determine what characters to encode, please refer to
     * RFC 3986, Sections 2 and 3.4.
     *
     * As an example, if a value in a key/value pair of the query string should
     * include an ampersand ("&") not intended as a delimiter between values,
     * that value MUST be passed in encoded form (e.g., "%26") to the instance.
     *
     * @see https://tools.ietf.org/html/rfc3986#section-2
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
     * @return string The URI query string.
     */
    public function getQuery();

    /**
     * Retrieve the fragment component of the URI.
     *
     * If no fragment is present, this method MUST return an empty string.
     *
     * The leading "#" character is not part of the fragment and MUST NOT be
     * added.
     *
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
     * any characters. To determine what characters to encode, please refer to
     * RFC 3986, Sections 2 and 3.5.
     *
     * @see https://tools.ietf.org/html/rfc3986#section-2
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
     * @return string The URI fragment.
     */
    public function getFragment();

    /**
     * Return an instance with the specified scheme.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified scheme.
     *
     * Implementations MUST support the schemes "http" and "https" case
     * insensitively, and MAY accommodate other schemes if required.
     *
     * An empty scheme is equivalent to removing the scheme.
     *
     * @param string $scheme The scheme to use with the new instance.
     * @return self A new instance with the specified scheme.
     * @throws \InvalidArgumentException for invalid or unsupported schemes.
     */
    public function withScheme($scheme);

    /**
     * Return an instance with the specified user information.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified user information.
     *
     * Password is optional, but the user information MUST include the
     * user; an empty string for the user is equivalent to removing user
     * information.
     *
     * @param string $user The user name to use for authority.
     * @param null|string $password The password associated with $user.
     * @return self A new instance with the specified user information.
     */
    public function withUserInfo($user, $password = null);

    /**
     * Return an instance with the specified host.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified host.
     *
     * An empty host value is equivalent to removing the host.
     *
     * @param string $host The hostname to use with the new instance.
     * @return self A new instance with the specified host.
     * @throws \InvalidArgumentException for invalid hostnames.
     */
    public function withHost($host);

    /**
     * Return an instance with the specified port.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified port.
     *
     * Implementations MUST raise an exception for ports outside the
     * established TCP and UDP port ranges.
     *
     * A null value provided for the port is equivalent to removing the port
     * information.
     *
     * @param null|int $port The port to use with the new instance; a null value
     *     removes the port information.
     * @return self A new instance with the specified port.
     * @throws \InvalidArgumentException for invalid ports.
     */
    public function withPort($port);

    /**
     * Return an instance with the specified path.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified path.
     *
     * The path can either be empty or absolute (starting with a slash) or
     * rootless (not starting with a slash). Implementations MUST support all
     * three syntaxes.
     *
     * If the path is intended to be domain-relative rather than path relative then
     * it must begin with a slash ("/"). Paths not starting with a slash ("/")
     * are assumed to be relative to some base path known to the application or
     * consumer.
     *
     * Users can provide both encoded and decoded path characters.
     * Implementations ensure the correct encoding as outlined in getPath().
     *
     * @param string $path The path to use with the new instance.
     * @return self A new instance with the specified path.
     * @throws \InvalidArgumentException for invalid paths.
     */
    public function withPath($path);

    /**
     * Return an instance with the specified query string.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified query string.
     *
     * Users can provide both encoded and decoded query characters.
     * Implementations ensure the correct encoding as outlined in getQuery().
     *
     * An empty query string value is equivalent to removing the query string.
     *
     * @param string $query The query string to use with the new instance.
     * @return self A new instance with the specified query string.
     * @throws \InvalidArgumentException for invalid query strings.
     */
    public function withQuery($query);

    /**
     * Return an instance with the specified URI fragment.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified URI fragment.
     *
     * Users can provide both encoded and decoded fragment characters.
     * Implementations ensure the correct encoding as outlined in getFragment().
     *
     * An empty fragment value is equivalent to removing the fragment.
     *
     * @param string $fragment The fragment to use with the new instance.
     * @return self A new instance with the specified fragment.
     */
    public function withFragment($fragment);

    /**
     * Return the string representation as a URI reference.
     *
     * Depending on which components of the URI are present, the resulting
     * string is either a full URI or relative reference according to RFC 3986,
     * Section 4.1. The method concatenates the various components of the URI,
     * using the appropriate delimiters:
     *
     * - If a scheme is present, it MUST be suffixed by ":".
     * - If an authority is present, it MUST be prefixed by "//".
     * - The path can be concatenated without delimiters. But there are two
     *   cases where the path has to be adjusted to make the URI reference
     *   valid as PHP does not allow to throw an exception in __toString():
     *     - If the path is rootless and an authority is present, the path MUST
     *       be prefixed by "/".
     *     - If the path is starting with more than one "/" and no authority is
     *       present, the starting slashes MUST be reduced to one.
     * - If a query is present, it MUST be prefixed by "?".
     * - If a fragment is present, it MUST be prefixed by "#".
     *
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
     * @return string
     */
    public function __toString();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Attachment class for attaching files to a {@link Swift_Mime_Message}.
 *
 * @author Chris Corbyn
 */
class Swift_Attachment extends Swift_Mime_Attachment
{
    /**
     * Create a new Attachment.
     *
     * Details may be optionally provided to the constructor.
     *
     * @param string|Swift_OutputByteStream $data
     * @param string                        $filename
     * @param string                        $contentType
     */
    public function __construct($data = null, $filename = null, $contentType = null)
    {
        call_user_func_array(
            array($this, 'Swift_Mime_Attachment::__construct'),
            Swift_DependencyContainer::getInstance()
                ->createDependenciesFor('mime.attachment')
            );

        $this->setBody($data);
        $this->setFilename($filename);
        if ($contentType) {
            $this->setContentType($contentType);
        }
    }

    /**
     * Create a new Attachment.
     *
     * @param string|Swift_OutputByteStream $data
     * @param string                        $filename
     * @param string                        $contentType
     *
     * @return Swift_Mime_Attachment
     */
    public static function newInstance($data = null, $filename = null, $contentType = null)
    {
        return new self($data, $filename, $contentType);
    }

    /**
     * Create a new Attachment from a filesystem path.
     *
     * @param string $path
     * @param string $contentType optional
     *
     * @return Swift_Mime_Attachment
     */
    public static function fromPath($path, $contentType = null)
    {
        return self::newInstance()->setFile(
            new Swift_ByteStream_FileByteStream($path),
            $contentType
            );
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Provides the base functionality for an InputStream supporting filters.
 *
 * @author Chris Corbyn
 */
abstract class Swift_ByteStream_AbstractFilterableInputStream implements Swift_InputByteStream, Swift_Filterable
{
    /**
     * Write sequence.
     */
    protected $_sequence = 0;

    /**
     * StreamFilters.
     *
     * @var Swift_StreamFilter[]
     */
    private $_filters = array();

    /**
     * A buffer for writing.
     */
    private $_writeBuffer = '';

    /**
     * Bound streams.
     *
     * @var Swift_InputByteStream[]
     */
    private $_mirrors = array();

    /**
     * Commit the given bytes to the storage medium immediately.
     *
     * @param string $bytes
     */
    abstract protected function _commit($bytes);

    /**
     * Flush any buffers/content with immediate effect.
     */
    abstract protected function _flush();

    /**
     * Add a StreamFilter to this InputByteStream.
     *
     * @param Swift_StreamFilter $filter
     * @param string             $key
     */
    public function addFilter(Swift_StreamFilter $filter, $key)
    {
        $this->_filters[$key] = $filter;
    }

    /**
     * Remove an already present StreamFilter based on its $key.
     *
     * @param string $key
     */
    public function removeFilter($key)
    {
        unset($this->_filters[$key]);
    }

    /**
     * Writes $bytes to the end of the stream.
     *
     * @param string $bytes
     *
     * @throws Swift_IoException
     *
     * @return int
     */
    public function write($bytes)
    {
        $this->_writeBuffer .= $bytes;
        foreach ($this->_filters as $filter) {
            if ($filter->shouldBuffer($this->_writeBuffer)) {
                return;
            }
        }
        $this->_doWrite($this->_writeBuffer);

        return ++$this->_sequence;
    }

    /**
     * For any bytes that are currently buffered inside the stream, force them
     * off the buffer.
     *
     * @throws Swift_IoException
     */
    public function commit()
    {
        $this->_doWrite($this->_writeBuffer);
    }

    /**
     * Attach $is to this stream.
     *
     * The stream acts as an observer, receiving all data that is written.
     * All {@link write()} and {@link flushBuffers()} operations will be mirrored.
     *
     * @param Swift_InputByteStream $is
     */
    public function bind(Swift_InputByteStream $is)
    {
        $this->_mirrors[] = $is;
    }

    /**
     * Remove an already bound stream.
     *
     * If $is is not bound, no errors will be raised.
     * If the stream currently has any buffered data it will be written to $is
     * before unbinding occurs.
     *
     * @param Swift_InputByteStream $is
     */
    public function unbind(Swift_InputByteStream $is)
    {
        foreach ($this->_mirrors as $k => $stream) {
            if ($is === $stream) {
                if ($this->_writeBuffer !== '') {
                    $stream->write($this->_writeBuffer);
                }
                unset($this->_mirrors[$k]);
            }
        }
    }

    /**
     * Flush the contents of the stream (empty it) and set the internal pointer
     * to the beginning.
     *
     * @throws Swift_IoException
     */
    public function flushBuffers()
    {
        if ($this->_writeBuffer !== '') {
            $this->_doWrite($this->_writeBuffer);
        }
        $this->_flush();

        foreach ($this->_mirrors as $stream) {
            $stream->flushBuffers();
        }
    }

    /** Run $bytes through all filters */
    private function _filter($bytes)
    {
        foreach ($this->_filters as $filter) {
            $bytes = $filter->filter($bytes);
        }

        return $bytes;
    }

    /** Just write the bytes to the stream */
    private function _doWrite($bytes)
    {
        $this->_commit($this->_filter($bytes));

        foreach ($this->_mirrors as $stream) {
            $stream->write($bytes);
        }

        $this->_writeBuffer = '';
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Allows reading and writing of bytes to and from an array.
 *
 * @author Chris Corbyn
 */
class Swift_ByteStream_ArrayByteStream implements Swift_InputByteStream, Swift_OutputByteStream
{
    /**
     * The internal stack of bytes.
     *
     * @var string[]
     */
    private $_array = array();

    /**
     * The size of the stack.
     *
     * @var int
     */
    private $_arraySize = 0;

    /**
     * The internal pointer offset.
     *
     * @var int
     */
    private $_offset = 0;

    /**
     * Bound streams.
     *
     * @var Swift_InputByteStream[]
     */
    private $_mirrors = array();

    /**
     * Create a new ArrayByteStream.
     *
     * If $stack is given the stream will be populated with the bytes it contains.
     *
     * @param mixed $stack of bytes in string or array form, optional
     */
    public function __construct($stack = null)
    {
        if (is_array($stack)) {
            $this->_array = $stack;
            $this->_arraySize = count($stack);
        } elseif (is_string($stack)) {
            $this->write($stack);
        } else {
            $this->_array = array();
        }
    }

    /**
     * Reads $length bytes from the stream into a string and moves the pointer
     * through the stream by $length.
     *
     * If less bytes exist than are requested the
     * remaining bytes are given instead. If no bytes are remaining at all, boolean
     * false is returned.
     *
     * @param int $length
     *
     * @return string
     */
    public function read($length)
    {
        if ($this->_offset == $this->_arraySize) {
            return false;
        }

        // Don't use array slice
        $end = $length + $this->_offset;
        $end = $this->_arraySize < $end ? $this->_arraySize : $end;
        $ret = '';
        for (; $this->_offset < $end; ++$this->_offset) {
            $ret .= $this->_array[$this->_offset];
        }

        return $ret;
    }

    /**
     * Writes $bytes to the end of the stream.
     *
     * @param string $bytes
     */
    public function write($bytes)
    {
        $to_add = str_split($bytes);
        foreach ($to_add as $value) {
            $this->_array[] = $value;
        }
        $this->_arraySize = count($this->_array);

        foreach ($this->_mirrors as $stream) {
            $stream->write($bytes);
        }
    }

    /**
     * Not used.
     */
    public function commit()
    {
    }

    /**
     * Attach $is to this stream.
     *
     * The stream acts as an observer, receiving all data that is written.
     * All {@link write()} and {@link flushBuffers()} operations will be mirrored.
     *
     * @param Swift_InputByteStream $is
     */
    public function bind(Swift_InputByteStream $is)
    {
        $this->_mirrors[] = $is;
    }

    /**
     * Remove an already bound stream.
     *
     * If $is is not bound, no errors will be raised.
     * If the stream currently has any buffered data it will be written to $is
     * before unbinding occurs.
     *
     * @param Swift_InputByteStream $is
     */
    public function unbind(Swift_InputByteStream $is)
    {
        foreach ($this->_mirrors as $k => $stream) {
            if ($is === $stream) {
                unset($this->_mirrors[$k]);
            }
        }
    }

    /**
     * Move the internal read pointer to $byteOffset in the stream.
     *
     * @param int $byteOffset
     *
     * @return bool
     */
    public function setReadPointer($byteOffset)
    {
        if ($byteOffset > $this->_arraySize) {
            $byteOffset = $this->_arraySize;
        } elseif ($byteOffset < 0) {
            $byteOffset = 0;
        }

        $this->_offset = $byteOffset;
    }

    /**
     * Flush the contents of the stream (empty it) and set the internal pointer
     * to the beginning.
     */
    public function flushBuffers()
    {
        $this->_offset = 0;
        $this->_array = array();
        $this->_arraySize = 0;

        foreach ($this->_mirrors as $stream) {
            $stream->flushBuffers();
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Allows reading and writing of bytes to and from a file.
 *
 * @author Chris Corbyn
 */
class Swift_ByteStream_FileByteStream extends Swift_ByteStream_AbstractFilterableInputStream implements Swift_FileStream
{
    /** The internal pointer offset */
    private $_offset = 0;

    /** The path to the file */
    private $_path;

    /** The mode this file is opened in for writing */
    private $_mode;

    /** A lazy-loaded resource handle for reading the file */
    private $_reader;

    /** A lazy-loaded resource handle for writing the file */
    private $_writer;

    /** If magic_quotes_runtime is on, this will be true */
    private $_quotes = false;

    /** If stream is seekable true/false, or null if not known */
    private $_seekable = null;

    /**
     * Create a new FileByteStream for $path.
     *
     * @param string $path
     * @param bool   $writable if true
     */
    public function __construct($path, $writable = false)
    {
        if (empty($path)) {
            throw new Swift_IoException('The path cannot be empty');
        }
        $this->_path = $path;
        $this->_mode = $writable ? 'w+b' : 'rb';

        if (function_exists('get_magic_quotes_runtime') && @get_magic_quotes_runtime() == 1) {
            $this->_quotes = true;
        }
    }

    /**
     * Get the complete path to the file.
     *
     * @return string
     */
    public function getPath()
    {
        return $this->_path;
    }

    /**
     * Reads $length bytes from the stream into a string and moves the pointer
     * through the stream by $length.
     *
     * If less bytes exist than are requested the
     * remaining bytes are given instead. If no bytes are remaining at all, boolean
     * false is returned.
     *
     * @param int $length
     *
     * @throws Swift_IoException
     *
     * @return string|bool
     */
    public function read($length)
    {
        $fp = $this->_getReadHandle();
        if (!feof($fp)) {
            if ($this->_quotes) {
                ini_set('magic_quotes_runtime', 0);
            }
            $bytes = fread($fp, $length);
            if ($this->_quotes) {
                ini_set('magic_quotes_runtime', 1);
            }
            $this->_offset = ftell($fp);

            // If we read one byte after reaching the end of the file
            // feof() will return false and an empty string is returned
            if ($bytes === '' && feof($fp)) {
                $this->_resetReadHandle();

                return false;
            }

            return $bytes;
        }

        $this->_resetReadHandle();

        return false;
    }

    /**
     * Move the internal read pointer to $byteOffset in the stream.
     *
     * @param int $byteOffset
     *
     * @return bool
     */
    public function setReadPointer($byteOffset)
    {
        if (isset($this->_reader)) {
            $this->_seekReadStreamToPosition($byteOffset);
        }
        $this->_offset = $byteOffset;
    }

    /** Just write the bytes to the file */
    protected function _commit($bytes)
    {
        fwrite($this->_getWriteHandle(), $bytes);
        $this->_resetReadHandle();
    }

    /** Not used */
    protected function _flush()
    {
    }

    /** Get the resource for reading */
    private function _getReadHandle()
    {
        if (!isset($this->_reader)) {
            $pointer = @fopen($this->_path, 'rb');
            if (!$pointer) {
                throw new Swift_IoException(
                    'Unable to open file for reading ['.$this->_path.']'
                );
            }
            $this->_reader = $pointer;
            if ($this->_offset != 0) {
                $this->_getReadStreamSeekableStatus();
                $this->_seekReadStreamToPosition($this->_offset);
            }
        }

        return $this->_reader;
    }

    /** Get the resource for writing */
    private function _getWriteHandle()
    {
        if (!isset($this->_writer)) {
            if (!$this->_writer = fopen($this->_path, $this->_mode)) {
                throw new Swift_IoException(
                    'Unable to open file for writing ['.$this->_path.']'
                );
            }
        }

        return $this->_writer;
    }

    /** Force a reload of the resource for reading */
    private function _resetReadHandle()
    {
        if (isset($this->_reader)) {
            fclose($this->_reader);
            $this->_reader = null;
        }
    }

    /** Check if ReadOnly Stream is seekable */
    private function _getReadStreamSeekableStatus()
    {
        $metas = stream_get_meta_data($this->_reader);
        $this->_seekable = $metas['seekable'];
    }

    /** Streams in a readOnly stream ensuring copy if needed */
    private function _seekReadStreamToPosition($offset)
    {
        if ($this->_seekable === null) {
            $this->_getReadStreamSeekableStatus();
        }
        if ($this->_seekable === false) {
            $currentPos = ftell($this->_reader);
            if ($currentPos < $offset) {
                $toDiscard = $offset - $currentPos;
                fread($this->_reader, $toDiscard);

                return;
            }
            $this->_copyReadStream();
        }
        fseek($this->_reader, $offset, SEEK_SET);
    }

    /** Copy a readOnly Stream to ensure seekability */
    private function _copyReadStream()
    {
        if ($tmpFile = fopen('php://temp/maxmemory:4096', 'w+b')) {
            /* We have opened a php:// Stream Should work without problem */
        } elseif (function_exists('sys_get_temp_dir') && is_writable(sys_get_temp_dir()) && ($tmpFile = tmpfile())) {
            /* We have opened a tmpfile */
        } else {
            throw new Swift_IoException('Unable to copy the file to make it seekable, sys_temp_dir is not writable, php://memory not available');
        }
        $currentPos = ftell($this->_reader);
        fclose($this->_reader);
        $source = fopen($this->_path, 'rb');
        if (!$source) {
            throw new Swift_IoException('Unable to open file for copying ['.$this->_path.']');
        }
        fseek($tmpFile, 0, SEEK_SET);
        while (!feof($source)) {
            fwrite($tmpFile, fread($source, 4096));
        }
        fseek($tmpFile, $currentPos, SEEK_SET);
        fclose($source);
        $this->_reader = $tmpFile;
    }
}
<?php

/*
* This file is part of SwiftMailer.
* (c) 2004-2009 Chris Corbyn
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/**
 * @author Romain-Geissler
 */
class Swift_ByteStream_TemporaryFileByteStream extends Swift_ByteStream_FileByteStream
{
    public function __construct()
    {
        $filePath = tempnam(sys_get_temp_dir(), 'FileByteStream');

        if ($filePath === false) {
            throw new Swift_IoException('Failed to retrieve temporary file name.');
        }

        parent::__construct($filePath, true);
    }

    public function getContent()
    {
        if (($content = file_get_contents($this->getPath())) === false) {
            throw new Swift_IoException('Failed to get temporary file content.');
        }

        return $content;
    }

    public function __destruct()
    {
        if (file_exists($this->getPath())) {
            @unlink($this->getPath());
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Provides fixed-width byte sizes for reading fixed-width character sets.
 *
 * @author Chris Corbyn
 * @author Xavier De Cock <xdecock@gmail.com>
 */
class Swift_CharacterReader_GenericFixedWidthReader implements Swift_CharacterReader
{
    /**
     * The number of bytes in a single character.
     *
     * @var int
     */
    private $_width;

    /**
     * Creates a new GenericFixedWidthReader using $width bytes per character.
     *
     * @param int $width
     */
    public function __construct($width)
    {
        $this->_width = $width;
    }

    /**
     * Returns the complete character map.
     *
     * @param string $string
     * @param int    $startOffset
     * @param array  $currentMap
     * @param mixed  $ignoredChars
     *
     * @return int
     */
    public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars)
    {
        $strlen = strlen($string);
        // % and / are CPU intensive, so, maybe find a better way
        $ignored = $strlen % $this->_width;
        $ignoredChars = $ignored ? substr($string, -$ignored) : '';
        $currentMap = $this->_width;

        return ($strlen - $ignored) / $this->_width;
    }

    /**
     * Returns the mapType.
     *
     * @return int
     */
    public function getMapType()
    {
        return self::MAP_TYPE_FIXED_LEN;
    }

    /**
     * Returns an integer which specifies how many more bytes to read.
     *
     * A positive integer indicates the number of more bytes to fetch before invoking
     * this method again.
     *
     * A value of zero means this is already a valid character.
     * A value of -1 means this cannot possibly be a valid character.
     *
     * @param string $bytes
     * @param int    $size
     *
     * @return int
     */
    public function validateByteSequence($bytes, $size)
    {
        $needed = $this->_width - $size;

        return $needed > -1 ? $needed : -1;
    }

    /**
     * Returns the number of bytes which should be read to start each character.
     *
     * @return int
     */
    public function getInitialByteSize()
    {
        return $this->_width;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Analyzes US-ASCII characters.
 *
 * @author Chris Corbyn
 */
class Swift_CharacterReader_UsAsciiReader implements Swift_CharacterReader
{
    /**
     * Returns the complete character map.
     *
     * @param string $string
     * @param int    $startOffset
     * @param array  $currentMap
     * @param string $ignoredChars
     *
     * @return int
     */
    public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars)
    {
        $strlen = strlen($string);
        $ignoredChars = '';
        for ($i = 0; $i < $strlen; ++$i) {
            if ($string[$i] > "\x07F") {
                // Invalid char
                $currentMap[$i + $startOffset] = $string[$i];
            }
        }

        return $strlen;
    }

    /**
     * Returns mapType.
     *
     * @return int mapType
     */
    public function getMapType()
    {
        return self::MAP_TYPE_INVALID;
    }

    /**
     * Returns an integer which specifies how many more bytes to read.
     *
     * A positive integer indicates the number of more bytes to fetch before invoking
     * this method again.
     * A value of zero means this is already a valid character.
     * A value of -1 means this cannot possibly be a valid character.
     *
     * @param string $bytes
     * @param int    $size
     *
     * @return int
     */
    public function validateByteSequence($bytes, $size)
    {
        $byte = reset($bytes);
        if (1 == count($bytes) && $byte >= 0x00 && $byte <= 0x7F) {
            return 0;
        }

        return -1;
    }

    /**
     * Returns the number of bytes which should be read to start each character.
     *
     * @return int
     */
    public function getInitialByteSize()
    {
        return 1;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Analyzes UTF-8 characters.
 *
 * @author Chris Corbyn
 * @author Xavier De Cock <xdecock@gmail.com>
 */
class Swift_CharacterReader_Utf8Reader implements Swift_CharacterReader
{
    /** Pre-computed for optimization */
    private static $length_map = array(
        // N=0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x0N
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x1N
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x2N
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x3N
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x4N
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x5N
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x6N
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x7N
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x8N
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x9N
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0xAN
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0xBN
        2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 0xCN
        2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 0xDN
        3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, // 0xEN
        4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0,  // 0xFN
    );

    private static $s_length_map = array(
        "\x00" => 1, "\x01" => 1, "\x02" => 1, "\x03" => 1, "\x04" => 1, "\x05" => 1, "\x06" => 1, "\x07" => 1,
        "\x08" => 1, "\x09" => 1, "\x0a" => 1, "\x0b" => 1, "\x0c" => 1, "\x0d" => 1, "\x0e" => 1, "\x0f" => 1,
        "\x10" => 1, "\x11" => 1, "\x12" => 1, "\x13" => 1, "\x14" => 1, "\x15" => 1, "\x16" => 1, "\x17" => 1,
        "\x18" => 1, "\x19" => 1, "\x1a" => 1, "\x1b" => 1, "\x1c" => 1, "\x1d" => 1, "\x1e" => 1, "\x1f" => 1,
        "\x20" => 1, "\x21" => 1, "\x22" => 1, "\x23" => 1, "\x24" => 1, "\x25" => 1, "\x26" => 1, "\x27" => 1,
        "\x28" => 1, "\x29" => 1, "\x2a" => 1, "\x2b" => 1, "\x2c" => 1, "\x2d" => 1, "\x2e" => 1, "\x2f" => 1,
        "\x30" => 1, "\x31" => 1, "\x32" => 1, "\x33" => 1, "\x34" => 1, "\x35" => 1, "\x36" => 1, "\x37" => 1,
        "\x38" => 1, "\x39" => 1, "\x3a" => 1, "\x3b" => 1, "\x3c" => 1, "\x3d" => 1, "\x3e" => 1, "\x3f" => 1,
        "\x40" => 1, "\x41" => 1, "\x42" => 1, "\x43" => 1, "\x44" => 1, "\x45" => 1, "\x46" => 1, "\x47" => 1,
        "\x48" => 1, "\x49" => 1, "\x4a" => 1, "\x4b" => 1, "\x4c" => 1, "\x4d" => 1, "\x4e" => 1, "\x4f" => 1,
        "\x50" => 1, "\x51" => 1, "\x52" => 1, "\x53" => 1, "\x54" => 1, "\x55" => 1, "\x56" => 1, "\x57" => 1,
        "\x58" => 1, "\x59" => 1, "\x5a" => 1, "\x5b" => 1, "\x5c" => 1, "\x5d" => 1, "\x5e" => 1, "\x5f" => 1,
        "\x60" => 1, "\x61" => 1, "\x62" => 1, "\x63" => 1, "\x64" => 1, "\x65" => 1, "\x66" => 1, "\x67" => 1,
        "\x68" => 1, "\x69" => 1, "\x6a" => 1, "\x6b" => 1, "\x6c" => 1, "\x6d" => 1, "\x6e" => 1, "\x6f" => 1,
        "\x70" => 1, "\x71" => 1, "\x72" => 1, "\x73" => 1, "\x74" => 1, "\x75" => 1, "\x76" => 1, "\x77" => 1,
        "\x78" => 1, "\x79" => 1, "\x7a" => 1, "\x7b" => 1, "\x7c" => 1, "\x7d" => 1, "\x7e" => 1, "\x7f" => 1,
        "\x80" => 0, "\x81" => 0, "\x82" => 0, "\x83" => 0, "\x84" => 0, "\x85" => 0, "\x86" => 0, "\x87" => 0,
        "\x88" => 0, "\x89" => 0, "\x8a" => 0, "\x8b" => 0, "\x8c" => 0, "\x8d" => 0, "\x8e" => 0, "\x8f" => 0,
        "\x90" => 0, "\x91" => 0, "\x92" => 0, "\x93" => 0, "\x94" => 0, "\x95" => 0, "\x96" => 0, "\x97" => 0,
        "\x98" => 0, "\x99" => 0, "\x9a" => 0, "\x9b" => 0, "\x9c" => 0, "\x9d" => 0, "\x9e" => 0, "\x9f" => 0,
        "\xa0" => 0, "\xa1" => 0, "\xa2" => 0, "\xa3" => 0, "\xa4" => 0, "\xa5" => 0, "\xa6" => 0, "\xa7" => 0,
        "\xa8" => 0, "\xa9" => 0, "\xaa" => 0, "\xab" => 0, "\xac" => 0, "\xad" => 0, "\xae" => 0, "\xaf" => 0,
        "\xb0" => 0, "\xb1" => 0, "\xb2" => 0, "\xb3" => 0, "\xb4" => 0, "\xb5" => 0, "\xb6" => 0, "\xb7" => 0,
        "\xb8" => 0, "\xb9" => 0, "\xba" => 0, "\xbb" => 0, "\xbc" => 0, "\xbd" => 0, "\xbe" => 0, "\xbf" => 0,
        "\xc0" => 2, "\xc1" => 2, "\xc2" => 2, "\xc3" => 2, "\xc4" => 2, "\xc5" => 2, "\xc6" => 2, "\xc7" => 2,
        "\xc8" => 2, "\xc9" => 2, "\xca" => 2, "\xcb" => 2, "\xcc" => 2, "\xcd" => 2, "\xce" => 2, "\xcf" => 2,
        "\xd0" => 2, "\xd1" => 2, "\xd2" => 2, "\xd3" => 2, "\xd4" => 2, "\xd5" => 2, "\xd6" => 2, "\xd7" => 2,
        "\xd8" => 2, "\xd9" => 2, "\xda" => 2, "\xdb" => 2, "\xdc" => 2, "\xdd" => 2, "\xde" => 2, "\xdf" => 2,
        "\xe0" => 3, "\xe1" => 3, "\xe2" => 3, "\xe3" => 3, "\xe4" => 3, "\xe5" => 3, "\xe6" => 3, "\xe7" => 3,
        "\xe8" => 3, "\xe9" => 3, "\xea" => 3, "\xeb" => 3, "\xec" => 3, "\xed" => 3, "\xee" => 3, "\xef" => 3,
        "\xf0" => 4, "\xf1" => 4, "\xf2" => 4, "\xf3" => 4, "\xf4" => 4, "\xf5" => 4, "\xf6" => 4, "\xf7" => 4,
        "\xf8" => 5, "\xf9" => 5, "\xfa" => 5, "\xfb" => 5, "\xfc" => 6, "\xfd" => 6, "\xfe" => 0, "\xff" => 0,
     );

    /**
     * Returns the complete character map.
     *
     * @param string $string
     * @param int    $startOffset
     * @param array  $currentMap
     * @param mixed  $ignoredChars
     *
     * @return int
     */
    public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars)
    {
        if (!isset($currentMap['i']) || !isset($currentMap['p'])) {
            $currentMap['p'] = $currentMap['i'] = array();
        }

        $strlen = strlen($string);
        $charPos = count($currentMap['p']);
        $foundChars = 0;
        $invalid = false;
        for ($i = 0; $i < $strlen; ++$i) {
            $char = $string[$i];
            $size = self::$s_length_map[$char];
            if ($size == 0) {
                /* char is invalid, we must wait for a resync */
                $invalid = true;
                continue;
            } else {
                if ($invalid == true) {
                    /* We mark the chars as invalid and start a new char */
                    $currentMap['p'][$charPos + $foundChars] = $startOffset + $i;
                    $currentMap['i'][$charPos + $foundChars] = true;
                    ++$foundChars;
                    $invalid = false;
                }
                if (($i + $size) > $strlen) {
                    $ignoredChars = substr($string, $i);
                    break;
                }
                for ($j = 1; $j < $size; ++$j) {
                    $char = $string[$i + $j];
                    if ($char > "\x7F" && $char < "\xC0") {
                        // Valid - continue parsing
                    } else {
                        /* char is invalid, we must wait for a resync */
                        $invalid = true;
                        continue 2;
                    }
                }
                /* Ok we got a complete char here */
                $currentMap['p'][$charPos + $foundChars] = $startOffset + $i + $size;
                $i += $j - 1;
                ++$foundChars;
            }
        }

        return $foundChars;
    }

    /**
     * Returns mapType.
     *
     * @return int mapType
     */
    public function getMapType()
    {
        return self::MAP_TYPE_POSITIONS;
    }

    /**
     * Returns an integer which specifies how many more bytes to read.
     *
     * A positive integer indicates the number of more bytes to fetch before invoking
     * this method again.
     * A value of zero means this is already a valid character.
     * A value of -1 means this cannot possibly be a valid character.
     *
     * @param string $bytes
     * @param int    $size
     *
     * @return int
     */
    public function validateByteSequence($bytes, $size)
    {
        if ($size < 1) {
            return -1;
        }
        $needed = self::$length_map[$bytes[0]] - $size;

        return $needed > -1 ? $needed : -1;
    }

    /**
     * Returns the number of bytes which should be read to start each character.
     *
     * @return int
     */
    public function getInitialByteSize()
    {
        return 1;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Analyzes characters for a specific character set.
 *
 * @author Chris Corbyn
 * @author Xavier De Cock <xdecock@gmail.com>
 */
interface Swift_CharacterReader
{
    const MAP_TYPE_INVALID = 0x01;
    const MAP_TYPE_FIXED_LEN = 0x02;
    const MAP_TYPE_POSITIONS = 0x03;

    /**
     * Returns the complete character map.
     *
     * @param string $string
     * @param int    $startOffset
     * @param array  $currentMap
     * @param mixed  $ignoredChars
     *
     * @return int
     */
    public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars);

    /**
     * Returns the mapType, see constants.
     *
     * @return int
     */
    public function getMapType();

    /**
     * Returns an integer which specifies how many more bytes to read.
     *
     * A positive integer indicates the number of more bytes to fetch before invoking
     * this method again.
     *
     * A value of zero means this is already a valid character.
     * A value of -1 means this cannot possibly be a valid character.
     *
     * @param integer[] $bytes
     * @param int       $size
     *
     * @return int
     */
    public function validateByteSequence($bytes, $size);

    /**
     * Returns the number of bytes which should be read to start each character.
     *
     * For fixed width character sets this should be the number of octets-per-character.
     * For multibyte character sets this will probably be 1.
     *
     * @return int
     */
    public function getInitialByteSize();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Standard factory for creating CharacterReaders.
 *
 * @author Chris Corbyn
 */
class Swift_CharacterReaderFactory_SimpleCharacterReaderFactory implements Swift_CharacterReaderFactory
{
    /**
     * A map of charset patterns to their implementation classes.
     *
     * @var array
     */
    private static $_map = array();

    /**
     * Factories which have already been loaded.
     *
     * @var Swift_CharacterReaderFactory[]
     */
    private static $_loaded = array();

    /**
     * Creates a new CharacterReaderFactory.
     */
    public function __construct()
    {
        $this->init();
    }

    public function __wakeup()
    {
        $this->init();
    }

    public function init()
    {
        if (count(self::$_map) > 0) {
            return;
        }

        $prefix = 'Swift_CharacterReader_';

        $singleByte = array(
            'class' => $prefix.'GenericFixedWidthReader',
            'constructor' => array(1),
            );

        $doubleByte = array(
            'class' => $prefix.'GenericFixedWidthReader',
            'constructor' => array(2),
            );

        $fourBytes = array(
            'class' => $prefix.'GenericFixedWidthReader',
            'constructor' => array(4),
            );

        // Utf-8
        self::$_map['utf-?8'] = array(
            'class' => $prefix.'Utf8Reader',
            'constructor' => array(),
            );

        //7-8 bit charsets
        self::$_map['(us-)?ascii'] = $singleByte;
        self::$_map['(iso|iec)-?8859-?[0-9]+'] = $singleByte;
        self::$_map['windows-?125[0-9]'] = $singleByte;
        self::$_map['cp-?[0-9]+'] = $singleByte;
        self::$_map['ansi'] = $singleByte;
        self::$_map['macintosh'] = $singleByte;
        self::$_map['koi-?7'] = $singleByte;
        self::$_map['koi-?8-?.+'] = $singleByte;
        self::$_map['mik'] = $singleByte;
        self::$_map['(cork|t1)'] = $singleByte;
        self::$_map['v?iscii'] = $singleByte;

        //16 bits
        self::$_map['(ucs-?2|utf-?16)'] = $doubleByte;

        //32 bits
        self::$_map['(ucs-?4|utf-?32)'] = $fourBytes;

        // Fallback
        self::$_map['.*'] = $singleByte;
    }

    /**
     * Returns a CharacterReader suitable for the charset applied.
     *
     * @param string $charset
     *
     * @return Swift_CharacterReader
     */
    public function getReaderFor($charset)
    {
        $charset = trim(strtolower($charset));
        foreach (self::$_map as $pattern => $spec) {
            $re = '/^'.$pattern.'$/D';
            if (preg_match($re, $charset)) {
                if (!array_key_exists($pattern, self::$_loaded)) {
                    $reflector = new ReflectionClass($spec['class']);
                    if ($reflector->getConstructor()) {
                        $reader = $reflector->newInstanceArgs($spec['constructor']);
                    } else {
                        $reader = $reflector->newInstance();
                    }
                    self::$_loaded[$pattern] = $reader;
                }

                return self::$_loaded[$pattern];
            }
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A factory for creating CharacterReaders.
 *
 * @author Chris Corbyn
 */
interface Swift_CharacterReaderFactory
{
    /**
     * Returns a CharacterReader suitable for the charset applied.
     *
     * @param string $charset
     *
     * @return Swift_CharacterReader
     */
    public function getReaderFor($charset);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A CharacterStream implementation which stores characters in an internal array.
 *
 * @author Chris Corbyn
 */
class Swift_CharacterStream_ArrayCharacterStream implements Swift_CharacterStream
{
    /** A map of byte values and their respective characters */
    private static $_charMap;

    /** A map of characters and their derivative byte values */
    private static $_byteMap;

    /** The char reader (lazy-loaded) for the current charset */
    private $_charReader;

    /** A factory for creating CharacterReader instances */
    private $_charReaderFactory;

    /** The character set this stream is using */
    private $_charset;

    /** Array of characters */
    private $_array = array();

    /** Size of the array of character */
    private $_array_size = array();

    /** The current character offset in the stream */
    private $_offset = 0;

    /**
     * Create a new CharacterStream with the given $chars, if set.
     *
     * @param Swift_CharacterReaderFactory $factory for loading validators
     * @param string                       $charset used in the stream
     */
    public function __construct(Swift_CharacterReaderFactory $factory, $charset)
    {
        self::_initializeMaps();
        $this->setCharacterReaderFactory($factory);
        $this->setCharacterSet($charset);
    }

    /**
     * Set the character set used in this CharacterStream.
     *
     * @param string $charset
     */
    public function setCharacterSet($charset)
    {
        $this->_charset = $charset;
        $this->_charReader = null;
    }

    /**
     * Set the CharacterReaderFactory for multi charset support.
     *
     * @param Swift_CharacterReaderFactory $factory
     */
    public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory)
    {
        $this->_charReaderFactory = $factory;
    }

    /**
     * Overwrite this character stream using the byte sequence in the byte stream.
     *
     * @param Swift_OutputByteStream $os output stream to read from
     */
    public function importByteStream(Swift_OutputByteStream $os)
    {
        if (!isset($this->_charReader)) {
            $this->_charReader = $this->_charReaderFactory
                ->getReaderFor($this->_charset);
        }

        $startLength = $this->_charReader->getInitialByteSize();
        while (false !== $bytes = $os->read($startLength)) {
            $c = array();
            for ($i = 0, $len = strlen($bytes); $i < $len; ++$i) {
                $c[] = self::$_byteMap[$bytes[$i]];
            }
            $size = count($c);
            $need = $this->_charReader
                ->validateByteSequence($c, $size);
            if ($need > 0 &&
                false !== $bytes = $os->read($need)) {
                for ($i = 0, $len = strlen($bytes); $i < $len; ++$i) {
                    $c[] = self::$_byteMap[$bytes[$i]];
                }
            }
            $this->_array[] = $c;
            ++$this->_array_size;
        }
    }

    /**
     * Import a string a bytes into this CharacterStream, overwriting any existing
     * data in the stream.
     *
     * @param string $string
     */
    public function importString($string)
    {
        $this->flushContents();
        $this->write($string);
    }

    /**
     * Read $length characters from the stream and move the internal pointer
     * $length further into the stream.
     *
     * @param int $length
     *
     * @return string
     */
    public function read($length)
    {
        if ($this->_offset == $this->_array_size) {
            return false;
        }

        // Don't use array slice
        $arrays = array();
        $end = $length + $this->_offset;
        for ($i = $this->_offset; $i < $end; ++$i) {
            if (!isset($this->_array[$i])) {
                break;
            }
            $arrays[] = $this->_array[$i];
        }
        $this->_offset += $i - $this->_offset; // Limit function calls
        $chars = false;
        foreach ($arrays as $array) {
            $chars .= implode('', array_map('chr', $array));
        }

        return $chars;
    }

    /**
     * Read $length characters from the stream and return a 1-dimensional array
     * containing there octet values.
     *
     * @param int $length
     *
     * @return integer[]
     */
    public function readBytes($length)
    {
        if ($this->_offset == $this->_array_size) {
            return false;
        }
        $arrays = array();
        $end = $length + $this->_offset;
        for ($i = $this->_offset; $i < $end; ++$i) {
            if (!isset($this->_array[$i])) {
                break;
            }
            $arrays[] = $this->_array[$i];
        }
        $this->_offset += ($i - $this->_offset); // Limit function calls

        return call_user_func_array('array_merge', $arrays);
    }

    /**
     * Write $chars to the end of the stream.
     *
     * @param string $chars
     */
    public function write($chars)
    {
        if (!isset($this->_charReader)) {
            $this->_charReader = $this->_charReaderFactory->getReaderFor(
                $this->_charset);
        }

        $startLength = $this->_charReader->getInitialByteSize();

        $fp = fopen('php://memory', 'w+b');
        fwrite($fp, $chars);
        unset($chars);
        fseek($fp, 0, SEEK_SET);

        $buffer = array(0);
        $buf_pos = 1;
        $buf_len = 1;
        $has_datas = true;
        do {
            $bytes = array();
            // Buffer Filing
            if ($buf_len - $buf_pos < $startLength) {
                $buf = array_splice($buffer, $buf_pos);
                $new = $this->_reloadBuffer($fp, 100);
                if ($new) {
                    $buffer = array_merge($buf, $new);
                    $buf_len = count($buffer);
                    $buf_pos = 0;
                } else {
                    $has_datas = false;
                }
            }
            if ($buf_len - $buf_pos > 0) {
                $size = 0;
                for ($i = 0; $i < $startLength && isset($buffer[$buf_pos]); ++$i) {
                    ++$size;
                    $bytes[] = $buffer[$buf_pos++];
                }
                $need = $this->_charReader->validateByteSequence(
                    $bytes, $size);
                if ($need > 0) {
                    if ($buf_len - $buf_pos < $need) {
                        $new = $this->_reloadBuffer($fp, $need);

                        if ($new) {
                            $buffer = array_merge($buffer, $new);
                            $buf_len = count($buffer);
                        }
                    }
                    for ($i = 0; $i < $need && isset($buffer[$buf_pos]); ++$i) {
                        $bytes[] = $buffer[$buf_pos++];
                    }
                }
                $this->_array[] = $bytes;
                ++$this->_array_size;
            }
        } while ($has_datas);

        fclose($fp);
    }

    /**
     * Move the internal pointer to $charOffset in the stream.
     *
     * @param int $charOffset
     */
    public function setPointer($charOffset)
    {
        if ($charOffset > $this->_array_size) {
            $charOffset = $this->_array_size;
        } elseif ($charOffset < 0) {
            $charOffset = 0;
        }
        $this->_offset = $charOffset;
    }

    /**
     * Empty the stream and reset the internal pointer.
     */
    public function flushContents()
    {
        $this->_offset = 0;
        $this->_array = array();
        $this->_array_size = 0;
    }

    private function _reloadBuffer($fp, $len)
    {
        if (!feof($fp) && ($bytes = fread($fp, $len)) !== false) {
            $buf = array();
            for ($i = 0, $len = strlen($bytes); $i < $len; ++$i) {
                $buf[] = self::$_byteMap[$bytes[$i]];
            }

            return $buf;
        }

        return false;
    }

    private static function _initializeMaps()
    {
        if (!isset(self::$_charMap)) {
            self::$_charMap = array();
            for ($byte = 0; $byte < 256; ++$byte) {
                self::$_charMap[$byte] = chr($byte);
            }
            self::$_byteMap = array_flip(self::$_charMap);
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A CharacterStream implementation which stores characters in an internal array.
 *
 * @author Xavier De Cock <xdecock@gmail.com>
 */
class Swift_CharacterStream_NgCharacterStream implements Swift_CharacterStream
{
    /**
     * The char reader (lazy-loaded) for the current charset.
     *
     * @var Swift_CharacterReader
     */
    private $_charReader;

    /**
     * A factory for creating CharacterReader instances.
     *
     * @var Swift_CharacterReaderFactory
     */
    private $_charReaderFactory;

    /**
     * The character set this stream is using.
     *
     * @var string
     */
    private $_charset;

    /**
     * The data's stored as-is.
     *
     * @var string
     */
    private $_datas = '';

    /**
     * Number of bytes in the stream.
     *
     * @var int
     */
    private $_datasSize = 0;

    /**
     * Map.
     *
     * @var mixed
     */
    private $_map;

    /**
     * Map Type.
     *
     * @var int
     */
    private $_mapType = 0;

    /**
     * Number of characters in the stream.
     *
     * @var int
     */
    private $_charCount = 0;

    /**
     * Position in the stream.
     *
     * @var int
     */
    private $_currentPos = 0;

    /**
     * Constructor.
     *
     * @param Swift_CharacterReaderFactory $factory
     * @param string                       $charset
     */
    public function __construct(Swift_CharacterReaderFactory $factory, $charset)
    {
        $this->setCharacterReaderFactory($factory);
        $this->setCharacterSet($charset);
    }

    /* -- Changing parameters of the stream -- */

    /**
     * Set the character set used in this CharacterStream.
     *
     * @param string $charset
     */
    public function setCharacterSet($charset)
    {
        $this->_charset = $charset;
        $this->_charReader = null;
        $this->_mapType = 0;
    }

    /**
     * Set the CharacterReaderFactory for multi charset support.
     *
     * @param Swift_CharacterReaderFactory $factory
     */
    public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory)
    {
        $this->_charReaderFactory = $factory;
    }

    /**
     * @see Swift_CharacterStream::flushContents()
     */
    public function flushContents()
    {
        $this->_datas = null;
        $this->_map = null;
        $this->_charCount = 0;
        $this->_currentPos = 0;
        $this->_datasSize = 0;
    }

    /**
     * @see Swift_CharacterStream::importByteStream()
     *
     * @param Swift_OutputByteStream $os
     */
    public function importByteStream(Swift_OutputByteStream $os)
    {
        $this->flushContents();
        $blocks = 512;
        $os->setReadPointer(0);
        while (false !== ($read = $os->read($blocks))) {
            $this->write($read);
        }
    }

    /**
     * @see Swift_CharacterStream::importString()
     *
     * @param string $string
     */
    public function importString($string)
    {
        $this->flushContents();
        $this->write($string);
    }

    /**
     * @see Swift_CharacterStream::read()
     *
     * @param int $length
     *
     * @return string
     */
    public function read($length)
    {
        if ($this->_currentPos >= $this->_charCount) {
            return false;
        }
        $ret = false;
        $length = $this->_currentPos + $length > $this->_charCount ? $this->_charCount - $this->_currentPos : $length;
        switch ($this->_mapType) {
            case Swift_CharacterReader::MAP_TYPE_FIXED_LEN:
                $len = $length * $this->_map;
                $ret = substr($this->_datas,
                        $this->_currentPos * $this->_map,
                        $len);
                $this->_currentPos += $length;
                break;

            case Swift_CharacterReader::MAP_TYPE_INVALID:
                $ret = '';
                for (; $this->_currentPos < $length; ++$this->_currentPos) {
                    if (isset($this->_map[$this->_currentPos])) {
                        $ret .= '?';
                    } else {
                        $ret .= $this->_datas[$this->_currentPos];
                    }
                }
                break;

            case Swift_CharacterReader::MAP_TYPE_POSITIONS:
                $end = $this->_currentPos + $length;
                $end = $end > $this->_charCount ? $this->_charCount : $end;
                $ret = '';
                $start = 0;
                if ($this->_currentPos > 0) {
                    $start = $this->_map['p'][$this->_currentPos - 1];
                }
                $to = $start;
                for (; $this->_currentPos < $end; ++$this->_currentPos) {
                    if (isset($this->_map['i'][$this->_currentPos])) {
                        $ret .= substr($this->_datas, $start, $to - $start).'?';
                        $start = $this->_map['p'][$this->_currentPos];
                    } else {
                        $to = $this->_map['p'][$this->_currentPos];
                    }
                }
                $ret .= substr($this->_datas, $start, $to - $start);
                break;
        }

        return $ret;
    }

    /**
     * @see Swift_CharacterStream::readBytes()
     *
     * @param int $length
     *
     * @return int[]
     */
    public function readBytes($length)
    {
        $read = $this->read($length);
        if ($read !== false) {
            $ret = array_map('ord', str_split($read, 1));

            return $ret;
        }

        return false;
    }

    /**
     * @see Swift_CharacterStream::setPointer()
     *
     * @param int $charOffset
     */
    public function setPointer($charOffset)
    {
        if ($this->_charCount < $charOffset) {
            $charOffset = $this->_charCount;
        }
        $this->_currentPos = $charOffset;
    }

    /**
     * @see Swift_CharacterStream::write()
     *
     * @param string $chars
     */
    public function write($chars)
    {
        if (!isset($this->_charReader)) {
            $this->_charReader = $this->_charReaderFactory->getReaderFor(
                $this->_charset);
            $this->_map = array();
            $this->_mapType = $this->_charReader->getMapType();
        }
        $ignored = '';
        $this->_datas .= $chars;
        $this->_charCount += $this->_charReader->getCharPositions(substr($this->_datas, $this->_datasSize), $this->_datasSize, $this->_map, $ignored);
        if ($ignored !== false) {
            $this->_datasSize = strlen($this->_datas) - strlen($ignored);
        } else {
            $this->_datasSize = strlen($this->_datas);
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An abstract means of reading and writing data in terms of characters as opposed
 * to bytes.
 *
 * Classes implementing this interface may use a subsystem which requires less
 * memory than working with large strings of data.
 *
 * @author Chris Corbyn
 */
interface Swift_CharacterStream
{
    /**
     * Set the character set used in this CharacterStream.
     *
     * @param string $charset
     */
    public function setCharacterSet($charset);

    /**
     * Set the CharacterReaderFactory for multi charset support.
     *
     * @param Swift_CharacterReaderFactory $factory
     */
    public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory);

    /**
     * Overwrite this character stream using the byte sequence in the byte stream.
     *
     * @param Swift_OutputByteStream $os output stream to read from
     */
    public function importByteStream(Swift_OutputByteStream $os);

    /**
     * Import a string a bytes into this CharacterStream, overwriting any existing
     * data in the stream.
     *
     * @param string $string
     */
    public function importString($string);

    /**
     * Read $length characters from the stream and move the internal pointer
     * $length further into the stream.
     *
     * @param int $length
     *
     * @return string
     */
    public function read($length);

    /**
     * Read $length characters from the stream and return a 1-dimensional array
     * containing there octet values.
     *
     * @param int $length
     *
     * @return int[]
     */
    public function readBytes($length);

    /**
     * Write $chars to the end of the stream.
     *
     * @param string $chars
     */
    public function write($chars);

    /**
     * Move the internal pointer to $charOffset in the stream.
     *
     * @param int $charOffset
     */
    public function setPointer($charOffset);

    /**
     * Empty the stream and reset the internal pointer.
     */
    public function flushContents();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Base class for Spools (implements time and message limits).
 *
 * @author Fabien Potencier
 */
abstract class Swift_ConfigurableSpool implements Swift_Spool
{
    /** The maximum number of messages to send per flush */
    private $_message_limit;

    /** The time limit per flush */
    private $_time_limit;

    /**
     * Sets the maximum number of messages to send per flush.
     *
     * @param int $limit
     */
    public function setMessageLimit($limit)
    {
        $this->_message_limit = (int) $limit;
    }

    /**
     * Gets the maximum number of messages to send per flush.
     *
     * @return int The limit
     */
    public function getMessageLimit()
    {
        return $this->_message_limit;
    }

    /**
     * Sets the time limit (in seconds) per flush.
     *
     * @param int $limit The limit
     */
    public function setTimeLimit($limit)
    {
        $this->_time_limit = (int) $limit;
    }

    /**
     * Gets the time limit (in seconds) per flush.
     *
     * @return int The limit
     */
    public function getTimeLimit()
    {
        return $this->_time_limit;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Dependency Injection container.
 *
 * @author Chris Corbyn
 */
class Swift_DependencyContainer
{
    /** Constant for literal value types */
    const TYPE_VALUE = 0x0001;

    /** Constant for new instance types */
    const TYPE_INSTANCE = 0x0010;

    /** Constant for shared instance types */
    const TYPE_SHARED = 0x0100;

    /** Constant for aliases */
    const TYPE_ALIAS = 0x1000;

    /** Singleton instance */
    private static $_instance = null;

    /** The data container */
    private $_store = array();

    /** The current endpoint in the data container */
    private $_endPoint;

    /**
     * Constructor should not be used.
     *
     * Use {@link getInstance()} instead.
     */
    public function __construct()
    {
    }

    /**
     * Returns a singleton of the DependencyContainer.
     *
     * @return Swift_DependencyContainer
     */
    public static function getInstance()
    {
        if (!isset(self::$_instance)) {
            self::$_instance = new self();
        }

        return self::$_instance;
    }

    /**
     * List the names of all items stored in the Container.
     *
     * @return array
     */
    public function listItems()
    {
        return array_keys($this->_store);
    }

    /**
     * Test if an item is registered in this container with the given name.
     *
     * @see register()
     *
     * @param string $itemName
     *
     * @return bool
     */
    public function has($itemName)
    {
        return array_key_exists($itemName, $this->_store)
            && isset($this->_store[$itemName]['lookupType']);
    }

    /**
     * Lookup the item with the given $itemName.
     *
     * @see register()
     *
     * @param string $itemName
     *
     * @throws Swift_DependencyException If the dependency is not found
     *
     * @return mixed
     */
    public function lookup($itemName)
    {
        if (!$this->has($itemName)) {
            throw new Swift_DependencyException(
                'Cannot lookup dependency "'.$itemName.'" since it is not registered.'
                );
        }

        switch ($this->_store[$itemName]['lookupType']) {
            case self::TYPE_ALIAS:
                return $this->_createAlias($itemName);
            case self::TYPE_VALUE:
                return $this->_getValue($itemName);
            case self::TYPE_INSTANCE:
                return $this->_createNewInstance($itemName);
            case self::TYPE_SHARED:
                return $this->_createSharedInstance($itemName);
        }
    }

    /**
     * Create an array of arguments passed to the constructor of $itemName.
     *
     * @param string $itemName
     *
     * @return array
     */
    public function createDependenciesFor($itemName)
    {
        $args = array();
        if (isset($this->_store[$itemName]['args'])) {
            $args = $this->_resolveArgs($this->_store[$itemName]['args']);
        }

        return $args;
    }

    /**
     * Register a new dependency with $itemName.
     *
     * This method returns the current DependencyContainer instance because it
     * requires the use of the fluid interface to set the specific details for the
     * dependency.
     *
     * @see asNewInstanceOf(), asSharedInstanceOf(), asValue()
     *
     * @param string $itemName
     *
     * @return Swift_DependencyContainer
     */
    public function register($itemName)
    {
        $this->_store[$itemName] = array();
        $this->_endPoint = &$this->_store[$itemName];

        return $this;
    }

    /**
     * Specify the previously registered item as a literal value.
     *
     * {@link register()} must be called before this will work.
     *
     * @param mixed $value
     *
     * @return Swift_DependencyContainer
     */
    public function asValue($value)
    {
        $endPoint = &$this->_getEndPoint();
        $endPoint['lookupType'] = self::TYPE_VALUE;
        $endPoint['value'] = $value;

        return $this;
    }

    /**
     * Specify the previously registered item as an alias of another item.
     *
     * @param string $lookup
     *
     * @return Swift_DependencyContainer
     */
    public function asAliasOf($lookup)
    {
        $endPoint = &$this->_getEndPoint();
        $endPoint['lookupType'] = self::TYPE_ALIAS;
        $endPoint['ref'] = $lookup;

        return $this;
    }

    /**
     * Specify the previously registered item as a new instance of $className.
     *
     * {@link register()} must be called before this will work.
     * Any arguments can be set with {@link withDependencies()},
     * {@link addConstructorValue()} or {@link addConstructorLookup()}.
     *
     * @see withDependencies(), addConstructorValue(), addConstructorLookup()
     *
     * @param string $className
     *
     * @return Swift_DependencyContainer
     */
    public function asNewInstanceOf($className)
    {
        $endPoint = &$this->_getEndPoint();
        $endPoint['lookupType'] = self::TYPE_INSTANCE;
        $endPoint['className'] = $className;

        return $this;
    }

    /**
     * Specify the previously registered item as a shared instance of $className.
     *
     * {@link register()} must be called before this will work.
     *
     * @param string $className
     *
     * @return Swift_DependencyContainer
     */
    public function asSharedInstanceOf($className)
    {
        $endPoint = &$this->_getEndPoint();
        $endPoint['lookupType'] = self::TYPE_SHARED;
        $endPoint['className'] = $className;

        return $this;
    }

    /**
     * Specify a list of injected dependencies for the previously registered item.
     *
     * This method takes an array of lookup names.
     *
     * @see addConstructorValue(), addConstructorLookup()
     *
     * @param array $lookups
     *
     * @return Swift_DependencyContainer
     */
    public function withDependencies(array $lookups)
    {
        $endPoint = &$this->_getEndPoint();
        $endPoint['args'] = array();
        foreach ($lookups as $lookup) {
            $this->addConstructorLookup($lookup);
        }

        return $this;
    }

    /**
     * Specify a literal (non looked up) value for the constructor of the
     * previously registered item.
     *
     * @see withDependencies(), addConstructorLookup()
     *
     * @param mixed $value
     *
     * @return Swift_DependencyContainer
     */
    public function addConstructorValue($value)
    {
        $endPoint = &$this->_getEndPoint();
        if (!isset($endPoint['args'])) {
            $endPoint['args'] = array();
        }
        $endPoint['args'][] = array('type' => 'value', 'item' => $value);

        return $this;
    }

    /**
     * Specify a dependency lookup for the constructor of the previously
     * registered item.
     *
     * @see withDependencies(), addConstructorValue()
     *
     * @param string $lookup
     *
     * @return Swift_DependencyContainer
     */
    public function addConstructorLookup($lookup)
    {
        $endPoint = &$this->_getEndPoint();
        if (!isset($this->_endPoint['args'])) {
            $endPoint['args'] = array();
        }
        $endPoint['args'][] = array('type' => 'lookup', 'item' => $lookup);

        return $this;
    }

    /** Get the literal value with $itemName */
    private function _getValue($itemName)
    {
        return $this->_store[$itemName]['value'];
    }

    /** Resolve an alias to another item */
    private function _createAlias($itemName)
    {
        return $this->lookup($this->_store[$itemName]['ref']);
    }

    /** Create a fresh instance of $itemName */
    private function _createNewInstance($itemName)
    {
        $reflector = new ReflectionClass($this->_store[$itemName]['className']);
        if ($reflector->getConstructor()) {
            return $reflector->newInstanceArgs(
                $this->createDependenciesFor($itemName)
                );
        }

        return $reflector->newInstance();
    }

    /** Create and register a shared instance of $itemName */
    private function _createSharedInstance($itemName)
    {
        if (!isset($this->_store[$itemName]['instance'])) {
            $this->_store[$itemName]['instance'] = $this->_createNewInstance($itemName);
        }

        return $this->_store[$itemName]['instance'];
    }

    /** Get the current endpoint in the store */
    private function &_getEndPoint()
    {
        if (!isset($this->_endPoint)) {
            throw new BadMethodCallException(
                'Component must first be registered by calling register()'
                );
        }

        return $this->_endPoint;
    }

    /** Get an argument list with dependencies resolved */
    private function _resolveArgs(array $args)
    {
        $resolved = array();
        foreach ($args as $argDefinition) {
            switch ($argDefinition['type']) {
                case 'lookup':
                    $resolved[] = $this->_lookupRecursive($argDefinition['item']);
                    break;
                case 'value':
                    $resolved[] = $argDefinition['item'];
                    break;
            }
        }

        return $resolved;
    }

    /** Resolve a single dependency with an collections */
    private function _lookupRecursive($item)
    {
        if (is_array($item)) {
            $collection = array();
            foreach ($item as $k => $v) {
                $collection[$k] = $this->_lookupRecursive($v);
            }

            return $collection;
        }

        return $this->lookup($item);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * DependencyException gets thrown when a requested dependency is missing.
 *
 * @author Chris Corbyn
 */
class Swift_DependencyException extends Swift_SwiftException
{
    /**
     * Create a new DependencyException with $message.
     *
     * @param string $message
     */
    public function __construct($message)
    {
        parent::__construct($message);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An embedded file, in a multipart message.
 *
 * @author Chris Corbyn
 */
class Swift_EmbeddedFile extends Swift_Mime_EmbeddedFile
{
    /**
     * Create a new EmbeddedFile.
     *
     * Details may be optionally provided to the constructor.
     *
     * @param string|Swift_OutputByteStream $data
     * @param string                        $filename
     * @param string                        $contentType
     */
    public function __construct($data = null, $filename = null, $contentType = null)
    {
        call_user_func_array(
            array($this, 'Swift_Mime_EmbeddedFile::__construct'),
            Swift_DependencyContainer::getInstance()
                ->createDependenciesFor('mime.embeddedfile')
            );

        $this->setBody($data);
        $this->setFilename($filename);
        if ($contentType) {
            $this->setContentType($contentType);
        }
    }

    /**
     * Create a new EmbeddedFile.
     *
     * @param string|Swift_OutputByteStream $data
     * @param string                        $filename
     * @param string                        $contentType
     *
     * @return Swift_Mime_EmbeddedFile
     */
    public static function newInstance($data = null, $filename = null, $contentType = null)
    {
        return new self($data, $filename, $contentType);
    }

    /**
     * Create a new EmbeddedFile from a filesystem path.
     *
     * @param string $path
     *
     * @return Swift_Mime_EmbeddedFile
     */
    public static function fromPath($path)
    {
        return self::newInstance()->setFile(
            new Swift_ByteStream_FileByteStream($path)
            );
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Handles Base 64 Encoding in Swift Mailer.
 *
 * @author Chris Corbyn
 */
class Swift_Encoder_Base64Encoder implements Swift_Encoder
{
    /**
     * Takes an unencoded string and produces a Base64 encoded string from it.
     *
     * Base64 encoded strings have a maximum line length of 76 characters.
     * If the first line needs to be shorter, indicate the difference with
     * $firstLineOffset.
     *
     * @param string $string          to encode
     * @param int    $firstLineOffset
     * @param int    $maxLineLength   optional, 0 indicates the default of 76 bytes
     *
     * @return string
     */
    public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
    {
        if (0 >= $maxLineLength || 76 < $maxLineLength) {
            $maxLineLength = 76;
        }

        $encodedString = base64_encode($string);
        $firstLine = '';

        if (0 != $firstLineOffset) {
            $firstLine = substr(
                $encodedString, 0, $maxLineLength - $firstLineOffset
                )."\r\n";
            $encodedString = substr(
                $encodedString, $maxLineLength - $firstLineOffset
                );
        }

        return $firstLine.trim(chunk_split($encodedString, $maxLineLength, "\r\n"));
    }

    /**
     * Does nothing.
     */
    public function charsetChanged($charset)
    {
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Handles Quoted Printable (QP) Encoding in Swift Mailer.
 *
 * Possibly the most accurate RFC 2045 QP implementation found in PHP.
 *
 * @author Chris Corbyn
 */
class Swift_Encoder_QpEncoder implements Swift_Encoder
{
    /**
     * The CharacterStream used for reading characters (as opposed to bytes).
     *
     * @var Swift_CharacterStream
     */
    protected $_charStream;

    /**
     * A filter used if input should be canonicalized.
     *
     * @var Swift_StreamFilter
     */
    protected $_filter;

    /**
     * Pre-computed QP for HUGE optimization.
     *
     * @var string[]
     */
    protected static $_qpMap = array(
        0 => '=00', 1 => '=01', 2 => '=02', 3 => '=03', 4 => '=04',
        5 => '=05', 6 => '=06', 7 => '=07', 8 => '=08', 9 => '=09',
        10 => '=0A', 11 => '=0B', 12 => '=0C', 13 => '=0D', 14 => '=0E',
        15 => '=0F', 16 => '=10', 17 => '=11', 18 => '=12', 19 => '=13',
        20 => '=14', 21 => '=15', 22 => '=16', 23 => '=17', 24 => '=18',
        25 => '=19', 26 => '=1A', 27 => '=1B', 28 => '=1C', 29 => '=1D',
        30 => '=1E', 31 => '=1F', 32 => '=20', 33 => '=21', 34 => '=22',
        35 => '=23', 36 => '=24', 37 => '=25', 38 => '=26', 39 => '=27',
        40 => '=28', 41 => '=29', 42 => '=2A', 43 => '=2B', 44 => '=2C',
        45 => '=2D', 46 => '=2E', 47 => '=2F', 48 => '=30', 49 => '=31',
        50 => '=32', 51 => '=33', 52 => '=34', 53 => '=35', 54 => '=36',
        55 => '=37', 56 => '=38', 57 => '=39', 58 => '=3A', 59 => '=3B',
        60 => '=3C', 61 => '=3D', 62 => '=3E', 63 => '=3F', 64 => '=40',
        65 => '=41', 66 => '=42', 67 => '=43', 68 => '=44', 69 => '=45',
        70 => '=46', 71 => '=47', 72 => '=48', 73 => '=49', 74 => '=4A',
        75 => '=4B', 76 => '=4C', 77 => '=4D', 78 => '=4E', 79 => '=4F',
        80 => '=50', 81 => '=51', 82 => '=52', 83 => '=53', 84 => '=54',
        85 => '=55', 86 => '=56', 87 => '=57', 88 => '=58', 89 => '=59',
        90 => '=5A', 91 => '=5B', 92 => '=5C', 93 => '=5D', 94 => '=5E',
        95 => '=5F', 96 => '=60', 97 => '=61', 98 => '=62', 99 => '=63',
        100 => '=64', 101 => '=65', 102 => '=66', 103 => '=67', 104 => '=68',
        105 => '=69', 106 => '=6A', 107 => '=6B', 108 => '=6C', 109 => '=6D',
        110 => '=6E', 111 => '=6F', 112 => '=70', 113 => '=71', 114 => '=72',
        115 => '=73', 116 => '=74', 117 => '=75', 118 => '=76', 119 => '=77',
        120 => '=78', 121 => '=79', 122 => '=7A', 123 => '=7B', 124 => '=7C',
        125 => '=7D', 126 => '=7E', 127 => '=7F', 128 => '=80', 129 => '=81',
        130 => '=82', 131 => '=83', 132 => '=84', 133 => '=85', 134 => '=86',
        135 => '=87', 136 => '=88', 137 => '=89', 138 => '=8A', 139 => '=8B',
        140 => '=8C', 141 => '=8D', 142 => '=8E', 143 => '=8F', 144 => '=90',
        145 => '=91', 146 => '=92', 147 => '=93', 148 => '=94', 149 => '=95',
        150 => '=96', 151 => '=97', 152 => '=98', 153 => '=99', 154 => '=9A',
        155 => '=9B', 156 => '=9C', 157 => '=9D', 158 => '=9E', 159 => '=9F',
        160 => '=A0', 161 => '=A1', 162 => '=A2', 163 => '=A3', 164 => '=A4',
        165 => '=A5', 166 => '=A6', 167 => '=A7', 168 => '=A8', 169 => '=A9',
        170 => '=AA', 171 => '=AB', 172 => '=AC', 173 => '=AD', 174 => '=AE',
        175 => '=AF', 176 => '=B0', 177 => '=B1', 178 => '=B2', 179 => '=B3',
        180 => '=B4', 181 => '=B5', 182 => '=B6', 183 => '=B7', 184 => '=B8',
        185 => '=B9', 186 => '=BA', 187 => '=BB', 188 => '=BC', 189 => '=BD',
        190 => '=BE', 191 => '=BF', 192 => '=C0', 193 => '=C1', 194 => '=C2',
        195 => '=C3', 196 => '=C4', 197 => '=C5', 198 => '=C6', 199 => '=C7',
        200 => '=C8', 201 => '=C9', 202 => '=CA', 203 => '=CB', 204 => '=CC',
        205 => '=CD', 206 => '=CE', 207 => '=CF', 208 => '=D0', 209 => '=D1',
        210 => '=D2', 211 => '=D3', 212 => '=D4', 213 => '=D5', 214 => '=D6',
        215 => '=D7', 216 => '=D8', 217 => '=D9', 218 => '=DA', 219 => '=DB',
        220 => '=DC', 221 => '=DD', 222 => '=DE', 223 => '=DF', 224 => '=E0',
        225 => '=E1', 226 => '=E2', 227 => '=E3', 228 => '=E4', 229 => '=E5',
        230 => '=E6', 231 => '=E7', 232 => '=E8', 233 => '=E9', 234 => '=EA',
        235 => '=EB', 236 => '=EC', 237 => '=ED', 238 => '=EE', 239 => '=EF',
        240 => '=F0', 241 => '=F1', 242 => '=F2', 243 => '=F3', 244 => '=F4',
        245 => '=F5', 246 => '=F6', 247 => '=F7', 248 => '=F8', 249 => '=F9',
        250 => '=FA', 251 => '=FB', 252 => '=FC', 253 => '=FD', 254 => '=FE',
        255 => '=FF',
        );

    protected static $_safeMapShare = array();

    /**
     * A map of non-encoded ascii characters.
     *
     * @var string[]
     */
    protected $_safeMap = array();

    /**
     * Creates a new QpEncoder for the given CharacterStream.
     *
     * @param Swift_CharacterStream $charStream to use for reading characters
     * @param Swift_StreamFilter    $filter     if input should be canonicalized
     */
    public function __construct(Swift_CharacterStream $charStream, Swift_StreamFilter $filter = null)
    {
        $this->_charStream = $charStream;
        if (!isset(self::$_safeMapShare[$this->getSafeMapShareId()])) {
            $this->initSafeMap();
            self::$_safeMapShare[$this->getSafeMapShareId()] = $this->_safeMap;
        } else {
            $this->_safeMap = self::$_safeMapShare[$this->getSafeMapShareId()];
        }
        $this->_filter = $filter;
    }

    public function __sleep()
    {
        return array('_charStream', '_filter');
    }

    public function __wakeup()
    {
        if (!isset(self::$_safeMapShare[$this->getSafeMapShareId()])) {
            $this->initSafeMap();
            self::$_safeMapShare[$this->getSafeMapShareId()] = $this->_safeMap;
        } else {
            $this->_safeMap = self::$_safeMapShare[$this->getSafeMapShareId()];
        }
    }

    protected function getSafeMapShareId()
    {
        return get_class($this);
    }

    protected function initSafeMap()
    {
        foreach (array_merge(
            array(0x09, 0x20), range(0x21, 0x3C), range(0x3E, 0x7E)) as $byte) {
            $this->_safeMap[$byte] = chr($byte);
        }
    }

    /**
     * Takes an unencoded string and produces a QP encoded string from it.
     *
     * QP encoded strings have a maximum line length of 76 characters.
     * If the first line needs to be shorter, indicate the difference with
     * $firstLineOffset.
     *
     * @param string $string           to encode
     * @param int    $firstLineOffset, optional
     * @param int    $maxLineLength,   optional 0 indicates the default of 76 chars
     *
     * @return string
     */
    public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
    {
        if ($maxLineLength > 76 || $maxLineLength <= 0) {
            $maxLineLength = 76;
        }

        $thisLineLength = $maxLineLength - $firstLineOffset;

        $lines = array();
        $lNo = 0;
        $lines[$lNo] = '';
        $currentLine = &$lines[$lNo++];
        $size = $lineLen = 0;

        $this->_charStream->flushContents();
        $this->_charStream->importString($string);

        // Fetching more than 4 chars at one is slower, as is fetching fewer bytes
        // Conveniently 4 chars is the UTF-8 safe number since UTF-8 has up to 6
        // bytes per char and (6 * 4 * 3 = 72 chars per line) * =NN is 3 bytes
        while (false !== $bytes = $this->_nextSequence()) {
            // If we're filtering the input
            if (isset($this->_filter)) {
                // If we can't filter because we need more bytes
                while ($this->_filter->shouldBuffer($bytes)) {
                    // Then collect bytes into the buffer
                    if (false === $moreBytes = $this->_nextSequence(1)) {
                        break;
                    }

                    foreach ($moreBytes as $b) {
                        $bytes[] = $b;
                    }
                }
                // And filter them
                $bytes = $this->_filter->filter($bytes);
            }

            $enc = $this->_encodeByteSequence($bytes, $size);

            $i = strpos($enc, '=0D=0A');
            $newLineLength = $lineLen + ($i === false ? $size : $i);

            if ($currentLine && $newLineLength >= $thisLineLength) {
                $lines[$lNo] = '';
                $currentLine = &$lines[$lNo++];
                $thisLineLength = $maxLineLength;
                $lineLen = 0;
            }

            $currentLine .= $enc;

            if ($i === false) {
                $lineLen += $size;
            } else {
                // 6 is the length of '=0D=0A'.
                $lineLen = $size - strrpos($enc, '=0D=0A') - 6;
            }
        }

        return $this->_standardize(implode("=\r\n", $lines));
    }

    /**
     * Updates the charset used.
     *
     * @param string $charset
     */
    public function charsetChanged($charset)
    {
        $this->_charStream->setCharacterSet($charset);
    }

    /**
     * Encode the given byte array into a verbatim QP form.
     *
     * @param integer[] $bytes
     * @param int       $size
     *
     * @return string
     */
    protected function _encodeByteSequence(array $bytes, &$size)
    {
        $ret = '';
        $size = 0;
        foreach ($bytes as $b) {
            if (isset($this->_safeMap[$b])) {
                $ret .= $this->_safeMap[$b];
                ++$size;
            } else {
                $ret .= self::$_qpMap[$b];
                $size += 3;
            }
        }

        return $ret;
    }

    /**
     * Get the next sequence of bytes to read from the char stream.
     *
     * @param int $size number of bytes to read
     *
     * @return integer[]
     */
    protected function _nextSequence($size = 4)
    {
        return $this->_charStream->readBytes($size);
    }

    /**
     * Make sure CRLF is correct and HT/SPACE are in valid places.
     *
     * @param string $string
     *
     * @return string
     */
    protected function _standardize($string)
    {
        $string = str_replace(array("\t=0D=0A", ' =0D=0A', '=0D=0A'),
            array("=09\r\n", "=20\r\n", "\r\n"), $string
            );
        switch ($end = ord(substr($string, -1))) {
            case 0x09:
            case 0x20:
                $string = substr_replace($string, self::$_qpMap[$end], -1);
        }

        return $string;
    }

    /**
     * Make a deep copy of object.
     */
    public function __clone()
    {
        $this->_charStream = clone $this->_charStream;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Handles RFC 2231 specified Encoding in Swift Mailer.
 *
 * @author Chris Corbyn
 */
class Swift_Encoder_Rfc2231Encoder implements Swift_Encoder
{
    /**
     * A character stream to use when reading a string as characters instead of bytes.
     *
     * @var Swift_CharacterStream
     */
    private $_charStream;

    /**
     * Creates a new Rfc2231Encoder using the given character stream instance.
     *
     * @param Swift_CharacterStream
     */
    public function __construct(Swift_CharacterStream $charStream)
    {
        $this->_charStream = $charStream;
    }

    /**
     * Takes an unencoded string and produces a string encoded according to
     * RFC 2231 from it.
     *
     * @param string $string
     * @param int    $firstLineOffset
     * @param int    $maxLineLength   optional, 0 indicates the default of 75 bytes
     *
     * @return string
     */
    public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
    {
        $lines = array();
        $lineCount = 0;
        $lines[] = '';
        $currentLine = &$lines[$lineCount++];

        if (0 >= $maxLineLength) {
            $maxLineLength = 75;
        }

        $this->_charStream->flushContents();
        $this->_charStream->importString($string);

        $thisLineLength = $maxLineLength - $firstLineOffset;

        while (false !== $char = $this->_charStream->read(4)) {
            $encodedChar = rawurlencode($char);
            if (0 != strlen($currentLine)
                && strlen($currentLine.$encodedChar) > $thisLineLength) {
                $lines[] = '';
                $currentLine = &$lines[$lineCount++];
                $thisLineLength = $maxLineLength;
            }
            $currentLine .= $encodedChar;
        }

        return implode("\r\n", $lines);
    }

    /**
     * Updates the charset used.
     *
     * @param string $charset
     */
    public function charsetChanged($charset)
    {
        $this->_charStream->setCharacterSet($charset);
    }

    /**
     * Make a deep copy of object.
     */
    public function __clone()
    {
        $this->_charStream = clone $this->_charStream;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Interface for all Encoder schemes.
 *
 * @author Chris Corbyn
 */
interface Swift_Encoder extends Swift_Mime_CharsetObserver
{
    /**
     * Encode a given string to produce an encoded string.
     *
     * @param string $string
     * @param int    $firstLineOffset if first line needs to be shorter
     * @param int    $maxLineLength   - 0 indicates the default length for this encoding
     *
     * @return string
     */
    public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Provides quick access to each encoding type.
 *
 * @author Chris Corbyn
 */
class Swift_Encoding
{
    /**
     * Get the Encoder that provides 7-bit encoding.
     *
     * @return Swift_Mime_ContentEncoder
     */
    public static function get7BitEncoding()
    {
        return self::_lookup('mime.7bitcontentencoder');
    }

    /**
     * Get the Encoder that provides 8-bit encoding.
     *
     * @return Swift_Mime_ContentEncoder
     */
    public static function get8BitEncoding()
    {
        return self::_lookup('mime.8bitcontentencoder');
    }

    /**
     * Get the Encoder that provides Quoted-Printable (QP) encoding.
     *
     * @return Swift_Mime_ContentEncoder
     */
    public static function getQpEncoding()
    {
        return self::_lookup('mime.qpcontentencoder');
    }

    /**
     * Get the Encoder that provides Base64 encoding.
     *
     * @return Swift_Mime_ContentEncoder
     */
    public static function getBase64Encoding()
    {
        return self::_lookup('mime.base64contentencoder');
    }

    // -- Private Static Methods

    private static function _lookup($key)
    {
        return Swift_DependencyContainer::getInstance()->lookup($key);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Generated when a command is sent over an SMTP connection.
 *
 * @author Chris Corbyn
 */
class Swift_Events_CommandEvent extends Swift_Events_EventObject
{
    /**
     * The command sent to the server.
     *
     * @var string
     */
    private $_command;

    /**
     * An array of codes which a successful response will contain.
     *
     * @var integer[]
     */
    private $_successCodes = array();

    /**
     * Create a new CommandEvent for $source with $command.
     *
     * @param Swift_Transport $source
     * @param string          $command
     * @param array           $successCodes
     */
    public function __construct(Swift_Transport $source, $command, $successCodes = array())
    {
        parent::__construct($source);
        $this->_command = $command;
        $this->_successCodes = $successCodes;
    }

    /**
     * Get the command which was sent to the server.
     *
     * @return string
     */
    public function getCommand()
    {
        return $this->_command;
    }

    /**
     * Get the numeric response codes which indicate success for this command.
     *
     * @return integer[]
     */
    public function getSuccessCodes()
    {
        return $this->_successCodes;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Listens for Transports to send commands to the server.
 *
 * @author Chris Corbyn
 */
interface Swift_Events_CommandListener extends Swift_Events_EventListener
{
    /**
     * Invoked immediately following a command being sent.
     *
     * @param Swift_Events_CommandEvent $evt
     */
    public function commandSent(Swift_Events_CommandEvent $evt);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * The minimum interface for an Event.
 *
 * @author Chris Corbyn
 */
interface Swift_Events_Event
{
    /**
     * Get the source object of this event.
     *
     * @return object
     */
    public function getSource();

    /**
     * Prevent this Event from bubbling any further up the stack.
     *
     * @param bool $cancel, optional
     */
    public function cancelBubble($cancel = true);

    /**
     * Returns true if this Event will not bubble any further up the stack.
     *
     * @return bool
     */
    public function bubbleCancelled();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Interface for the EventDispatcher which handles the event dispatching layer.
 *
 * @author Chris Corbyn
 */
interface Swift_Events_EventDispatcher
{
    /**
     * Create a new SendEvent for $source and $message.
     *
     * @param Swift_Transport $source
     * @param Swift_Mime_Message
     *
     * @return Swift_Events_SendEvent
     */
    public function createSendEvent(Swift_Transport $source, Swift_Mime_Message $message);

    /**
     * Create a new CommandEvent for $source and $command.
     *
     * @param Swift_Transport $source
     * @param string          $command      That will be executed
     * @param array           $successCodes That are needed
     *
     * @return Swift_Events_CommandEvent
     */
    public function createCommandEvent(Swift_Transport $source, $command, $successCodes = array());

    /**
     * Create a new ResponseEvent for $source and $response.
     *
     * @param Swift_Transport $source
     * @param string          $response
     * @param bool            $valid    If the response is valid
     *
     * @return Swift_Events_ResponseEvent
     */
    public function createResponseEvent(Swift_Transport $source, $response, $valid);

    /**
     * Create a new TransportChangeEvent for $source.
     *
     * @param Swift_Transport $source
     *
     * @return Swift_Events_TransportChangeEvent
     */
    public function createTransportChangeEvent(Swift_Transport $source);

    /**
     * Create a new TransportExceptionEvent for $source.
     *
     * @param Swift_Transport          $source
     * @param Swift_TransportException $ex
     *
     * @return Swift_Events_TransportExceptionEvent
     */
    public function createTransportExceptionEvent(Swift_Transport $source, Swift_TransportException $ex);

    /**
     * Bind an event listener to this dispatcher.
     *
     * @param Swift_Events_EventListener $listener
     */
    public function bindEventListener(Swift_Events_EventListener $listener);

    /**
     * Dispatch the given Event to all suitable listeners.
     *
     * @param Swift_Events_EventObject $evt
     * @param string                   $target method
     */
    public function dispatchEvent(Swift_Events_EventObject $evt, $target);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An identity interface which all EventListeners must extend.
 *
 * @author Chris Corbyn
 */
interface Swift_Events_EventListener
{
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A base Event which all Event classes inherit from.
 *
 * @author Chris Corbyn
 */
class Swift_Events_EventObject implements Swift_Events_Event
{
    /** The source of this Event */
    private $_source;

    /** The state of this Event (should it bubble up the stack?) */
    private $_bubbleCancelled = false;

    /**
     * Create a new EventObject originating at $source.
     *
     * @param object $source
     */
    public function __construct($source)
    {
        $this->_source = $source;
    }

    /**
     * Get the source object of this event.
     *
     * @return object
     */
    public function getSource()
    {
        return $this->_source;
    }

    /**
     * Prevent this Event from bubbling any further up the stack.
     *
     * @param bool $cancel, optional
     */
    public function cancelBubble($cancel = true)
    {
        $this->_bubbleCancelled = $cancel;
    }

    /**
     * Returns true if this Event will not bubble any further up the stack.
     *
     * @return bool
     */
    public function bubbleCancelled()
    {
        return $this->_bubbleCancelled;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Generated when a response is received on a SMTP connection.
 *
 * @author Chris Corbyn
 */
class Swift_Events_ResponseEvent extends Swift_Events_EventObject
{
    /**
     * The overall result.
     *
     * @var bool
     */
    private $_valid;

    /**
     * The response received from the server.
     *
     * @var string
     */
    private $_response;

    /**
     * Create a new ResponseEvent for $source and $response.
     *
     * @param Swift_Transport $source
     * @param string          $response
     * @param bool            $valid
     */
    public function __construct(Swift_Transport $source, $response, $valid = false)
    {
        parent::__construct($source);
        $this->_response = $response;
        $this->_valid = $valid;
    }

    /**
     * Get the response which was received from the server.
     *
     * @return string
     */
    public function getResponse()
    {
        return $this->_response;
    }

    /**
     * Get the success status of this Event.
     *
     * @return bool
     */
    public function isValid()
    {
        return $this->_valid;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Listens for responses from a remote SMTP server.
 *
 * @author Chris Corbyn
 */
interface Swift_Events_ResponseListener extends Swift_Events_EventListener
{
    /**
     * Invoked immediately following a response coming back.
     *
     * @param Swift_Events_ResponseEvent $evt
     */
    public function responseReceived(Swift_Events_ResponseEvent $evt);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Generated when a message is being sent.
 *
 * @author Chris Corbyn
 */
class Swift_Events_SendEvent extends Swift_Events_EventObject
{
    /** Sending has yet to occur */
    const RESULT_PENDING = 0x0001;

    /** Email is spooled, ready to be sent */
    const RESULT_SPOOLED = 0x0011;

    /** Sending was successful */
    const RESULT_SUCCESS = 0x0010;

    /** Sending worked, but there were some failures */
    const RESULT_TENTATIVE = 0x0100;

    /** Sending failed */
    const RESULT_FAILED = 0x1000;

    /**
     * The Message being sent.
     *
     * @var Swift_Mime_Message
     */
    private $_message;

    /**
     * Any recipients which failed after sending.
     *
     * @var string[]
     */
    private $_failedRecipients = array();

    /**
     * The overall result as a bitmask from the class constants.
     *
     * @var int
     */
    private $_result;

    /**
     * Create a new SendEvent for $source and $message.
     *
     * @param Swift_Transport    $source
     * @param Swift_Mime_Message $message
     */
    public function __construct(Swift_Transport $source, Swift_Mime_Message $message)
    {
        parent::__construct($source);
        $this->_message = $message;
        $this->_result = self::RESULT_PENDING;
    }

    /**
     * Get the Transport used to send the Message.
     *
     * @return Swift_Transport
     */
    public function getTransport()
    {
        return $this->getSource();
    }

    /**
     * Get the Message being sent.
     *
     * @return Swift_Mime_Message
     */
    public function getMessage()
    {
        return $this->_message;
    }

    /**
     * Set the array of addresses that failed in sending.
     *
     * @param array $recipients
     */
    public function setFailedRecipients($recipients)
    {
        $this->_failedRecipients = $recipients;
    }

    /**
     * Get an recipient addresses which were not accepted for delivery.
     *
     * @return string[]
     */
    public function getFailedRecipients()
    {
        return $this->_failedRecipients;
    }

    /**
     * Set the result of sending.
     *
     * @param int $result
     */
    public function setResult($result)
    {
        $this->_result = $result;
    }

    /**
     * Get the result of this Event.
     *
     * The return value is a bitmask from
     * {@see RESULT_PENDING, RESULT_SUCCESS, RESULT_TENTATIVE, RESULT_FAILED}
     *
     * @return int
     */
    public function getResult()
    {
        return $this->_result;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Listens for Messages being sent from within the Transport system.
 *
 * @author Chris Corbyn
 */
interface Swift_Events_SendListener extends Swift_Events_EventListener
{
    /**
     * Invoked immediately before the Message is sent.
     *
     * @param Swift_Events_SendEvent $evt
     */
    public function beforeSendPerformed(Swift_Events_SendEvent $evt);

    /**
     * Invoked immediately after the Message is sent.
     *
     * @param Swift_Events_SendEvent $evt
     */
    public function sendPerformed(Swift_Events_SendEvent $evt);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * The EventDispatcher which handles the event dispatching layer.
 *
 * @author Chris Corbyn
 */
class Swift_Events_SimpleEventDispatcher implements Swift_Events_EventDispatcher
{
    /** A map of event types to their associated listener types */
    private $_eventMap = array();

    /** Event listeners bound to this dispatcher */
    private $_listeners = array();

    /** Listeners queued to have an Event bubbled up the stack to them */
    private $_bubbleQueue = array();

    /**
     * Create a new EventDispatcher.
     */
    public function __construct()
    {
        $this->_eventMap = array(
            'Swift_Events_CommandEvent' => 'Swift_Events_CommandListener',
            'Swift_Events_ResponseEvent' => 'Swift_Events_ResponseListener',
            'Swift_Events_SendEvent' => 'Swift_Events_SendListener',
            'Swift_Events_TransportChangeEvent' => 'Swift_Events_TransportChangeListener',
            'Swift_Events_TransportExceptionEvent' => 'Swift_Events_TransportExceptionListener',
            );
    }

    /**
     * Create a new SendEvent for $source and $message.
     *
     * @param Swift_Transport $source
     * @param Swift_Mime_Message
     *
     * @return Swift_Events_SendEvent
     */
    public function createSendEvent(Swift_Transport $source, Swift_Mime_Message $message)
    {
        return new Swift_Events_SendEvent($source, $message);
    }

    /**
     * Create a new CommandEvent for $source and $command.
     *
     * @param Swift_Transport $source
     * @param string          $command      That will be executed
     * @param array           $successCodes That are needed
     *
     * @return Swift_Events_CommandEvent
     */
    public function createCommandEvent(Swift_Transport $source, $command, $successCodes = array())
    {
        return new Swift_Events_CommandEvent($source, $command, $successCodes);
    }

    /**
     * Create a new ResponseEvent for $source and $response.
     *
     * @param Swift_Transport $source
     * @param string          $response
     * @param bool            $valid    If the response is valid
     *
     * @return Swift_Events_ResponseEvent
     */
    public function createResponseEvent(Swift_Transport $source, $response, $valid)
    {
        return new Swift_Events_ResponseEvent($source, $response, $valid);
    }

    /**
     * Create a new TransportChangeEvent for $source.
     *
     * @param Swift_Transport $source
     *
     * @return Swift_Events_TransportChangeEvent
     */
    public function createTransportChangeEvent(Swift_Transport $source)
    {
        return new Swift_Events_TransportChangeEvent($source);
    }

    /**
     * Create a new TransportExceptionEvent for $source.
     *
     * @param Swift_Transport          $source
     * @param Swift_TransportException $ex
     *
     * @return Swift_Events_TransportExceptionEvent
     */
    public function createTransportExceptionEvent(Swift_Transport $source, Swift_TransportException $ex)
    {
        return new Swift_Events_TransportExceptionEvent($source, $ex);
    }

    /**
     * Bind an event listener to this dispatcher.
     *
     * @param Swift_Events_EventListener $listener
     */
    public function bindEventListener(Swift_Events_EventListener $listener)
    {
        foreach ($this->_listeners as $l) {
            // Already loaded
            if ($l === $listener) {
                return;
            }
        }
        $this->_listeners[] = $listener;
    }

    /**
     * Dispatch the given Event to all suitable listeners.
     *
     * @param Swift_Events_EventObject $evt
     * @param string                   $target method
     */
    public function dispatchEvent(Swift_Events_EventObject $evt, $target)
    {
        $this->_prepareBubbleQueue($evt);
        $this->_bubble($evt, $target);
    }

    /** Queue listeners on a stack ready for $evt to be bubbled up it */
    private function _prepareBubbleQueue(Swift_Events_EventObject $evt)
    {
        $this->_bubbleQueue = array();
        $evtClass = get_class($evt);
        foreach ($this->_listeners as $listener) {
            if (array_key_exists($evtClass, $this->_eventMap)
                && ($listener instanceof $this->_eventMap[$evtClass])) {
                $this->_bubbleQueue[] = $listener;
            }
        }
    }

    /** Bubble $evt up the stack calling $target() on each listener */
    private function _bubble(Swift_Events_EventObject $evt, $target)
    {
        if (!$evt->bubbleCancelled() && $listener = array_shift($this->_bubbleQueue)) {
            $listener->$target($evt);
            $this->_bubble($evt, $target);
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Generated when the state of a Transport is changed (i.e. stopped/started).
 *
 * @author Chris Corbyn
 */
class Swift_Events_TransportChangeEvent extends Swift_Events_EventObject
{
    /**
     * Get the Transport.
     *
     * @return Swift_Transport
     */
    public function getTransport()
    {
        return $this->getSource();
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Listens for changes within the Transport system.
 *
 * @author Chris Corbyn
 */
interface Swift_Events_TransportChangeListener extends Swift_Events_EventListener
{
    /**
     * Invoked just before a Transport is started.
     *
     * @param Swift_Events_TransportChangeEvent $evt
     */
    public function beforeTransportStarted(Swift_Events_TransportChangeEvent $evt);

    /**
     * Invoked immediately after the Transport is started.
     *
     * @param Swift_Events_TransportChangeEvent $evt
     */
    public function transportStarted(Swift_Events_TransportChangeEvent $evt);

    /**
     * Invoked just before a Transport is stopped.
     *
     * @param Swift_Events_TransportChangeEvent $evt
     */
    public function beforeTransportStopped(Swift_Events_TransportChangeEvent $evt);

    /**
     * Invoked immediately after the Transport is stopped.
     *
     * @param Swift_Events_TransportChangeEvent $evt
     */
    public function transportStopped(Swift_Events_TransportChangeEvent $evt);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Generated when a TransportException is thrown from the Transport system.
 *
 * @author Chris Corbyn
 */
class Swift_Events_TransportExceptionEvent extends Swift_Events_EventObject
{
    /**
     * The Exception thrown.
     *
     * @var Swift_TransportException
     */
    private $_exception;

    /**
     * Create a new TransportExceptionEvent for $transport.
     *
     * @param Swift_Transport          $transport
     * @param Swift_TransportException $ex
     */
    public function __construct(Swift_Transport $transport, Swift_TransportException $ex)
    {
        parent::__construct($transport);
        $this->_exception = $ex;
    }

    /**
     * Get the TransportException thrown.
     *
     * @return Swift_TransportException
     */
    public function getException()
    {
        return $this->_exception;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Listens for Exceptions thrown from within the Transport system.
 *
 * @author Chris Corbyn
 */
interface Swift_Events_TransportExceptionListener extends Swift_Events_EventListener
{
    /**
     * Invoked as a TransportException is thrown in the Transport system.
     *
     * @param Swift_Events_TransportExceptionEvent $evt
     */
    public function exceptionThrown(Swift_Events_TransportExceptionEvent $evt);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Contains a list of redundant Transports so when one fails, the next is used.
 *
 * @author Chris Corbyn
 */
class Swift_FailoverTransport extends Swift_Transport_FailoverTransport
{
    /**
     * Creates a new FailoverTransport with $transports.
     *
     * @param Swift_Transport[] $transports
     */
    public function __construct($transports = array())
    {
        call_user_func_array(
            array($this, 'Swift_Transport_FailoverTransport::__construct'),
            Swift_DependencyContainer::getInstance()
                ->createDependenciesFor('transport.failover')
            );

        $this->setTransports($transports);
    }

    /**
     * Create a new FailoverTransport instance.
     *
     * @param Swift_Transport[] $transports
     *
     * @return Swift_FailoverTransport
     */
    public static function newInstance($transports = array())
    {
        return new self($transports);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Stores Messages on the filesystem.
 *
 * @author Fabien Potencier
 * @author Xavier De Cock <xdecock@gmail.com>
 */
class Swift_FileSpool extends Swift_ConfigurableSpool
{
    /** The spool directory */
    private $_path;

    /**
     * File WriteRetry Limit.
     *
     * @var int
     */
    private $_retryLimit = 10;

    /**
     * Create a new FileSpool.
     *
     * @param string $path
     *
     * @throws Swift_IoException
     */
    public function __construct($path)
    {
        $this->_path = $path;

        if (!file_exists($this->_path)) {
            if (!mkdir($this->_path, 0777, true)) {
                throw new Swift_IoException(sprintf('Unable to create path "%s".', $this->_path));
            }
        }
    }

    /**
     * Tests if this Spool mechanism has started.
     *
     * @return bool
     */
    public function isStarted()
    {
        return true;
    }

    /**
     * Starts this Spool mechanism.
     */
    public function start()
    {
    }

    /**
     * Stops this Spool mechanism.
     */
    public function stop()
    {
    }

    /**
     * Allow to manage the enqueuing retry limit.
     *
     * Default, is ten and allows over 64^20 different fileNames
     *
     * @param int $limit
     */
    public function setRetryLimit($limit)
    {
        $this->_retryLimit = $limit;
    }

    /**
     * Queues a message.
     *
     * @param Swift_Mime_Message $message The message to store
     *
     * @throws Swift_IoException
     *
     * @return bool
     */
    public function queueMessage(Swift_Mime_Message $message)
    {
        $ser = serialize($message);
        $fileName = $this->_path.'/'.$this->getRandomString(10);
        for ($i = 0; $i < $this->_retryLimit; ++$i) {
            /* We try an exclusive creation of the file. This is an atomic operation, it avoid locking mechanism */
            $fp = @fopen($fileName.'.message', 'x');
            if (false !== $fp) {
                if (false === fwrite($fp, $ser)) {
                    return false;
                }

                return fclose($fp);
            } else {
                /* The file already exists, we try a longer fileName */
                $fileName .= $this->getRandomString(1);
            }
        }

        throw new Swift_IoException(sprintf('Unable to create a file for enqueuing Message in "%s".', $this->_path));
    }

    /**
     * Execute a recovery if for any reason a process is sending for too long.
     *
     * @param int $timeout in second Defaults is for very slow smtp responses
     */
    public function recover($timeout = 900)
    {
        foreach (new DirectoryIterator($this->_path) as $file) {
            $file = $file->getRealPath();

            if (substr($file, -16) == '.message.sending') {
                $lockedtime = filectime($file);
                if ((time() - $lockedtime) > $timeout) {
                    rename($file, substr($file, 0, -8));
                }
            }
        }
    }

    /**
     * Sends messages using the given transport instance.
     *
     * @param Swift_Transport $transport        A transport instance
     * @param string[]        $failedRecipients An array of failures by-reference
     *
     * @return int The number of sent e-mail's
     */
    public function flushQueue(Swift_Transport $transport, &$failedRecipients = null)
    {
        $directoryIterator = new DirectoryIterator($this->_path);

        /* Start the transport only if there are queued files to send */
        if (!$transport->isStarted()) {
            foreach ($directoryIterator as $file) {
                if (substr($file->getRealPath(), -8) == '.message') {
                    $transport->start();
                    break;
                }
            }
        }

        $failedRecipients = (array) $failedRecipients;
        $count = 0;
        $time = time();
        foreach ($directoryIterator as $file) {
            $file = $file->getRealPath();

            if (substr($file, -8) != '.message') {
                continue;
            }

            /* We try a rename, it's an atomic operation, and avoid locking the file */
            if (rename($file, $file.'.sending')) {
                $message = unserialize(file_get_contents($file.'.sending'));

                $count += $transport->send($message, $failedRecipients);

                unlink($file.'.sending');
            } else {
                /* This message has just been catched by another process */
                continue;
            }

            if ($this->getMessageLimit() && $count >= $this->getMessageLimit()) {
                break;
            }

            if ($this->getTimeLimit() && (time() - $time) >= $this->getTimeLimit()) {
                break;
            }
        }

        return $count;
    }

    /**
     * Returns a random string needed to generate a fileName for the queue.
     *
     * @param int $count
     *
     * @return string
     */
    protected function getRandomString($count)
    {
        // This string MUST stay FS safe, avoid special chars
        $base = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-';
        $ret = '';
        $strlen = strlen($base);
        for ($i = 0; $i < $count; ++$i) {
            $ret .= $base[((int) rand(0, $strlen - 1))];
        }

        return $ret;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An OutputByteStream which specifically reads from a file.
 *
 * @author Chris Corbyn
 */
interface Swift_FileStream extends Swift_OutputByteStream
{
    /**
     * Get the complete path to the file.
     *
     * @return string
     */
    public function getPath();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Allows StreamFilters to operate on a stream.
 *
 * @author Chris Corbyn
 */
interface Swift_Filterable
{
    /**
     * Add a new StreamFilter, referenced by $key.
     *
     * @param Swift_StreamFilter $filter
     * @param string             $key
     */
    public function addFilter(Swift_StreamFilter $filter, $key);

    /**
     * Remove an existing filter using $key.
     *
     * @param string $key
     */
    public function removeFilter($key);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An image, embedded in a multipart message.
 *
 * @author Chris Corbyn
 */
class Swift_Image extends Swift_EmbeddedFile
{
    /**
     * Create a new EmbeddedFile.
     *
     * Details may be optionally provided to the constructor.
     *
     * @param string|Swift_OutputByteStream $data
     * @param string                        $filename
     * @param string                        $contentType
     */
    public function __construct($data = null, $filename = null, $contentType = null)
    {
        parent::__construct($data, $filename, $contentType);
    }

    /**
     * Create a new Image.
     *
     * @param string|Swift_OutputByteStream $data
     * @param string                        $filename
     * @param string                        $contentType
     *
     * @return Swift_Image
     */
    public static function newInstance($data = null, $filename = null, $contentType = null)
    {
        return new self($data, $filename, $contentType);
    }

    /**
     * Create a new Image from a filesystem path.
     *
     * @param string $path
     *
     * @return Swift_Image
     */
    public static function fromPath($path)
    {
        $image = self::newInstance()->setFile(
            new Swift_ByteStream_FileByteStream($path)
            );

        return $image;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An abstract means of writing data.
 *
 * Classes implementing this interface may use a subsystem which requires less
 * memory than working with large strings of data.
 *
 * @author Chris Corbyn
 */
interface Swift_InputByteStream
{
    /**
     * Writes $bytes to the end of the stream.
     *
     * Writing may not happen immediately if the stream chooses to buffer.  If
     * you want to write these bytes with immediate effect, call {@link commit()}
     * after calling write().
     *
     * This method returns the sequence ID of the write (i.e. 1 for first, 2 for
     * second, etc etc).
     *
     * @param string $bytes
     *
     * @throws Swift_IoException
     *
     * @return int
     */
    public function write($bytes);

    /**
     * For any bytes that are currently buffered inside the stream, force them
     * off the buffer.
     *
     * @throws Swift_IoException
     */
    public function commit();

    /**
     * Attach $is to this stream.
     *
     * The stream acts as an observer, receiving all data that is written.
     * All {@link write()} and {@link flushBuffers()} operations will be mirrored.
     *
     * @param Swift_InputByteStream $is
     */
    public function bind(Swift_InputByteStream $is);

    /**
     * Remove an already bound stream.
     *
     * If $is is not bound, no errors will be raised.
     * If the stream currently has any buffered data it will be written to $is
     * before unbinding occurs.
     *
     * @param Swift_InputByteStream $is
     */
    public function unbind(Swift_InputByteStream $is);

    /**
     * Flush the contents of the stream (empty it) and set the internal pointer
     * to the beginning.
     *
     * @throws Swift_IoException
     */
    public function flushBuffers();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * I/O Exception class.
 *
 * @author Chris Corbyn
 */
class Swift_IoException extends Swift_SwiftException
{
    /**
     * Create a new IoException with $message.
     *
     * @param string    $message
     * @param int       $code
     * @param Exception $previous
     */
    public function __construct($message, $code = 0, Exception $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A basic KeyCache backed by an array.
 *
 * @author Chris Corbyn
 */
class Swift_KeyCache_ArrayKeyCache implements Swift_KeyCache
{
    /**
     * Cache contents.
     *
     * @var array
     */
    private $_contents = array();

    /**
     * An InputStream for cloning.
     *
     * @var Swift_KeyCache_KeyCacheInputStream
     */
    private $_stream;

    /**
     * Create a new ArrayKeyCache with the given $stream for cloning to make
     * InputByteStreams.
     *
     * @param Swift_KeyCache_KeyCacheInputStream $stream
     */
    public function __construct(Swift_KeyCache_KeyCacheInputStream $stream)
    {
        $this->_stream = $stream;
    }

    /**
     * Set a string into the cache under $itemKey for the namespace $nsKey.
     *
     * @see MODE_WRITE, MODE_APPEND
     *
     * @param string $nsKey
     * @param string $itemKey
     * @param string $string
     * @param int    $mode
     */
    public function setString($nsKey, $itemKey, $string, $mode)
    {
        $this->_prepareCache($nsKey);
        switch ($mode) {
            case self::MODE_WRITE:
                $this->_contents[$nsKey][$itemKey] = $string;
                break;
            case self::MODE_APPEND:
                if (!$this->hasKey($nsKey, $itemKey)) {
                    $this->_contents[$nsKey][$itemKey] = '';
                }
                $this->_contents[$nsKey][$itemKey] .= $string;
                break;
            default:
                throw new Swift_SwiftException(
                    'Invalid mode ['.$mode.'] used to set nsKey='.
                    $nsKey.', itemKey='.$itemKey
                    );
        }
    }

    /**
     * Set a ByteStream into the cache under $itemKey for the namespace $nsKey.
     *
     * @see MODE_WRITE, MODE_APPEND
     *
     * @param string                 $nsKey
     * @param string                 $itemKey
     * @param Swift_OutputByteStream $os
     * @param int                    $mode
     */
    public function importFromByteStream($nsKey, $itemKey, Swift_OutputByteStream $os, $mode)
    {
        $this->_prepareCache($nsKey);
        switch ($mode) {
            case self::MODE_WRITE:
                $this->clearKey($nsKey, $itemKey);
            case self::MODE_APPEND:
                if (!$this->hasKey($nsKey, $itemKey)) {
                    $this->_contents[$nsKey][$itemKey] = '';
                }
                while (false !== $bytes = $os->read(8192)) {
                    $this->_contents[$nsKey][$itemKey] .= $bytes;
                }
                break;
            default:
                throw new Swift_SwiftException(
                    'Invalid mode ['.$mode.'] used to set nsKey='.
                    $nsKey.', itemKey='.$itemKey
                    );
        }
    }

    /**
     * Provides a ByteStream which when written to, writes data to $itemKey.
     *
     * NOTE: The stream will always write in append mode.
     *
     * @param string                $nsKey
     * @param string                $itemKey
     * @param Swift_InputByteStream $writeThrough
     *
     * @return Swift_InputByteStream
     */
    public function getInputByteStream($nsKey, $itemKey, Swift_InputByteStream $writeThrough = null)
    {
        $is = clone $this->_stream;
        $is->setKeyCache($this);
        $is->setNsKey($nsKey);
        $is->setItemKey($itemKey);
        if (isset($writeThrough)) {
            $is->setWriteThroughStream($writeThrough);
        }

        return $is;
    }

    /**
     * Get data back out of the cache as a string.
     *
     * @param string $nsKey
     * @param string $itemKey
     *
     * @return string
     */
    public function getString($nsKey, $itemKey)
    {
        $this->_prepareCache($nsKey);
        if ($this->hasKey($nsKey, $itemKey)) {
            return $this->_contents[$nsKey][$itemKey];
        }
    }

    /**
     * Get data back out of the cache as a ByteStream.
     *
     * @param string                $nsKey
     * @param string                $itemKey
     * @param Swift_InputByteStream $is      to write the data to
     */
    public function exportToByteStream($nsKey, $itemKey, Swift_InputByteStream $is)
    {
        $this->_prepareCache($nsKey);
        $is->write($this->getString($nsKey, $itemKey));
    }

    /**
     * Check if the given $itemKey exists in the namespace $nsKey.
     *
     * @param string $nsKey
     * @param string $itemKey
     *
     * @return bool
     */
    public function hasKey($nsKey, $itemKey)
    {
        $this->_prepareCache($nsKey);

        return array_key_exists($itemKey, $this->_contents[$nsKey]);
    }

    /**
     * Clear data for $itemKey in the namespace $nsKey if it exists.
     *
     * @param string $nsKey
     * @param string $itemKey
     */
    public function clearKey($nsKey, $itemKey)
    {
        unset($this->_contents[$nsKey][$itemKey]);
    }

    /**
     * Clear all data in the namespace $nsKey if it exists.
     *
     * @param string $nsKey
     */
    public function clearAll($nsKey)
    {
        unset($this->_contents[$nsKey]);
    }

    /**
     * Initialize the namespace of $nsKey if needed.
     *
     * @param string $nsKey
     */
    private function _prepareCache($nsKey)
    {
        if (!array_key_exists($nsKey, $this->_contents)) {
            $this->_contents[$nsKey] = array();
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A KeyCache which streams to and from disk.
 *
 * @author Chris Corbyn
 */
class Swift_KeyCache_DiskKeyCache implements Swift_KeyCache
{
    /** Signal to place pointer at start of file */
    const POSITION_START = 0;

    /** Signal to place pointer at end of file */
    const POSITION_END = 1;

    /** Signal to leave pointer in whatever position it currently is */
    const POSITION_CURRENT = 2;

    /**
     * An InputStream for cloning.
     *
     * @var Swift_KeyCache_KeyCacheInputStream
     */
    private $_stream;

    /**
     * A path to write to.
     *
     * @var string
     */
    private $_path;

    /**
     * Stored keys.
     *
     * @var array
     */
    private $_keys = array();

    /**
     * Will be true if magic_quotes_runtime is turned on.
     *
     * @var bool
     */
    private $_quotes = false;

    /**
     * Create a new DiskKeyCache with the given $stream for cloning to make
     * InputByteStreams, and the given $path to save to.
     *
     * @param Swift_KeyCache_KeyCacheInputStream $stream
     * @param string                             $path   to save to
     */
    public function __construct(Swift_KeyCache_KeyCacheInputStream $stream, $path)
    {
        $this->_stream = $stream;
        $this->_path = $path;

        if (function_exists('get_magic_quotes_runtime') && @get_magic_quotes_runtime() == 1) {
            $this->_quotes = true;
        }
    }

    /**
     * Set a string into the cache under $itemKey for the namespace $nsKey.
     *
     * @see MODE_WRITE, MODE_APPEND
     *
     * @param string $nsKey
     * @param string $itemKey
     * @param string $string
     * @param int    $mode
     *
     * @throws Swift_IoException
     */
    public function setString($nsKey, $itemKey, $string, $mode)
    {
        $this->_prepareCache($nsKey);
        switch ($mode) {
            case self::MODE_WRITE:
                $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START);
                break;
            case self::MODE_APPEND:
                $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_END);
                break;
            default:
                throw new Swift_SwiftException(
                    'Invalid mode ['.$mode.'] used to set nsKey='.
                    $nsKey.', itemKey='.$itemKey
                    );
                break;
        }
        fwrite($fp, $string);
        $this->_freeHandle($nsKey, $itemKey);
    }

    /**
     * Set a ByteStream into the cache under $itemKey for the namespace $nsKey.
     *
     * @see MODE_WRITE, MODE_APPEND
     *
     * @param string                 $nsKey
     * @param string                 $itemKey
     * @param Swift_OutputByteStream $os
     * @param int                    $mode
     *
     * @throws Swift_IoException
     */
    public function importFromByteStream($nsKey, $itemKey, Swift_OutputByteStream $os, $mode)
    {
        $this->_prepareCache($nsKey);
        switch ($mode) {
            case self::MODE_WRITE:
                $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START);
                break;
            case self::MODE_APPEND:
                $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_END);
                break;
            default:
                throw new Swift_SwiftException(
                    'Invalid mode ['.$mode.'] used to set nsKey='.
                    $nsKey.', itemKey='.$itemKey
                    );
                break;
        }
        while (false !== $bytes = $os->read(8192)) {
            fwrite($fp, $bytes);
        }
        $this->_freeHandle($nsKey, $itemKey);
    }

    /**
     * Provides a ByteStream which when written to, writes data to $itemKey.
     *
     * NOTE: The stream will always write in append mode.
     *
     * @param string                $nsKey
     * @param string                $itemKey
     * @param Swift_InputByteStream $writeThrough
     *
     * @return Swift_InputByteStream
     */
    public function getInputByteStream($nsKey, $itemKey, Swift_InputByteStream $writeThrough = null)
    {
        $is = clone $this->_stream;
        $is->setKeyCache($this);
        $is->setNsKey($nsKey);
        $is->setItemKey($itemKey);
        if (isset($writeThrough)) {
            $is->setWriteThroughStream($writeThrough);
        }

        return $is;
    }

    /**
     * Get data back out of the cache as a string.
     *
     * @param string $nsKey
     * @param string $itemKey
     *
     * @throws Swift_IoException
     *
     * @return string
     */
    public function getString($nsKey, $itemKey)
    {
        $this->_prepareCache($nsKey);
        if ($this->hasKey($nsKey, $itemKey)) {
            $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START);
            if ($this->_quotes) {
                ini_set('magic_quotes_runtime', 0);
            }
            $str = '';
            while (!feof($fp) && false !== $bytes = fread($fp, 8192)) {
                $str .= $bytes;
            }
            if ($this->_quotes) {
                ini_set('magic_quotes_runtime', 1);
            }
            $this->_freeHandle($nsKey, $itemKey);

            return $str;
        }
    }

    /**
     * Get data back out of the cache as a ByteStream.
     *
     * @param string                $nsKey
     * @param string                $itemKey
     * @param Swift_InputByteStream $is      to write the data to
     */
    public function exportToByteStream($nsKey, $itemKey, Swift_InputByteStream $is)
    {
        if ($this->hasKey($nsKey, $itemKey)) {
            $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START);
            if ($this->_quotes) {
                ini_set('magic_quotes_runtime', 0);
            }
            while (!feof($fp) && false !== $bytes = fread($fp, 8192)) {
                $is->write($bytes);
            }
            if ($this->_quotes) {
                ini_set('magic_quotes_runtime', 1);
            }
            $this->_freeHandle($nsKey, $itemKey);
        }
    }

    /**
     * Check if the given $itemKey exists in the namespace $nsKey.
     *
     * @param string $nsKey
     * @param string $itemKey
     *
     * @return bool
     */
    public function hasKey($nsKey, $itemKey)
    {
        return is_file($this->_path.'/'.$nsKey.'/'.$itemKey);
    }

    /**
     * Clear data for $itemKey in the namespace $nsKey if it exists.
     *
     * @param string $nsKey
     * @param string $itemKey
     */
    public function clearKey($nsKey, $itemKey)
    {
        if ($this->hasKey($nsKey, $itemKey)) {
            $this->_freeHandle($nsKey, $itemKey);
            unlink($this->_path.'/'.$nsKey.'/'.$itemKey);
        }
    }

    /**
     * Clear all data in the namespace $nsKey if it exists.
     *
     * @param string $nsKey
     */
    public function clearAll($nsKey)
    {
        if (array_key_exists($nsKey, $this->_keys)) {
            foreach ($this->_keys[$nsKey] as $itemKey => $null) {
                $this->clearKey($nsKey, $itemKey);
            }
            if (is_dir($this->_path.'/'.$nsKey)) {
                rmdir($this->_path.'/'.$nsKey);
            }
            unset($this->_keys[$nsKey]);
        }
    }

    /**
     * Initialize the namespace of $nsKey if needed.
     *
     * @param string $nsKey
     */
    private function _prepareCache($nsKey)
    {
        $cacheDir = $this->_path.'/'.$nsKey;
        if (!is_dir($cacheDir)) {
            if (!mkdir($cacheDir)) {
                throw new Swift_IoException('Failed to create cache directory '.$cacheDir);
            }
            $this->_keys[$nsKey] = array();
        }
    }

    /**
     * Get a file handle on the cache item.
     *
     * @param string $nsKey
     * @param string $itemKey
     * @param int    $position
     *
     * @return resource
     */
    private function _getHandle($nsKey, $itemKey, $position)
    {
        if (!isset($this->_keys[$nsKey][$itemKey])) {
            $openMode = $this->hasKey($nsKey, $itemKey) ? 'r+b' : 'w+b';
            $fp = fopen($this->_path.'/'.$nsKey.'/'.$itemKey, $openMode);
            $this->_keys[$nsKey][$itemKey] = $fp;
        }
        if (self::POSITION_START == $position) {
            fseek($this->_keys[$nsKey][$itemKey], 0, SEEK_SET);
        } elseif (self::POSITION_END == $position) {
            fseek($this->_keys[$nsKey][$itemKey], 0, SEEK_END);
        }

        return $this->_keys[$nsKey][$itemKey];
    }

    private function _freeHandle($nsKey, $itemKey)
    {
        $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_CURRENT);
        fclose($fp);
        $this->_keys[$nsKey][$itemKey] = null;
    }

    /**
     * Destructor.
     */
    public function __destruct()
    {
        foreach ($this->_keys as $nsKey => $null) {
            $this->clearAll($nsKey);
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Writes data to a KeyCache using a stream.
 *
 * @author Chris Corbyn
 */
interface Swift_KeyCache_KeyCacheInputStream extends Swift_InputByteStream
{
    /**
     * Set the KeyCache to wrap.
     *
     * @param Swift_KeyCache $keyCache
     */
    public function setKeyCache(Swift_KeyCache $keyCache);

    /**
     * Set the nsKey which will be written to.
     *
     * @param string $nsKey
     */
    public function setNsKey($nsKey);

    /**
     * Set the itemKey which will be written to.
     *
     * @param string $itemKey
     */
    public function setItemKey($itemKey);

    /**
     * Specify a stream to write through for each write().
     *
     * @param Swift_InputByteStream $is
     */
    public function setWriteThroughStream(Swift_InputByteStream $is);

    /**
     * Any implementation should be cloneable, allowing the clone to access a
     * separate $nsKey and $itemKey.
     */
    public function __clone();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A null KeyCache that does not cache at all.
 *
 * @author Chris Corbyn
 */
class Swift_KeyCache_NullKeyCache implements Swift_KeyCache
{
    /**
     * Set a string into the cache under $itemKey for the namespace $nsKey.
     *
     * @see MODE_WRITE, MODE_APPEND
     *
     * @param string $nsKey
     * @param string $itemKey
     * @param string $string
     * @param int    $mode
     */
    public function setString($nsKey, $itemKey, $string, $mode)
    {
    }

    /**
     * Set a ByteStream into the cache under $itemKey for the namespace $nsKey.
     *
     * @see MODE_WRITE, MODE_APPEND
     *
     * @param string                 $nsKey
     * @param string                 $itemKey
     * @param Swift_OutputByteStream $os
     * @param int                    $mode
     */
    public function importFromByteStream($nsKey, $itemKey, Swift_OutputByteStream $os, $mode)
    {
    }

    /**
     * Provides a ByteStream which when written to, writes data to $itemKey.
     *
     * NOTE: The stream will always write in append mode.
     *
     * @param string                $nsKey
     * @param string                $itemKey
     * @param Swift_InputByteStream $writeThrough
     *
     * @return Swift_InputByteStream
     */
    public function getInputByteStream($nsKey, $itemKey, Swift_InputByteStream $writeThrough = null)
    {
    }

    /**
     * Get data back out of the cache as a string.
     *
     * @param string $nsKey
     * @param string $itemKey
     *
     * @return string
     */
    public function getString($nsKey, $itemKey)
    {
    }

    /**
     * Get data back out of the cache as a ByteStream.
     *
     * @param string                $nsKey
     * @param string                $itemKey
     * @param Swift_InputByteStream $is      to write the data to
     */
    public function exportToByteStream($nsKey, $itemKey, Swift_InputByteStream $is)
    {
    }

    /**
     * Check if the given $itemKey exists in the namespace $nsKey.
     *
     * @param string $nsKey
     * @param string $itemKey
     *
     * @return bool
     */
    public function hasKey($nsKey, $itemKey)
    {
        return false;
    }

    /**
     * Clear data for $itemKey in the namespace $nsKey if it exists.
     *
     * @param string $nsKey
     * @param string $itemKey
     */
    public function clearKey($nsKey, $itemKey)
    {
    }

    /**
     * Clear all data in the namespace $nsKey if it exists.
     *
     * @param string $nsKey
     */
    public function clearAll($nsKey)
    {
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Writes data to a KeyCache using a stream.
 *
 * @author Chris Corbyn
 */
class Swift_KeyCache_SimpleKeyCacheInputStream implements Swift_KeyCache_KeyCacheInputStream
{
    /** The KeyCache being written to */
    private $_keyCache;

    /** The nsKey of the KeyCache being written to */
    private $_nsKey;

    /** The itemKey of the KeyCache being written to */
    private $_itemKey;

    /** A stream to write through on each write() */
    private $_writeThrough = null;

    /**
     * Set the KeyCache to wrap.
     *
     * @param Swift_KeyCache $keyCache
     */
    public function setKeyCache(Swift_KeyCache $keyCache)
    {
        $this->_keyCache = $keyCache;
    }

    /**
     * Specify a stream to write through for each write().
     *
     * @param Swift_InputByteStream $is
     */
    public function setWriteThroughStream(Swift_InputByteStream $is)
    {
        $this->_writeThrough = $is;
    }

    /**
     * Writes $bytes to the end of the stream.
     *
     * @param string                $bytes
     * @param Swift_InputByteStream $is    optional
     */
    public function write($bytes, Swift_InputByteStream $is = null)
    {
        $this->_keyCache->setString(
            $this->_nsKey, $this->_itemKey, $bytes, Swift_KeyCache::MODE_APPEND
            );
        if (isset($is)) {
            $is->write($bytes);
        }
        if (isset($this->_writeThrough)) {
            $this->_writeThrough->write($bytes);
        }
    }

    /**
     * Not used.
     */
    public function commit()
    {
    }

    /**
     * Not used.
     */
    public function bind(Swift_InputByteStream $is)
    {
    }

    /**
     * Not used.
     */
    public function unbind(Swift_InputByteStream $is)
    {
    }

    /**
     * Flush the contents of the stream (empty it) and set the internal pointer
     * to the beginning.
     */
    public function flushBuffers()
    {
        $this->_keyCache->clearKey($this->_nsKey, $this->_itemKey);
    }

    /**
     * Set the nsKey which will be written to.
     *
     * @param string $nsKey
     */
    public function setNsKey($nsKey)
    {
        $this->_nsKey = $nsKey;
    }

    /**
     * Set the itemKey which will be written to.
     *
     * @param string $itemKey
     */
    public function setItemKey($itemKey)
    {
        $this->_itemKey = $itemKey;
    }

    /**
     * Any implementation should be cloneable, allowing the clone to access a
     * separate $nsKey and $itemKey.
     */
    public function __clone()
    {
        $this->_writeThrough = null;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Provides a mechanism for storing data using two keys.
 *
 * @author Chris Corbyn
 */
interface Swift_KeyCache
{
    /** Mode for replacing existing cached data */
    const MODE_WRITE = 1;

    /** Mode for appending data to the end of existing cached data */
    const MODE_APPEND = 2;

    /**
     * Set a string into the cache under $itemKey for the namespace $nsKey.
     *
     * @see MODE_WRITE, MODE_APPEND
     *
     * @param string $nsKey
     * @param string $itemKey
     * @param string $string
     * @param int    $mode
     */
    public function setString($nsKey, $itemKey, $string, $mode);

    /**
     * Set a ByteStream into the cache under $itemKey for the namespace $nsKey.
     *
     * @see MODE_WRITE, MODE_APPEND
     *
     * @param string                 $nsKey
     * @param string                 $itemKey
     * @param Swift_OutputByteStream $os
     * @param int                    $mode
     */
    public function importFromByteStream($nsKey, $itemKey, Swift_OutputByteStream $os, $mode);

    /**
     * Provides a ByteStream which when written to, writes data to $itemKey.
     *
     * NOTE: The stream will always write in append mode.
     * If the optional third parameter is passed all writes will go through $is.
     *
     * @param string                $nsKey
     * @param string                $itemKey
     * @param Swift_InputByteStream $is      optional input stream
     *
     * @return Swift_InputByteStream
     */
    public function getInputByteStream($nsKey, $itemKey, Swift_InputByteStream $is = null);

    /**
     * Get data back out of the cache as a string.
     *
     * @param string $nsKey
     * @param string $itemKey
     *
     * @return string
     */
    public function getString($nsKey, $itemKey);

    /**
     * Get data back out of the cache as a ByteStream.
     *
     * @param string                $nsKey
     * @param string                $itemKey
     * @param Swift_InputByteStream $is      stream to write the data to
     */
    public function exportToByteStream($nsKey, $itemKey, Swift_InputByteStream $is);

    /**
     * Check if the given $itemKey exists in the namespace $nsKey.
     *
     * @param string $nsKey
     * @param string $itemKey
     *
     * @return bool
     */
    public function hasKey($nsKey, $itemKey);

    /**
     * Clear data for $itemKey in the namespace $nsKey if it exists.
     *
     * @param string $nsKey
     * @param string $itemKey
     */
    public function clearKey($nsKey, $itemKey);

    /**
     * Clear all data in the namespace $nsKey if it exists.
     *
     * @param string $nsKey
     */
    public function clearAll($nsKey);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Redundantly and rotationally uses several Transport implementations when sending.
 *
 * @author Chris Corbyn
 */
class Swift_LoadBalancedTransport extends Swift_Transport_LoadBalancedTransport
{
    /**
     * Creates a new LoadBalancedTransport with $transports.
     *
     * @param array $transports
     */
    public function __construct($transports = array())
    {
        call_user_func_array(
            array($this, 'Swift_Transport_LoadBalancedTransport::__construct'),
            Swift_DependencyContainer::getInstance()
                ->createDependenciesFor('transport.loadbalanced')
            );

        $this->setTransports($transports);
    }

    /**
     * Create a new LoadBalancedTransport instance.
     *
     * @param array $transports
     *
     * @return Swift_LoadBalancedTransport
     */
    public static function newInstance($transports = array())
    {
        return new self($transports);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Wraps a standard PHP array in an iterator.
 *
 * @author Chris Corbyn
 */
class Swift_Mailer_ArrayRecipientIterator implements Swift_Mailer_RecipientIterator
{
    /**
     * The list of recipients.
     *
     * @var array
     */
    private $_recipients = array();

    /**
     * Create a new ArrayRecipientIterator from $recipients.
     *
     * @param array $recipients
     */
    public function __construct(array $recipients)
    {
        $this->_recipients = $recipients;
    }

    /**
     * Returns true only if there are more recipients to send to.
     *
     * @return bool
     */
    public function hasNext()
    {
        return !empty($this->_recipients);
    }

    /**
     * Returns an array where the keys are the addresses of recipients and the
     * values are the names. e.g. ('foo@bar' => 'Foo') or ('foo@bar' => NULL).
     *
     * @return array
     */
    public function nextRecipient()
    {
        return array_splice($this->_recipients, 0, 1);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Provides an abstract way of specifying recipients for batch sending.
 *
 * @author Chris Corbyn
 */
interface Swift_Mailer_RecipientIterator
{
    /**
     * Returns true only if there are more recipients to send to.
     *
     * @return bool
     */
    public function hasNext();

    /**
     * Returns an array where the keys are the addresses of recipients and the
     * values are the names. e.g. ('foo@bar' => 'Foo') or ('foo@bar' => NULL).
     *
     * @return array
     */
    public function nextRecipient();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Swift Mailer class.
 *
 * @author Chris Corbyn
 */
class Swift_Mailer
{
    /** The Transport used to send messages */
    private $_transport;

    /**
     * Create a new Mailer using $transport for delivery.
     *
     * @param Swift_Transport $transport
     */
    public function __construct(Swift_Transport $transport)
    {
        $this->_transport = $transport;
    }

    /**
     * Create a new Mailer instance.
     *
     * @param Swift_Transport $transport
     *
     * @return Swift_Mailer
     */
    public static function newInstance(Swift_Transport $transport)
    {
        return new self($transport);
    }

    /**
     * Create a new class instance of one of the message services.
     *
     * For example 'mimepart' would create a 'message.mimepart' instance
     *
     * @param string $service
     *
     * @return object
     */
    public function createMessage($service = 'message')
    {
        return Swift_DependencyContainer::getInstance()
            ->lookup('message.'.$service);
    }

    /**
     * Send the given Message like it would be sent in a mail client.
     *
     * All recipients (with the exception of Bcc) will be able to see the other
     * recipients this message was sent to.
     *
     * Recipient/sender data will be retrieved from the Message object.
     *
     * The return value is the number of recipients who were accepted for
     * delivery.
     *
     * @param Swift_Mime_Message $message
     * @param array              $failedRecipients An array of failures by-reference
     *
     * @return int The number of successful recipients. Can be 0 which indicates failure
     */
    public function send(Swift_Mime_Message $message, &$failedRecipients = null)
    {
        $failedRecipients = (array) $failedRecipients;

        if (!$this->_transport->isStarted()) {
            $this->_transport->start();
        }

        $sent = 0;

        try {
            $sent = $this->_transport->send($message, $failedRecipients);
        } catch (Swift_RfcComplianceException $e) {
            foreach ($message->getTo() as $address => $name) {
                $failedRecipients[] = $address;
            }
        }

        return $sent;
    }

    /**
     * Register a plugin using a known unique key (e.g. myPlugin).
     *
     * @param Swift_Events_EventListener $plugin
     */
    public function registerPlugin(Swift_Events_EventListener $plugin)
    {
        $this->_transport->registerPlugin($plugin);
    }

    /**
     * The Transport used to send messages.
     *
     * @return Swift_Transport
     */
    public function getTransport()
    {
        return $this->_transport;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Sends Messages using the mail() function.
 *
 * @author Chris Corbyn
 *
 * @deprecated since 5.4.5 (to be removed in 6.0)
 */
class Swift_MailTransport extends Swift_Transport_MailTransport
{
    /**
     * Create a new MailTransport, optionally specifying $extraParams.
     *
     * @param string $extraParams
     */
    public function __construct($extraParams = '-f%s')
    {
        call_user_func_array(
            array($this, 'Swift_Transport_MailTransport::__construct'),
            Swift_DependencyContainer::getInstance()
                ->createDependenciesFor('transport.mail')
            );

        $this->setExtraParams($extraParams);
    }

    /**
     * Create a new MailTransport instance.
     *
     * @param string $extraParams To be passed to mail()
     *
     * @return Swift_MailTransport
     */
    public static function newInstance($extraParams = '-f%s')
    {
        return new self($extraParams);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2011 Fabien Potencier <fabien.potencier@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Stores Messages in memory.
 *
 * @author Fabien Potencier
 */
class Swift_MemorySpool implements Swift_Spool
{
    protected $messages = array();
    private $flushRetries = 3;

    /**
     * Tests if this Transport mechanism has started.
     *
     * @return bool
     */
    public function isStarted()
    {
        return true;
    }

    /**
     * Starts this Transport mechanism.
     */
    public function start()
    {
    }

    /**
     * Stops this Transport mechanism.
     */
    public function stop()
    {
    }

    /**
     * @param int $retries
     */
    public function setFlushRetries($retries)
    {
        $this->flushRetries = $retries;
    }

    /**
     * Stores a message in the queue.
     *
     * @param Swift_Mime_Message $message The message to store
     *
     * @return bool Whether the operation has succeeded
     */
    public function queueMessage(Swift_Mime_Message $message)
    {
        //clone the message to make sure it is not changed while in the queue
        $this->messages[] = clone $message;

        return true;
    }

    /**
     * Sends messages using the given transport instance.
     *
     * @param Swift_Transport $transport        A transport instance
     * @param string[]        $failedRecipients An array of failures by-reference
     *
     * @return int The number of sent emails
     */
    public function flushQueue(Swift_Transport $transport, &$failedRecipients = null)
    {
        if (!$this->messages) {
            return 0;
        }

        if (!$transport->isStarted()) {
            $transport->start();
        }

        $count = 0;
        $retries = $this->flushRetries;
        while ($retries--) {
            try {
                while ($message = array_pop($this->messages)) {
                    $count += $transport->send($message, $failedRecipients);
                }
            } catch (Swift_TransportException $exception) {
                if ($retries) {
                    // re-queue the message at the end of the queue to give a chance
                    // to the other messages to be sent, in case the failure was due to
                    // this message and not just the transport failing
                    array_unshift($this->messages, $message);

                    // wait half a second before we try again
                    usleep(500000);
                } else {
                    throw $exception;
                }
            }
        }

        return $count;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * The Message class for building emails.
 *
 * @author Chris Corbyn
 */
class Swift_Message extends Swift_Mime_SimpleMessage
{
    /**
     * @var Swift_Signers_HeaderSigner[]
     */
    private $headerSigners = array();

    /**
     * @var Swift_Signers_BodySigner[]
     */
    private $bodySigners = array();

    /**
     * @var array
     */
    private $savedMessage = array();

    /**
     * Create a new Message.
     *
     * Details may be optionally passed into the constructor.
     *
     * @param string $subject
     * @param string $body
     * @param string $contentType
     * @param string $charset
     */
    public function __construct($subject = null, $body = null, $contentType = null, $charset = null)
    {
        call_user_func_array(
            array($this, 'Swift_Mime_SimpleMessage::__construct'),
            Swift_DependencyContainer::getInstance()
                ->createDependenciesFor('mime.message')
            );

        if (!isset($charset)) {
            $charset = Swift_DependencyContainer::getInstance()
                ->lookup('properties.charset');
        }
        $this->setSubject($subject);
        $this->setBody($body);
        $this->setCharset($charset);
        if ($contentType) {
            $this->setContentType($contentType);
        }
    }

    /**
     * Create a new Message.
     *
     * @param string $subject
     * @param string $body
     * @param string $contentType
     * @param string $charset
     *
     * @return $this
     */
    public static function newInstance($subject = null, $body = null, $contentType = null, $charset = null)
    {
        return new self($subject, $body, $contentType, $charset);
    }

    /**
     * Add a MimePart to this Message.
     *
     * @param string|Swift_OutputByteStream $body
     * @param string                        $contentType
     * @param string                        $charset
     *
     * @return $this
     */
    public function addPart($body, $contentType = null, $charset = null)
    {
        return $this->attach(Swift_MimePart::newInstance(
            $body, $contentType, $charset
            ));
    }

    /**
     * Detach a signature handler from a message.
     *
     * @param Swift_Signer $signer
     *
     * @return $this
     */
    public function attachSigner(Swift_Signer $signer)
    {
        if ($signer instanceof Swift_Signers_HeaderSigner) {
            $this->headerSigners[] = $signer;
        } elseif ($signer instanceof Swift_Signers_BodySigner) {
            $this->bodySigners[] = $signer;
        }

        return $this;
    }

    /**
     * Attach a new signature handler to the message.
     *
     * @param Swift_Signer $signer
     *
     * @return $this
     */
    public function detachSigner(Swift_Signer $signer)
    {
        if ($signer instanceof Swift_Signers_HeaderSigner) {
            foreach ($this->headerSigners as $k => $headerSigner) {
                if ($headerSigner === $signer) {
                    unset($this->headerSigners[$k]);

                    return $this;
                }
            }
        } elseif ($signer instanceof Swift_Signers_BodySigner) {
            foreach ($this->bodySigners as $k => $bodySigner) {
                if ($bodySigner === $signer) {
                    unset($this->bodySigners[$k]);

                    return $this;
                }
            }
        }

        return $this;
    }

    /**
     * Get this message as a complete string.
     *
     * @return string
     */
    public function toString()
    {
        if (empty($this->headerSigners) && empty($this->bodySigners)) {
            return parent::toString();
        }

        $this->saveMessage();

        $this->doSign();

        $string = parent::toString();

        $this->restoreMessage();

        return $string;
    }

    /**
     * Write this message to a {@link Swift_InputByteStream}.
     *
     * @param Swift_InputByteStream $is
     */
    public function toByteStream(Swift_InputByteStream $is)
    {
        if (empty($this->headerSigners) && empty($this->bodySigners)) {
            parent::toByteStream($is);

            return;
        }

        $this->saveMessage();

        $this->doSign();

        parent::toByteStream($is);

        $this->restoreMessage();
    }

    public function __wakeup()
    {
        Swift_DependencyContainer::getInstance()->createDependenciesFor('mime.message');
    }

    /**
     * loops through signers and apply the signatures.
     */
    protected function doSign()
    {
        foreach ($this->bodySigners as $signer) {
            $altered = $signer->getAlteredHeaders();
            $this->saveHeaders($altered);
            $signer->signMessage($this);
        }

        foreach ($this->headerSigners as $signer) {
            $altered = $signer->getAlteredHeaders();
            $this->saveHeaders($altered);
            $signer->reset();

            $signer->setHeaders($this->getHeaders());

            $signer->startBody();
            $this->_bodyToByteStream($signer);
            $signer->endBody();

            $signer->addSignature($this->getHeaders());
        }
    }

    /**
     * save the message before any signature is applied.
     */
    protected function saveMessage()
    {
        $this->savedMessage = array('headers' => array());
        $this->savedMessage['body'] = $this->getBody();
        $this->savedMessage['children'] = $this->getChildren();
        if (count($this->savedMessage['children']) > 0 && $this->getBody() != '') {
            $this->setChildren(array_merge(array($this->_becomeMimePart()), $this->savedMessage['children']));
            $this->setBody('');
        }
    }

    /**
     * save the original headers.
     *
     * @param array $altered
     */
    protected function saveHeaders(array $altered)
    {
        foreach ($altered as $head) {
            $lc = strtolower($head);

            if (!isset($this->savedMessage['headers'][$lc])) {
                $this->savedMessage['headers'][$lc] = $this->getHeaders()->getAll($head);
            }
        }
    }

    /**
     * Remove or restore altered headers.
     */
    protected function restoreHeaders()
    {
        foreach ($this->savedMessage['headers'] as $name => $savedValue) {
            $headers = $this->getHeaders()->getAll($name);

            foreach ($headers as $key => $value) {
                if (!isset($savedValue[$key])) {
                    $this->getHeaders()->remove($name, $key);
                }
            }
        }
    }

    /**
     * Restore message body.
     */
    protected function restoreMessage()
    {
        $this->setBody($this->savedMessage['body']);
        $this->setChildren($this->savedMessage['children']);

        $this->restoreHeaders();
        $this->savedMessage = array();
    }

    /**
     * Clone Message Signers.
     *
     * @see Swift_Mime_SimpleMimeEntity::__clone()
     */
    public function __clone()
    {
        parent::__clone();
        foreach ($this->bodySigners as $key => $bodySigner) {
            $this->bodySigners[$key] = clone $bodySigner;
        }

        foreach ($this->headerSigners as $key => $headerSigner) {
            $this->headerSigners[$key] = clone $headerSigner;
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An attachment, in a multipart message.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_Attachment extends Swift_Mime_SimpleMimeEntity
{
    /** Recognized MIME types */
    private $_mimeTypes = array();

    /**
     * Create a new Attachment with $headers, $encoder and $cache.
     *
     * @param Swift_Mime_HeaderSet      $headers
     * @param Swift_Mime_ContentEncoder $encoder
     * @param Swift_KeyCache            $cache
     * @param Swift_Mime_Grammar        $grammar
     * @param array                     $mimeTypes optional
     */
    public function __construct(Swift_Mime_HeaderSet $headers, Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache, Swift_Mime_Grammar $grammar, $mimeTypes = array())
    {
        parent::__construct($headers, $encoder, $cache, $grammar);
        $this->setDisposition('attachment');
        $this->setContentType('application/octet-stream');
        $this->_mimeTypes = $mimeTypes;
    }

    /**
     * Get the nesting level used for this attachment.
     *
     * Always returns {@link LEVEL_MIXED}.
     *
     * @return int
     */
    public function getNestingLevel()
    {
        return self::LEVEL_MIXED;
    }

    /**
     * Get the Content-Disposition of this attachment.
     *
     * By default attachments have a disposition of "attachment".
     *
     * @return string
     */
    public function getDisposition()
    {
        return $this->_getHeaderFieldModel('Content-Disposition');
    }

    /**
     * Set the Content-Disposition of this attachment.
     *
     * @param string $disposition
     *
     * @return Swift_Mime_Attachment
     */
    public function setDisposition($disposition)
    {
        if (!$this->_setHeaderFieldModel('Content-Disposition', $disposition)) {
            $this->getHeaders()->addParameterizedHeader('Content-Disposition', $disposition);
        }

        return $this;
    }

    /**
     * Get the filename of this attachment when downloaded.
     *
     * @return string
     */
    public function getFilename()
    {
        return $this->_getHeaderParameter('Content-Disposition', 'filename');
    }

    /**
     * Set the filename of this attachment.
     *
     * @param string $filename
     *
     * @return Swift_Mime_Attachment
     */
    public function setFilename($filename)
    {
        $this->_setHeaderParameter('Content-Disposition', 'filename', $filename);
        $this->_setHeaderParameter('Content-Type', 'name', $filename);

        return $this;
    }

    /**
     * Get the file size of this attachment.
     *
     * @return int
     */
    public function getSize()
    {
        return $this->_getHeaderParameter('Content-Disposition', 'size');
    }

    /**
     * Set the file size of this attachment.
     *
     * @param int $size
     *
     * @return Swift_Mime_Attachment
     */
    public function setSize($size)
    {
        $this->_setHeaderParameter('Content-Disposition', 'size', $size);

        return $this;
    }

    /**
     * Set the file that this attachment is for.
     *
     * @param Swift_FileStream $file
     * @param string           $contentType optional
     *
     * @return Swift_Mime_Attachment
     */
    public function setFile(Swift_FileStream $file, $contentType = null)
    {
        $this->setFilename(basename($file->getPath()));
        $this->setBody($file, $contentType);
        if (!isset($contentType)) {
            $extension = strtolower(substr($file->getPath(), strrpos($file->getPath(), '.') + 1));

            if (array_key_exists($extension, $this->_mimeTypes)) {
                $this->setContentType($this->_mimeTypes[$extension]);
            }
        }

        return $this;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Observes changes in an Mime entity's character set.
 *
 * @author Chris Corbyn
 */
interface Swift_Mime_CharsetObserver
{
    /**
     * Notify this observer that the entity's charset has changed.
     *
     * @param string $charset
     */
    public function charsetChanged($charset);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Handles Base 64 Transfer Encoding in Swift Mailer.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_ContentEncoder_Base64ContentEncoder extends Swift_Encoder_Base64Encoder implements Swift_Mime_ContentEncoder
{
    /**
     * Encode stream $in to stream $out.
     *
     * @param Swift_OutputByteStream $os
     * @param Swift_InputByteStream  $is
     * @param int                    $firstLineOffset
     * @param int                    $maxLineLength,  optional, 0 indicates the default of 76 bytes
     */
    public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0)
    {
        if (0 >= $maxLineLength || 76 < $maxLineLength) {
            $maxLineLength = 76;
        }

        $remainder = 0;
        $base64ReadBufferRemainderBytes = null;

        // To reduce memory usage, the output buffer is streamed to the input buffer like so:
        //   Output Stream => base64encode => wrap line length => Input Stream
        // HOWEVER it's important to note that base64_encode() should only be passed whole triplets of data (except for the final chunk of data)
        // otherwise it will assume the input data has *ended* and it will incorrectly pad/terminate the base64 data mid-stream.
        // We use $base64ReadBufferRemainderBytes to carry over 1-2 "remainder" bytes from the each chunk from OutputStream and pre-pend those onto the
        // chunk of bytes read in the next iteration.
        // When the OutputStream is empty, we must flush any remainder bytes.
        while (true) {
            $readBytes = $os->read(8192);
            $atEOF = ($readBytes === false);

            if ($atEOF) {
                $streamTheseBytes = $base64ReadBufferRemainderBytes;
            } else {
                $streamTheseBytes = $base64ReadBufferRemainderBytes.$readBytes;
            }
            $base64ReadBufferRemainderBytes = null;
            $bytesLength = strlen($streamTheseBytes);

            if ($bytesLength === 0) { // no data left to encode
                break;
            }

            // if we're not on the last block of the ouput stream, make sure $streamTheseBytes ends with a complete triplet of data
            // and carry over remainder 1-2 bytes to the next loop iteration
            if (!$atEOF) {
                $excessBytes = $bytesLength % 3;
                if ($excessBytes !== 0) {
                    $base64ReadBufferRemainderBytes = substr($streamTheseBytes, -$excessBytes);
                    $streamTheseBytes = substr($streamTheseBytes, 0, $bytesLength - $excessBytes);
                }
            }

            $encoded = base64_encode($streamTheseBytes);
            $encodedTransformed = '';
            $thisMaxLineLength = $maxLineLength - $remainder - $firstLineOffset;

            while ($thisMaxLineLength < strlen($encoded)) {
                $encodedTransformed .= substr($encoded, 0, $thisMaxLineLength)."\r\n";
                $firstLineOffset = 0;
                $encoded = substr($encoded, $thisMaxLineLength);
                $thisMaxLineLength = $maxLineLength;
                $remainder = 0;
            }

            if (0 < $remainingLength = strlen($encoded)) {
                $remainder += $remainingLength;
                $encodedTransformed .= $encoded;
                $encoded = null;
            }

            $is->write($encodedTransformed);

            if ($atEOF) {
                break;
            }
        }
    }

    /**
     * Get the name of this encoding scheme.
     * Returns the string 'base64'.
     *
     * @return string
     */
    public function getName()
    {
        return 'base64';
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Handles Quoted Printable (QP) Transfer Encoding in Swift Mailer using the PHP core function.
 *
 * @author Lars Strojny
 */
class Swift_Mime_ContentEncoder_NativeQpContentEncoder implements Swift_Mime_ContentEncoder
{
    /**
     * @var null|string
     */
    private $charset;

    /**
     * @param null|string $charset
     */
    public function __construct($charset = null)
    {
        $this->charset = $charset ? $charset : 'utf-8';
    }

    /**
     * Notify this observer that the entity's charset has changed.
     *
     * @param string $charset
     */
    public function charsetChanged($charset)
    {
        $this->charset = $charset;
    }

    /**
     * Encode $in to $out.
     *
     * @param Swift_OutputByteStream $os              to read from
     * @param Swift_InputByteStream  $is              to write to
     * @param int                    $firstLineOffset
     * @param int                    $maxLineLength   0 indicates the default length for this encoding
     *
     * @throws RuntimeException
     */
    public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0)
    {
        if ($this->charset !== 'utf-8') {
            throw new RuntimeException(
                sprintf('Charset "%s" not supported. NativeQpContentEncoder only supports "utf-8"', $this->charset));
        }

        $string = '';

        while (false !== $bytes = $os->read(8192)) {
            $string .= $bytes;
        }

        $is->write($this->encodeString($string));
    }

    /**
     * Get the MIME name of this content encoding scheme.
     *
     * @return string
     */
    public function getName()
    {
        return 'quoted-printable';
    }

    /**
     * Encode a given string to produce an encoded string.
     *
     * @param string $string
     * @param int    $firstLineOffset if first line needs to be shorter
     * @param int    $maxLineLength   0 indicates the default length for this encoding
     *
     * @throws RuntimeException
     *
     * @return string
     */
    public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
    {
        if ($this->charset !== 'utf-8') {
            throw new RuntimeException(
                sprintf('Charset "%s" not supported. NativeQpContentEncoder only supports "utf-8"', $this->charset));
        }

        return $this->_standardize(quoted_printable_encode($string));
    }

    /**
     * Make sure CRLF is correct and HT/SPACE are in valid places.
     *
     * @param string $string
     *
     * @return string
     */
    protected function _standardize($string)
    {
        // transform CR or LF to CRLF
        $string = preg_replace('~=0D(?!=0A)|(?<!=0D)=0A~', '=0D=0A', $string);
        // transform =0D=0A to CRLF
        $string = str_replace(array("\t=0D=0A", ' =0D=0A', '=0D=0A'), array("=09\r\n", "=20\r\n", "\r\n"), $string);

        switch ($end = ord(substr($string, -1))) {
            case 0x09:
                $string = substr_replace($string, '=09', -1);
                break;
            case 0x20:
                $string = substr_replace($string, '=20', -1);
                break;
        }

        return $string;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Handles binary/7/8-bit Transfer Encoding in Swift Mailer.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_ContentEncoder_PlainContentEncoder implements Swift_Mime_ContentEncoder
{
    /**
     * The name of this encoding scheme (probably 7bit or 8bit).
     *
     * @var string
     */
    private $_name;

    /**
     * True if canonical transformations should be done.
     *
     * @var bool
     */
    private $_canonical;

    /**
     * Creates a new PlainContentEncoder with $name (probably 7bit or 8bit).
     *
     * @param string $name
     * @param bool   $canonical If canonicalization transformation should be done.
     */
    public function __construct($name, $canonical = false)
    {
        $this->_name = $name;
        $this->_canonical = $canonical;
    }

    /**
     * Encode a given string to produce an encoded string.
     *
     * @param string $string
     * @param int    $firstLineOffset ignored
     * @param int    $maxLineLength   - 0 means no wrapping will occur
     *
     * @return string
     */
    public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
    {
        if ($this->_canonical) {
            $string = $this->_canonicalize($string);
        }

        return $this->_safeWordWrap($string, $maxLineLength, "\r\n");
    }

    /**
     * Encode stream $in to stream $out.
     *
     * @param Swift_OutputByteStream $os
     * @param Swift_InputByteStream  $is
     * @param int                    $firstLineOffset ignored
     * @param int                    $maxLineLength   optional, 0 means no wrapping will occur
     */
    public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0)
    {
        $leftOver = '';
        while (false !== $bytes = $os->read(8192)) {
            $toencode = $leftOver.$bytes;
            if ($this->_canonical) {
                $toencode = $this->_canonicalize($toencode);
            }
            $wrapped = $this->_safeWordWrap($toencode, $maxLineLength, "\r\n");
            $lastLinePos = strrpos($wrapped, "\r\n");
            $leftOver = substr($wrapped, $lastLinePos);
            $wrapped = substr($wrapped, 0, $lastLinePos);

            $is->write($wrapped);
        }
        if (strlen($leftOver)) {
            $is->write($leftOver);
        }
    }

    /**
     * Get the name of this encoding scheme.
     *
     * @return string
     */
    public function getName()
    {
        return $this->_name;
    }

    /**
     * Not used.
     */
    public function charsetChanged($charset)
    {
    }

    /**
     * A safer (but weaker) wordwrap for unicode.
     *
     * @param string $string
     * @param int    $length
     * @param string $le
     *
     * @return string
     */
    private function _safeWordwrap($string, $length = 75, $le = "\r\n")
    {
        if (0 >= $length) {
            return $string;
        }

        $originalLines = explode($le, $string);

        $lines = array();
        $lineCount = 0;

        foreach ($originalLines as $originalLine) {
            $lines[] = '';
            $currentLine = &$lines[$lineCount++];

            //$chunks = preg_split('/(?<=[\ \t,\.!\?\-&\+\/])/', $originalLine);
            $chunks = preg_split('/(?<=\s)/', $originalLine);

            foreach ($chunks as $chunk) {
                if (0 != strlen($currentLine)
                    && strlen($currentLine.$chunk) > $length) {
                    $lines[] = '';
                    $currentLine = &$lines[$lineCount++];
                }
                $currentLine .= $chunk;
            }
        }

        return implode("\r\n", $lines);
    }

    /**
     * Canonicalize string input (fix CRLF).
     *
     * @param string $string
     *
     * @return string
     */
    private function _canonicalize($string)
    {
        return str_replace(
            array("\r\n", "\r", "\n"),
            array("\n", "\n", "\r\n"),
            $string
            );
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Handles Quoted Printable (QP) Transfer Encoding in Swift Mailer.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_ContentEncoder_QpContentEncoder extends Swift_Encoder_QpEncoder implements Swift_Mime_ContentEncoder
{
    protected $_dotEscape;

    /**
     * Creates a new QpContentEncoder for the given CharacterStream.
     *
     * @param Swift_CharacterStream $charStream to use for reading characters
     * @param Swift_StreamFilter    $filter     if canonicalization should occur
     * @param bool                  $dotEscape  if dot stuffing workaround must be enabled
     */
    public function __construct(Swift_CharacterStream $charStream, Swift_StreamFilter $filter = null, $dotEscape = false)
    {
        $this->_dotEscape = $dotEscape;
        parent::__construct($charStream, $filter);
    }

    public function __sleep()
    {
        return array('_charStream', '_filter', '_dotEscape');
    }

    protected function getSafeMapShareId()
    {
        return get_class($this).($this->_dotEscape ? '.dotEscape' : '');
    }

    protected function initSafeMap()
    {
        parent::initSafeMap();
        if ($this->_dotEscape) {
            /* Encode . as =2e for buggy remote servers */
            unset($this->_safeMap[0x2e]);
        }
    }

    /**
     * Encode stream $in to stream $out.
     *
     * QP encoded strings have a maximum line length of 76 characters.
     * If the first line needs to be shorter, indicate the difference with
     * $firstLineOffset.
     *
     * @param Swift_OutputByteStream $os              output stream
     * @param Swift_InputByteStream  $is              input stream
     * @param int                    $firstLineOffset
     * @param int                    $maxLineLength
     */
    public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0)
    {
        if ($maxLineLength > 76 || $maxLineLength <= 0) {
            $maxLineLength = 76;
        }

        $thisLineLength = $maxLineLength - $firstLineOffset;

        $this->_charStream->flushContents();
        $this->_charStream->importByteStream($os);

        $currentLine = '';
        $prepend = '';
        $size = $lineLen = 0;

        while (false !== $bytes = $this->_nextSequence()) {
            // If we're filtering the input
            if (isset($this->_filter)) {
                // If we can't filter because we need more bytes
                while ($this->_filter->shouldBuffer($bytes)) {
                    // Then collect bytes into the buffer
                    if (false === $moreBytes = $this->_nextSequence(1)) {
                        break;
                    }

                    foreach ($moreBytes as $b) {
                        $bytes[] = $b;
                    }
                }
                // And filter them
                $bytes = $this->_filter->filter($bytes);
            }

            $enc = $this->_encodeByteSequence($bytes, $size);

            $i = strpos($enc, '=0D=0A');
            $newLineLength = $lineLen + ($i === false ? $size : $i);

            if ($currentLine && $newLineLength >= $thisLineLength) {
                $is->write($prepend.$this->_standardize($currentLine));
                $currentLine = '';
                $prepend = "=\r\n";
                $thisLineLength = $maxLineLength;
                $lineLen = 0;
            }

            $currentLine .= $enc;

            if ($i === false) {
                $lineLen += $size;
            } else {
                // 6 is the length of '=0D=0A'.
                $lineLen = $size - strrpos($enc, '=0D=0A') - 6;
            }
        }
        if (strlen($currentLine)) {
            $is->write($prepend.$this->_standardize($currentLine));
        }
    }

    /**
     * Get the name of this encoding scheme.
     * Returns the string 'quoted-printable'.
     *
     * @return string
     */
    public function getName()
    {
        return 'quoted-printable';
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Proxy for quoted-printable content encoders.
 *
 * Switches on the best QP encoder implementation for current charset.
 *
 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
 */
class Swift_Mime_ContentEncoder_QpContentEncoderProxy implements Swift_Mime_ContentEncoder
{
    /**
     * @var Swift_Mime_ContentEncoder_QpContentEncoder
     */
    private $safeEncoder;

    /**
     * @var Swift_Mime_ContentEncoder_NativeQpContentEncoder
     */
    private $nativeEncoder;

    /**
     * @var null|string
     */
    private $charset;

    /**
     * Constructor.
     *
     * @param Swift_Mime_ContentEncoder_QpContentEncoder       $safeEncoder
     * @param Swift_Mime_ContentEncoder_NativeQpContentEncoder $nativeEncoder
     * @param string|null                                      $charset
     */
    public function __construct(Swift_Mime_ContentEncoder_QpContentEncoder $safeEncoder, Swift_Mime_ContentEncoder_NativeQpContentEncoder $nativeEncoder, $charset)
    {
        $this->safeEncoder = $safeEncoder;
        $this->nativeEncoder = $nativeEncoder;
        $this->charset = $charset;
    }

    /**
     * Make a deep copy of object.
     */
    public function __clone()
    {
        $this->safeEncoder = clone $this->safeEncoder;
        $this->nativeEncoder = clone $this->nativeEncoder;
    }

    /**
     * {@inheritdoc}
     */
    public function charsetChanged($charset)
    {
        $this->charset = $charset;
        $this->safeEncoder->charsetChanged($charset);
    }

    /**
     * {@inheritdoc}
     */
    public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0)
    {
        $this->getEncoder()->encodeByteStream($os, $is, $firstLineOffset, $maxLineLength);
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'quoted-printable';
    }

    /**
     * {@inheritdoc}
     */
    public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
    {
        return $this->getEncoder()->encodeString($string, $firstLineOffset, $maxLineLength);
    }

    /**
     * @return Swift_Mime_ContentEncoder
     */
    private function getEncoder()
    {
        return 'utf-8' === $this->charset ? $this->nativeEncoder : $this->safeEncoder;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Handles raw Transfer Encoding in Swift Mailer.
 *
 *
 * @author Sebastiaan Stok <s.stok@rollerscapes.net>
 */
class Swift_Mime_ContentEncoder_RawContentEncoder implements Swift_Mime_ContentEncoder
{
    /**
     * Encode a given string to produce an encoded string.
     *
     * @param string $string
     * @param int    $firstLineOffset ignored
     * @param int    $maxLineLength   ignored
     *
     * @return string
     */
    public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
    {
        return $string;
    }

    /**
     * Encode stream $in to stream $out.
     *
     * @param Swift_OutputByteStream $in
     * @param Swift_InputByteStream  $out
     * @param int                    $firstLineOffset ignored
     * @param int                    $maxLineLength   ignored
     */
    public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0)
    {
        while (false !== ($bytes = $os->read(8192))) {
            $is->write($bytes);
        }
    }

    /**
     * Get the name of this encoding scheme.
     *
     * @return string
     */
    public function getName()
    {
        return 'raw';
    }

    /**
     * Not used.
     */
    public function charsetChanged($charset)
    {
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Interface for all Transfer Encoding schemes.
 *
 * @author Chris Corbyn
 */
interface Swift_Mime_ContentEncoder extends Swift_Encoder
{
    /**
     * Encode $in to $out.
     *
     * @param Swift_OutputByteStream $os              to read from
     * @param Swift_InputByteStream  $is              to write to
     * @param int                    $firstLineOffset
     * @param int                    $maxLineLength   - 0 indicates the default length for this encoding
     */
    public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0);

    /**
     * Get the MIME name of this content encoding scheme.
     *
     * @return string
     */
    public function getName();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An embedded file, in a multipart message.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_EmbeddedFile extends Swift_Mime_Attachment
{
    /**
     * Creates a new Attachment with $headers and $encoder.
     *
     * @param Swift_Mime_HeaderSet      $headers
     * @param Swift_Mime_ContentEncoder $encoder
     * @param Swift_KeyCache            $cache
     * @param Swift_Mime_Grammar        $grammar
     * @param array                     $mimeTypes optional
     */
    public function __construct(Swift_Mime_HeaderSet $headers, Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache, Swift_Mime_Grammar $grammar, $mimeTypes = array())
    {
        parent::__construct($headers, $encoder, $cache, $grammar, $mimeTypes);
        $this->setDisposition('inline');
        $this->setId($this->getId());
    }

    /**
     * Get the nesting level of this EmbeddedFile.
     *
     * Returns {@see LEVEL_RELATED}.
     *
     * @return int
     */
    public function getNestingLevel()
    {
        return self::LEVEL_RELATED;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Observes changes for a Mime entity's ContentEncoder.
 *
 * @author Chris Corbyn
 */
interface Swift_Mime_EncodingObserver
{
    /**
     * Notify this observer that the observed entity's ContentEncoder has changed.
     *
     * @param Swift_Mime_ContentEncoder $encoder
     */
    public function encoderChanged(Swift_Mime_ContentEncoder $encoder);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Defines the grammar to use for validation, implements the RFC 2822 (and friends) ABNF grammar definitions.
 *
 * @author     Fabien Potencier
 * @author     Chris Corbyn
 */
class Swift_Mime_Grammar
{
    /**
     * Special characters used in the syntax which need to be escaped.
     *
     * @var string[]
     */
    private static $_specials = array();

    /**
     * Tokens defined in RFC 2822 (and some related RFCs).
     *
     * @var string[]
     */
    private static $_grammar = array();

    /**
     * Initialize some RFC 2822 (and friends) ABNF grammar definitions.
     */
    public function __construct()
    {
        $this->init();
    }

    public function __wakeup()
    {
        $this->init();
    }

    protected function init()
    {
        if (count(self::$_specials) > 0) {
            return;
        }

        self::$_specials = array(
            '(', ')', '<', '>', '[', ']',
            ':', ';', '@', ',', '.', '"',
            );

        /*** Refer to RFC 2822 for ABNF grammar ***/

        // All basic building blocks
        self::$_grammar['NO-WS-CTL'] = '[\x01-\x08\x0B\x0C\x0E-\x19\x7F]';
        self::$_grammar['WSP'] = '[ \t]';
        self::$_grammar['CRLF'] = '(?:\r\n)';
        self::$_grammar['FWS'] = '(?:(?:'.self::$_grammar['WSP'].'*'.
                self::$_grammar['CRLF'].')?'.self::$_grammar['WSP'].')';
        self::$_grammar['text'] = '[\x00-\x08\x0B\x0C\x0E-\x7F]';
        self::$_grammar['quoted-pair'] = '(?:\\\\'.self::$_grammar['text'].')';
        self::$_grammar['ctext'] = '(?:'.self::$_grammar['NO-WS-CTL'].
                '|[\x21-\x27\x2A-\x5B\x5D-\x7E])';
        // Uses recursive PCRE (?1) -- could be a weak point??
        self::$_grammar['ccontent'] = '(?:'.self::$_grammar['ctext'].'|'.
                self::$_grammar['quoted-pair'].'|(?1))';
        self::$_grammar['comment'] = '(\((?:'.self::$_grammar['FWS'].'|'.
                self::$_grammar['ccontent'].')*'.self::$_grammar['FWS'].'?\))';
        self::$_grammar['CFWS'] = '(?:(?:'.self::$_grammar['FWS'].'?'.
                self::$_grammar['comment'].')*(?:(?:'.self::$_grammar['FWS'].'?'.
                self::$_grammar['comment'].')|'.self::$_grammar['FWS'].'))';
        self::$_grammar['qtext'] = '(?:'.self::$_grammar['NO-WS-CTL'].
                '|[\x21\x23-\x5B\x5D-\x7E])';
        self::$_grammar['qcontent'] = '(?:'.self::$_grammar['qtext'].'|'.
                self::$_grammar['quoted-pair'].')';
        self::$_grammar['quoted-string'] = '(?:'.self::$_grammar['CFWS'].'?"'.
                '('.self::$_grammar['FWS'].'?'.self::$_grammar['qcontent'].')*'.
                self::$_grammar['FWS'].'?"'.self::$_grammar['CFWS'].'?)';
        self::$_grammar['atext'] = '[a-zA-Z0-9!#\$%&\'\*\+\-\/=\?\^_`\{\}\|~]';
        self::$_grammar['atom'] = '(?:'.self::$_grammar['CFWS'].'?'.
                self::$_grammar['atext'].'+'.self::$_grammar['CFWS'].'?)';
        self::$_grammar['dot-atom-text'] = '(?:'.self::$_grammar['atext'].'+'.
                '(\.'.self::$_grammar['atext'].'+)*)';
        self::$_grammar['dot-atom'] = '(?:'.self::$_grammar['CFWS'].'?'.
                self::$_grammar['dot-atom-text'].'+'.self::$_grammar['CFWS'].'?)';
        self::$_grammar['word'] = '(?:'.self::$_grammar['atom'].'|'.
                self::$_grammar['quoted-string'].')';
        self::$_grammar['phrase'] = '(?:'.self::$_grammar['word'].'+?)';
        self::$_grammar['no-fold-quote'] = '(?:"(?:'.self::$_grammar['qtext'].
                '|'.self::$_grammar['quoted-pair'].')*")';
        self::$_grammar['dtext'] = '(?:'.self::$_grammar['NO-WS-CTL'].
                '|[\x21-\x5A\x5E-\x7E])';
        self::$_grammar['no-fold-literal'] = '(?:\[(?:'.self::$_grammar['dtext'].
                '|'.self::$_grammar['quoted-pair'].')*\])';

        // Message IDs
        self::$_grammar['id-left'] = '(?:'.self::$_grammar['dot-atom-text'].'|'.
                self::$_grammar['no-fold-quote'].')';
        self::$_grammar['id-right'] = '(?:'.self::$_grammar['dot-atom-text'].'|'.
                self::$_grammar['no-fold-literal'].')';

        // Addresses, mailboxes and paths
        self::$_grammar['local-part'] = '(?:'.self::$_grammar['dot-atom'].'|'.
                self::$_grammar['quoted-string'].')';
        self::$_grammar['dcontent'] = '(?:'.self::$_grammar['dtext'].'|'.
                self::$_grammar['quoted-pair'].')';
        self::$_grammar['domain-literal'] = '(?:'.self::$_grammar['CFWS'].'?\[('.
                self::$_grammar['FWS'].'?'.self::$_grammar['dcontent'].')*?'.
                self::$_grammar['FWS'].'?\]'.self::$_grammar['CFWS'].'?)';
        self::$_grammar['domain'] = '(?:'.self::$_grammar['dot-atom'].'|'.
                self::$_grammar['domain-literal'].')';
        self::$_grammar['addr-spec'] = '(?:'.self::$_grammar['local-part'].'@'.
                self::$_grammar['domain'].')';
    }

    /**
     * Get the grammar defined for $name token.
     *
     * @param string $name exactly as written in the RFC
     *
     * @return string
     */
    public function getDefinition($name)
    {
        if (array_key_exists($name, self::$_grammar)) {
            return self::$_grammar[$name];
        }

        throw new Swift_RfcComplianceException(
            "No such grammar '".$name."' defined."
        );
    }

    /**
     * Returns the tokens defined in RFC 2822 (and some related RFCs).
     *
     * @return array
     */
    public function getGrammarDefinitions()
    {
        return self::$_grammar;
    }

    /**
     * Returns the current special characters used in the syntax which need to be escaped.
     *
     * @return array
     */
    public function getSpecials()
    {
        return self::$_specials;
    }

    /**
     * Escape special characters in a string (convert to quoted-pairs).
     *
     * @param string   $token
     * @param string[] $include additional chars to escape
     * @param string[] $exclude chars from escaping
     *
     * @return string
     */
    public function escapeSpecials($token, $include = array(), $exclude = array())
    {
        foreach (array_merge(array('\\'), array_diff(self::$_specials, $exclude), $include) as $char) {
            $token = str_replace($char, '\\'.$char, $token);
        }

        return $token;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A MIME Header.
 *
 * @author Chris Corbyn
 */
interface Swift_Mime_Header
{
    /** Text headers */
    const TYPE_TEXT = 2;

    /**  headers (text + params) */
    const TYPE_PARAMETERIZED = 6;

    /** Mailbox and address headers */
    const TYPE_MAILBOX = 8;

    /** Date and time headers */
    const TYPE_DATE = 16;

    /** Identification headers */
    const TYPE_ID = 32;

    /** Address path headers */
    const TYPE_PATH = 64;

    /**
     * Get the type of Header that this instance represents.
     *
     * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
     * @see TYPE_DATE, TYPE_ID, TYPE_PATH
     *
     * @return int
     */
    public function getFieldType();

    /**
     * Set the model for the field body.
     *
     * The actual types needed will vary depending upon the type of Header.
     *
     * @param mixed $model
     */
    public function setFieldBodyModel($model);

    /**
     * Set the charset used when rendering the Header.
     *
     * @param string $charset
     */
    public function setCharset($charset);

    /**
     * Get the model for the field body.
     *
     * The return type depends on the specifics of the Header.
     *
     * @return mixed
     */
    public function getFieldBodyModel();

    /**
     * Get the name of this header (e.g. Subject).
     *
     * The name is an identifier and as such will be immutable.
     *
     * @return string
     */
    public function getFieldName();

    /**
     * Get the field body, prepared for folding into a final header value.
     *
     * @return string
     */
    public function getFieldBody();

    /**
     * Get this Header rendered as a compliant string.
     *
     * @return string
     */
    public function toString();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Handles Base64 (B) Header Encoding in Swift Mailer.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_HeaderEncoder_Base64HeaderEncoder extends Swift_Encoder_Base64Encoder implements Swift_Mime_HeaderEncoder
{
    /**
     * Get the name of this encoding scheme.
     * Returns the string 'B'.
     *
     * @return string
     */
    public function getName()
    {
        return 'B';
    }

    /**
     * Takes an unencoded string and produces a Base64 encoded string from it.
     *
     * If the charset is iso-2022-jp, it uses mb_encode_mimeheader instead of
     * default encodeString, otherwise pass to the parent method.
     *
     * @param string $string          string to encode
     * @param int    $firstLineOffset
     * @param int    $maxLineLength   optional, 0 indicates the default of 76 bytes
     * @param string $charset
     *
     * @return string
     */
    public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0, $charset = 'utf-8')
    {
        if (strtolower($charset) === 'iso-2022-jp') {
            $old = mb_internal_encoding();
            mb_internal_encoding('utf-8');
            $newstring = mb_encode_mimeheader($string, $charset, $this->getName(), "\r\n");
            mb_internal_encoding($old);

            return $newstring;
        }

        return parent::encodeString($string, $firstLineOffset, $maxLineLength);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Handles Quoted Printable (Q) Header Encoding in Swift Mailer.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_HeaderEncoder_QpHeaderEncoder extends Swift_Encoder_QpEncoder implements Swift_Mime_HeaderEncoder
{
    /**
     * Creates a new QpHeaderEncoder for the given CharacterStream.
     *
     * @param Swift_CharacterStream $charStream to use for reading characters
     */
    public function __construct(Swift_CharacterStream $charStream)
    {
        parent::__construct($charStream);
    }

    protected function initSafeMap()
    {
        foreach (array_merge(
            range(0x61, 0x7A), range(0x41, 0x5A),
            range(0x30, 0x39), array(0x20, 0x21, 0x2A, 0x2B, 0x2D, 0x2F)
        ) as $byte) {
            $this->_safeMap[$byte] = chr($byte);
        }
    }

    /**
     * Get the name of this encoding scheme.
     *
     * Returns the string 'Q'.
     *
     * @return string
     */
    public function getName()
    {
        return 'Q';
    }

    /**
     * Takes an unencoded string and produces a QP encoded string from it.
     *
     * @param string $string          string to encode
     * @param int    $firstLineOffset optional
     * @param int    $maxLineLength   optional, 0 indicates the default of 76 chars
     *
     * @return string
     */
    public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
    {
        return str_replace(array(' ', '=20', "=\r\n"), array('_', '_', "\r\n"),
            parent::encodeString($string, $firstLineOffset, $maxLineLength)
        );
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Interface for all Header Encoding schemes.
 *
 * @author Chris Corbyn
 */
interface Swift_Mime_HeaderEncoder extends Swift_Encoder
{
    /**
     * Get the MIME name of this content encoding scheme.
     *
     * @return string
     */
    public function getName();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Creates MIME headers.
 *
 * @author Chris Corbyn
 */
interface Swift_Mime_HeaderFactory extends Swift_Mime_CharsetObserver
{
    /**
     * Create a new Mailbox Header with a list of $addresses.
     *
     * @param string       $name
     * @param array|string $addresses
     *
     * @return Swift_Mime_Header
     */
    public function createMailboxHeader($name, $addresses = null);

    /**
     * Create a new Date header using $timestamp (UNIX time).
     *
     * @param string $name
     * @param int    $timestamp
     *
     * @return Swift_Mime_Header
     */
    public function createDateHeader($name, $timestamp = null);

    /**
     * Create a new basic text header with $name and $value.
     *
     * @param string $name
     * @param string $value
     *
     * @return Swift_Mime_Header
     */
    public function createTextHeader($name, $value = null);

    /**
     * Create a new ParameterizedHeader with $name, $value and $params.
     *
     * @param string $name
     * @param string $value
     * @param array  $params
     *
     * @return Swift_Mime_ParameterizedHeader
     */
    public function createParameterizedHeader($name, $value = null, $params = array());

    /**
     * Create a new ID header for Message-ID or Content-ID.
     *
     * @param string       $name
     * @param string|array $ids
     *
     * @return Swift_Mime_Header
     */
    public function createIdHeader($name, $ids = null);

    /**
     * Create a new Path header with an address (path) in it.
     *
     * @param string $name
     * @param string $path
     *
     * @return Swift_Mime_Header
     */
    public function createPathHeader($name, $path = null);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An abstract base MIME Header.
 *
 * @author Chris Corbyn
 */
abstract class Swift_Mime_Headers_AbstractHeader implements Swift_Mime_Header
{
    /**
     * The name of this Header.
     *
     * @var string
     */
    private $_name;

    /**
     * The Grammar used for this Header.
     *
     * @var Swift_Mime_Grammar
     */
    private $_grammar;

    /**
     * The Encoder used to encode this Header.
     *
     * @var Swift_Encoder
     */
    private $_encoder;

    /**
     * The maximum length of a line in the header.
     *
     * @var int
     */
    private $_lineLength = 78;

    /**
     * The language used in this Header.
     *
     * @var string
     */
    private $_lang;

    /**
     * The character set of the text in this Header.
     *
     * @var string
     */
    private $_charset = 'utf-8';

    /**
     * The value of this Header, cached.
     *
     * @var string
     */
    private $_cachedValue = null;

    /**
     * Creates a new Header.
     *
     * @param Swift_Mime_Grammar $grammar
     */
    public function __construct(Swift_Mime_Grammar $grammar)
    {
        $this->setGrammar($grammar);
    }

    /**
     * Set the character set used in this Header.
     *
     * @param string $charset
     */
    public function setCharset($charset)
    {
        $this->clearCachedValueIf($charset != $this->_charset);
        $this->_charset = $charset;
        if (isset($this->_encoder)) {
            $this->_encoder->charsetChanged($charset);
        }
    }

    /**
     * Get the character set used in this Header.
     *
     * @return string
     */
    public function getCharset()
    {
        return $this->_charset;
    }

    /**
     * Set the language used in this Header.
     *
     * For example, for US English, 'en-us'.
     * This can be unspecified.
     *
     * @param string $lang
     */
    public function setLanguage($lang)
    {
        $this->clearCachedValueIf($this->_lang != $lang);
        $this->_lang = $lang;
    }

    /**
     * Get the language used in this Header.
     *
     * @return string
     */
    public function getLanguage()
    {
        return $this->_lang;
    }

    /**
     * Set the encoder used for encoding the header.
     *
     * @param Swift_Mime_HeaderEncoder $encoder
     */
    public function setEncoder(Swift_Mime_HeaderEncoder $encoder)
    {
        $this->_encoder = $encoder;
        $this->setCachedValue(null);
    }

    /**
     * Get the encoder used for encoding this Header.
     *
     * @return Swift_Mime_HeaderEncoder
     */
    public function getEncoder()
    {
        return $this->_encoder;
    }

    /**
     * Set the grammar used for the header.
     *
     * @param Swift_Mime_Grammar $grammar
     */
    public function setGrammar(Swift_Mime_Grammar $grammar)
    {
        $this->_grammar = $grammar;
        $this->setCachedValue(null);
    }

    /**
     * Get the grammar used for this Header.
     *
     * @return Swift_Mime_Grammar
     */
    public function getGrammar()
    {
        return $this->_grammar;
    }

    /**
     * Get the name of this header (e.g. charset).
     *
     * @return string
     */
    public function getFieldName()
    {
        return $this->_name;
    }

    /**
     * Set the maximum length of lines in the header (excluding EOL).
     *
     * @param int $lineLength
     */
    public function setMaxLineLength($lineLength)
    {
        $this->clearCachedValueIf($this->_lineLength != $lineLength);
        $this->_lineLength = $lineLength;
    }

    /**
     * Get the maximum permitted length of lines in this Header.
     *
     * @return int
     */
    public function getMaxLineLength()
    {
        return $this->_lineLength;
    }

    /**
     * Get this Header rendered as a RFC 2822 compliant string.
     *
     * @throws Swift_RfcComplianceException
     *
     * @return string
     */
    public function toString()
    {
        return $this->_tokensToString($this->toTokens());
    }

    /**
     * Returns a string representation of this object.
     *
     * @return string
     *
     * @see toString()
     */
    public function __toString()
    {
        return $this->toString();
    }

    // -- Points of extension

    /**
     * Set the name of this Header field.
     *
     * @param string $name
     */
    protected function setFieldName($name)
    {
        $this->_name = $name;
    }

    /**
     * Produces a compliant, formatted RFC 2822 'phrase' based on the string given.
     *
     * @param Swift_Mime_Header        $header
     * @param string                   $string  as displayed
     * @param string                   $charset of the text
     * @param Swift_Mime_HeaderEncoder $encoder
     * @param bool                     $shorten the first line to make remove for header name
     *
     * @return string
     */
    protected function createPhrase(Swift_Mime_Header $header, $string, $charset, Swift_Mime_HeaderEncoder $encoder = null, $shorten = false)
    {
        // Treat token as exactly what was given
        $phraseStr = $string;
        // If it's not valid
        if (!preg_match('/^'.$this->getGrammar()->getDefinition('phrase').'$/D', $phraseStr)) {
            // .. but it is just ascii text, try escaping some characters
            // and make it a quoted-string
            if (preg_match('/^'.$this->getGrammar()->getDefinition('text').'*$/D', $phraseStr)) {
                $phraseStr = $this->getGrammar()->escapeSpecials(
                    $phraseStr, array('"'), $this->getGrammar()->getSpecials()
                    );
                $phraseStr = '"'.$phraseStr.'"';
            } else {
                // ... otherwise it needs encoding
                // Determine space remaining on line if first line
                if ($shorten) {
                    $usedLength = strlen($header->getFieldName().': ');
                } else {
                    $usedLength = 0;
                }
                $phraseStr = $this->encodeWords($header, $string, $usedLength);
            }
        }

        return $phraseStr;
    }

    /**
     * Encode needed word tokens within a string of input.
     *
     * @param Swift_Mime_Header $header
     * @param string            $input
     * @param string            $usedLength optional
     *
     * @return string
     */
    protected function encodeWords(Swift_Mime_Header $header, $input, $usedLength = -1)
    {
        $value = '';

        $tokens = $this->getEncodableWordTokens($input);

        foreach ($tokens as $token) {
            // See RFC 2822, Sect 2.2 (really 2.2 ??)
            if ($this->tokenNeedsEncoding($token)) {
                // Don't encode starting WSP
                $firstChar = substr($token, 0, 1);
                switch ($firstChar) {
                    case ' ':
                    case "\t":
                        $value .= $firstChar;
                        $token = substr($token, 1);
                }

                if (-1 == $usedLength) {
                    $usedLength = strlen($header->getFieldName().': ') + strlen($value);
                }
                $value .= $this->getTokenAsEncodedWord($token, $usedLength);

                $header->setMaxLineLength(76); // Forcefully override
            } else {
                $value .= $token;
            }
        }

        return $value;
    }

    /**
     * Test if a token needs to be encoded or not.
     *
     * @param string $token
     *
     * @return bool
     */
    protected function tokenNeedsEncoding($token)
    {
        return preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token);
    }

    /**
     * Splits a string into tokens in blocks of words which can be encoded quickly.
     *
     * @param string $string
     *
     * @return string[]
     */
    protected function getEncodableWordTokens($string)
    {
        $tokens = array();

        $encodedToken = '';
        // Split at all whitespace boundaries
        foreach (preg_split('~(?=[\t ])~', $string) as $token) {
            if ($this->tokenNeedsEncoding($token)) {
                $encodedToken .= $token;
            } else {
                if (strlen($encodedToken) > 0) {
                    $tokens[] = $encodedToken;
                    $encodedToken = '';
                }
                $tokens[] = $token;
            }
        }
        if (strlen($encodedToken)) {
            $tokens[] = $encodedToken;
        }

        return $tokens;
    }

    /**
     * Get a token as an encoded word for safe insertion into headers.
     *
     * @param string $token           token to encode
     * @param int    $firstLineOffset optional
     *
     * @return string
     */
    protected function getTokenAsEncodedWord($token, $firstLineOffset = 0)
    {
        // Adjust $firstLineOffset to account for space needed for syntax
        $charsetDecl = $this->_charset;
        if (isset($this->_lang)) {
            $charsetDecl .= '*'.$this->_lang;
        }
        $encodingWrapperLength = strlen(
            '=?'.$charsetDecl.'?'.$this->_encoder->getName().'??='
            );

        if ($firstLineOffset >= 75) {
            //Does this logic need to be here?
            $firstLineOffset = 0;
        }

        $encodedTextLines = explode("\r\n",
            $this->_encoder->encodeString(
                $token, $firstLineOffset, 75 - $encodingWrapperLength, $this->_charset
                )
        );

        if (strtolower($this->_charset) !== 'iso-2022-jp') {
            // special encoding for iso-2022-jp using mb_encode_mimeheader
            foreach ($encodedTextLines as $lineNum => $line) {
                $encodedTextLines[$lineNum] = '=?'.$charsetDecl.
                    '?'.$this->_encoder->getName().
                    '?'.$line.'?=';
            }
        }

        return implode("\r\n ", $encodedTextLines);
    }

    /**
     * Generates tokens from the given string which include CRLF as individual tokens.
     *
     * @param string $token
     *
     * @return string[]
     */
    protected function generateTokenLines($token)
    {
        return preg_split('~(\r\n)~', $token, -1, PREG_SPLIT_DELIM_CAPTURE);
    }

    /**
     * Set a value into the cache.
     *
     * @param string $value
     */
    protected function setCachedValue($value)
    {
        $this->_cachedValue = $value;
    }

    /**
     * Get the value in the cache.
     *
     * @return string
     */
    protected function getCachedValue()
    {
        return $this->_cachedValue;
    }

    /**
     * Clear the cached value if $condition is met.
     *
     * @param bool $condition
     */
    protected function clearCachedValueIf($condition)
    {
        if ($condition) {
            $this->setCachedValue(null);
        }
    }

    /**
     * Generate a list of all tokens in the final header.
     *
     * @param string $string The string to tokenize
     *
     * @return array An array of tokens as strings
     */
    protected function toTokens($string = null)
    {
        if (is_null($string)) {
            $string = $this->getFieldBody();
        }

        $tokens = array();

        // Generate atoms; split at all invisible boundaries followed by WSP
        foreach (preg_split('~(?=[ \t])~', $string) as $token) {
            $newTokens = $this->generateTokenLines($token);
            foreach ($newTokens as $newToken) {
                $tokens[] = $newToken;
            }
        }

        return $tokens;
    }

    /**
     * Takes an array of tokens which appear in the header and turns them into
     * an RFC 2822 compliant string, adding FWSP where needed.
     *
     * @param string[] $tokens
     *
     * @return string
     */
    private function _tokensToString(array $tokens)
    {
        $lineCount = 0;
        $headerLines = array();
        $headerLines[] = $this->_name.': ';
        $currentLine = &$headerLines[$lineCount++];

        // Build all tokens back into compliant header
        foreach ($tokens as $i => $token) {
            // Line longer than specified maximum or token was just a new line
            if (("\r\n" == $token) ||
                ($i > 0 && strlen($currentLine.$token) > $this->_lineLength)
                && 0 < strlen($currentLine)) {
                $headerLines[] = '';
                $currentLine = &$headerLines[$lineCount++];
            }

            // Append token to the line
            if ("\r\n" != $token) {
                $currentLine .= $token;
            }
        }

        // Implode with FWS (RFC 2822, 2.2.3)
        return implode("\r\n", $headerLines)."\r\n";
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A Date MIME Header for Swift Mailer.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_Headers_DateHeader extends Swift_Mime_Headers_AbstractHeader
{
    /**
     * The UNIX timestamp value of this Header.
     *
     * @var int
     */
    private $_timestamp;

    /**
     * Creates a new DateHeader with $name and $timestamp.
     *
     * Example:
     * <code>
     * <?php
     * $header = new Swift_Mime_Headers_DateHeader('Date', time());
     * ?>
     * </code>
     *
     * @param string             $name    of Header
     * @param Swift_Mime_Grammar $grammar
     */
    public function __construct($name, Swift_Mime_Grammar $grammar)
    {
        $this->setFieldName($name);
        parent::__construct($grammar);
    }

    /**
     * Get the type of Header that this instance represents.
     *
     * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
     * @see TYPE_DATE, TYPE_ID, TYPE_PATH
     *
     * @return int
     */
    public function getFieldType()
    {
        return self::TYPE_DATE;
    }

    /**
     * Set the model for the field body.
     *
     * This method takes a UNIX timestamp.
     *
     * @param int $model
     */
    public function setFieldBodyModel($model)
    {
        $this->setTimestamp($model);
    }

    /**
     * Get the model for the field body.
     *
     * This method returns a UNIX timestamp.
     *
     * @return mixed
     */
    public function getFieldBodyModel()
    {
        return $this->getTimestamp();
    }

    /**
     * Get the UNIX timestamp of the Date in this Header.
     *
     * @return int
     */
    public function getTimestamp()
    {
        return $this->_timestamp;
    }

    /**
     * Set the UNIX timestamp of the Date in this Header.
     *
     * @param int $timestamp
     */
    public function setTimestamp($timestamp)
    {
        if (!is_null($timestamp)) {
            $timestamp = (int) $timestamp;
        }
        $this->clearCachedValueIf($this->_timestamp != $timestamp);
        $this->_timestamp = $timestamp;
    }

    /**
     * Get the string value of the body in this Header.
     *
     * This is not necessarily RFC 2822 compliant since folding white space will
     * not be added at this stage (see {@link toString()} for that).
     *
     * @see toString()
     *
     * @return string
     */
    public function getFieldBody()
    {
        if (!$this->getCachedValue()) {
            if (isset($this->_timestamp)) {
                $this->setCachedValue(date('r', $this->_timestamp));
            }
        }

        return $this->getCachedValue();
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An ID MIME Header for something like Message-ID or Content-ID.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_Headers_IdentificationHeader extends Swift_Mime_Headers_AbstractHeader
{
    /**
     * The IDs used in the value of this Header.
     *
     * This may hold multiple IDs or just a single ID.
     *
     * @var string[]
     */
    private $_ids = array();

    /**
     * Creates a new IdentificationHeader with the given $name and $id.
     *
     * @param string             $name
     * @param Swift_Mime_Grammar $grammar
     */
    public function __construct($name, Swift_Mime_Grammar $grammar)
    {
        $this->setFieldName($name);
        parent::__construct($grammar);
    }

    /**
     * Get the type of Header that this instance represents.
     *
     * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
     * @see TYPE_DATE, TYPE_ID, TYPE_PATH
     *
     * @return int
     */
    public function getFieldType()
    {
        return self::TYPE_ID;
    }

    /**
     * Set the model for the field body.
     *
     * This method takes a string ID, or an array of IDs.
     *
     * @param mixed $model
     *
     * @throws Swift_RfcComplianceException
     */
    public function setFieldBodyModel($model)
    {
        $this->setId($model);
    }

    /**
     * Get the model for the field body.
     *
     * This method returns an array of IDs
     *
     * @return array
     */
    public function getFieldBodyModel()
    {
        return $this->getIds();
    }

    /**
     * Set the ID used in the value of this header.
     *
     * @param string|array $id
     *
     * @throws Swift_RfcComplianceException
     */
    public function setId($id)
    {
        $this->setIds(is_array($id) ? $id : array($id));
    }

    /**
     * Get the ID used in the value of this Header.
     *
     * If multiple IDs are set only the first is returned.
     *
     * @return string
     */
    public function getId()
    {
        if (count($this->_ids) > 0) {
            return $this->_ids[0];
        }
    }

    /**
     * Set a collection of IDs to use in the value of this Header.
     *
     * @param string[] $ids
     *
     * @throws Swift_RfcComplianceException
     */
    public function setIds(array $ids)
    {
        $actualIds = array();

        foreach ($ids as $id) {
            $this->_assertValidId($id);
            $actualIds[] = $id;
        }

        $this->clearCachedValueIf($this->_ids != $actualIds);
        $this->_ids = $actualIds;
    }

    /**
     * Get the list of IDs used in this Header.
     *
     * @return string[]
     */
    public function getIds()
    {
        return $this->_ids;
    }

    /**
     * Get the string value of the body in this Header.
     *
     * This is not necessarily RFC 2822 compliant since folding white space will
     * not be added at this stage (see {@see toString()} for that).
     *
     * @see toString()
     *
     * @throws Swift_RfcComplianceException
     *
     * @return string
     */
    public function getFieldBody()
    {
        if (!$this->getCachedValue()) {
            $angleAddrs = array();

            foreach ($this->_ids as $id) {
                $angleAddrs[] = '<'.$id.'>';
            }

            $this->setCachedValue(implode(' ', $angleAddrs));
        }

        return $this->getCachedValue();
    }

    /**
     * Throws an Exception if the id passed does not comply with RFC 2822.
     *
     * @param string $id
     *
     * @throws Swift_RfcComplianceException
     */
    private function _assertValidId($id)
    {
        if (!preg_match(
            '/^'.$this->getGrammar()->getDefinition('id-left').'@'.
            $this->getGrammar()->getDefinition('id-right').'$/D',
            $id
            )) {
            throw new Swift_RfcComplianceException(
                'Invalid ID given <'.$id.'>'
                );
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A Mailbox Address MIME Header for something like From or Sender.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_Headers_MailboxHeader extends Swift_Mime_Headers_AbstractHeader
{
    /**
     * The mailboxes used in this Header.
     *
     * @var string[]
     */
    private $_mailboxes = array();

    /**
     * Creates a new MailboxHeader with $name.
     *
     * @param string                   $name    of Header
     * @param Swift_Mime_HeaderEncoder $encoder
     * @param Swift_Mime_Grammar       $grammar
     */
    public function __construct($name, Swift_Mime_HeaderEncoder $encoder, Swift_Mime_Grammar $grammar)
    {
        $this->setFieldName($name);
        $this->setEncoder($encoder);
        parent::__construct($grammar);
    }

    /**
     * Get the type of Header that this instance represents.
     *
     * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
     * @see TYPE_DATE, TYPE_ID, TYPE_PATH
     *
     * @return int
     */
    public function getFieldType()
    {
        return self::TYPE_MAILBOX;
    }

    /**
     * Set the model for the field body.
     *
     * This method takes a string, or an array of addresses.
     *
     * @param mixed $model
     *
     * @throws Swift_RfcComplianceException
     */
    public function setFieldBodyModel($model)
    {
        $this->setNameAddresses($model);
    }

    /**
     * Get the model for the field body.
     *
     * This method returns an associative array like {@link getNameAddresses()}
     *
     * @throws Swift_RfcComplianceException
     *
     * @return array
     */
    public function getFieldBodyModel()
    {
        return $this->getNameAddresses();
    }

    /**
     * Set a list of mailboxes to be shown in this Header.
     *
     * The mailboxes can be a simple array of addresses, or an array of
     * key=>value pairs where (email => personalName).
     * Example:
     * <code>
     * <?php
     * //Sets two mailboxes in the Header, one with a personal name
     * $header->setNameAddresses(array(
     *  'chris@swiftmailer.org' => 'Chris Corbyn',
     *  'mark@swiftmailer.org' //No associated personal name
     *  ));
     * ?>
     * </code>
     *
     * @see __construct()
     * @see setAddresses()
     * @see setValue()
     *
     * @param string|string[] $mailboxes
     *
     * @throws Swift_RfcComplianceException
     */
    public function setNameAddresses($mailboxes)
    {
        $this->_mailboxes = $this->normalizeMailboxes((array) $mailboxes);
        $this->setCachedValue(null); //Clear any cached value
    }

    /**
     * Get the full mailbox list of this Header as an array of valid RFC 2822 strings.
     *
     * Example:
     * <code>
     * <?php
     * $header = new Swift_Mime_Headers_MailboxHeader('From',
     *  array('chris@swiftmailer.org' => 'Chris Corbyn',
     *  'mark@swiftmailer.org' => 'Mark Corbyn')
     *  );
     * print_r($header->getNameAddressStrings());
     * // array (
     * // 0 => Chris Corbyn <chris@swiftmailer.org>,
     * // 1 => Mark Corbyn <mark@swiftmailer.org>
     * // )
     * ?>
     * </code>
     *
     * @see getNameAddresses()
     * @see toString()
     *
     * @throws Swift_RfcComplianceException
     *
     * @return string[]
     */
    public function getNameAddressStrings()
    {
        return $this->_createNameAddressStrings($this->getNameAddresses());
    }

    /**
     * Get all mailboxes in this Header as key=>value pairs.
     *
     * The key is the address and the value is the name (or null if none set).
     * Example:
     * <code>
     * <?php
     * $header = new Swift_Mime_Headers_MailboxHeader('From',
     *  array('chris@swiftmailer.org' => 'Chris Corbyn',
     *  'mark@swiftmailer.org' => 'Mark Corbyn')
     *  );
     * print_r($header->getNameAddresses());
     * // array (
     * // chris@swiftmailer.org => Chris Corbyn,
     * // mark@swiftmailer.org => Mark Corbyn
     * // )
     * ?>
     * </code>
     *
     * @see getAddresses()
     * @see getNameAddressStrings()
     *
     * @return string[]
     */
    public function getNameAddresses()
    {
        return $this->_mailboxes;
    }

    /**
     * Makes this Header represent a list of plain email addresses with no names.
     *
     * Example:
     * <code>
     * <?php
     * //Sets three email addresses as the Header data
     * $header->setAddresses(
     *  array('one@domain.tld', 'two@domain.tld', 'three@domain.tld')
     *  );
     * ?>
     * </code>
     *
     * @see setNameAddresses()
     * @see setValue()
     *
     * @param string[] $addresses
     *
     * @throws Swift_RfcComplianceException
     */
    public function setAddresses($addresses)
    {
        $this->setNameAddresses(array_values((array) $addresses));
    }

    /**
     * Get all email addresses in this Header.
     *
     * @see getNameAddresses()
     *
     * @return string[]
     */
    public function getAddresses()
    {
        return array_keys($this->_mailboxes);
    }

    /**
     * Remove one or more addresses from this Header.
     *
     * @param string|string[] $addresses
     */
    public function removeAddresses($addresses)
    {
        $this->setCachedValue(null);
        foreach ((array) $addresses as $address) {
            unset($this->_mailboxes[$address]);
        }
    }

    /**
     * Get the string value of the body in this Header.
     *
     * This is not necessarily RFC 2822 compliant since folding white space will
     * not be added at this stage (see {@link toString()} for that).
     *
     * @see toString()
     *
     * @throws Swift_RfcComplianceException
     *
     * @return string
     */
    public function getFieldBody()
    {
        // Compute the string value of the header only if needed
        if (is_null($this->getCachedValue())) {
            $this->setCachedValue($this->createMailboxListString($this->_mailboxes));
        }

        return $this->getCachedValue();
    }

    // -- Points of extension

    /**
     * Normalizes a user-input list of mailboxes into consistent key=>value pairs.
     *
     * @param string[] $mailboxes
     *
     * @return string[]
     */
    protected function normalizeMailboxes(array $mailboxes)
    {
        $actualMailboxes = array();

        foreach ($mailboxes as $key => $value) {
            if (is_string($key)) {
                //key is email addr
                $address = $key;
                $name = $value;
            } else {
                $address = $value;
                $name = null;
            }
            $this->_assertValidAddress($address);
            $actualMailboxes[$address] = $name;
        }

        return $actualMailboxes;
    }

    /**
     * Produces a compliant, formatted display-name based on the string given.
     *
     * @param string $displayName as displayed
     * @param bool   $shorten     the first line to make remove for header name
     *
     * @return string
     */
    protected function createDisplayNameString($displayName, $shorten = false)
    {
        return $this->createPhrase($this, $displayName, $this->getCharset(), $this->getEncoder(), $shorten);
    }

    /**
     * Creates a string form of all the mailboxes in the passed array.
     *
     * @param string[] $mailboxes
     *
     * @throws Swift_RfcComplianceException
     *
     * @return string
     */
    protected function createMailboxListString(array $mailboxes)
    {
        return implode(', ', $this->_createNameAddressStrings($mailboxes));
    }

    /**
     * Redefine the encoding requirements for mailboxes.
     *
     * All "specials" must be encoded as the full header value will not be quoted
     *
     * @see RFC 2822 3.2.1
     *
     * @param string $token
     *
     * @return bool
     */
    protected function tokenNeedsEncoding($token)
    {
        return preg_match('/[()<>\[\]:;@\,."]/', $token) || parent::tokenNeedsEncoding($token);
    }

    /**
     * Return an array of strings conforming the the name-addr spec of RFC 2822.
     *
     * @param string[] $mailboxes
     *
     * @return string[]
     */
    private function _createNameAddressStrings(array $mailboxes)
    {
        $strings = array();

        foreach ($mailboxes as $email => $name) {
            $mailboxStr = $email;
            if (!is_null($name)) {
                $nameStr = $this->createDisplayNameString($name, empty($strings));
                $mailboxStr = $nameStr.' <'.$mailboxStr.'>';
            }
            $strings[] = $mailboxStr;
        }

        return $strings;
    }

    /**
     * Throws an Exception if the address passed does not comply with RFC 2822.
     *
     * @param string $address
     *
     * @throws Swift_RfcComplianceException If invalid.
     */
    private function _assertValidAddress($address)
    {
        if (!preg_match('/^'.$this->getGrammar()->getDefinition('addr-spec').'$/D',
            $address)) {
            throw new Swift_RfcComplianceException(
                'Address in mailbox given ['.$address.
                '] does not comply with RFC 2822, 3.6.2.'
                );
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An OpenDKIM Specific Header using only raw header datas without encoding.
 *
 * @author De Cock Xavier <xdecock@gmail.com>
 */
class Swift_Mime_Headers_OpenDKIMHeader implements Swift_Mime_Header
{
    /**
     * The value of this Header.
     *
     * @var string
     */
    private $_value;

    /**
     * The name of this Header.
     *
     * @var string
     */
    private $_fieldName;

    /**
     * @param string $name
     */
    public function __construct($name)
    {
        $this->_fieldName = $name;
    }

    /**
     * Get the type of Header that this instance represents.
     *
     * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
     * @see TYPE_DATE, TYPE_ID, TYPE_PATH
     *
     * @return int
     */
    public function getFieldType()
    {
        return self::TYPE_TEXT;
    }

    /**
     * Set the model for the field body.
     *
     * This method takes a string for the field value.
     *
     * @param string $model
     */
    public function setFieldBodyModel($model)
    {
        $this->setValue($model);
    }

    /**
     * Get the model for the field body.
     *
     * This method returns a string.
     *
     * @return string
     */
    public function getFieldBodyModel()
    {
        return $this->getValue();
    }

    /**
     * Get the (unencoded) value of this header.
     *
     * @return string
     */
    public function getValue()
    {
        return $this->_value;
    }

    /**
     * Set the (unencoded) value of this header.
     *
     * @param string $value
     */
    public function setValue($value)
    {
        $this->_value = $value;
    }

    /**
     * Get the value of this header prepared for rendering.
     *
     * @return string
     */
    public function getFieldBody()
    {
        return $this->_value;
    }

    /**
     * Get this Header rendered as a RFC 2822 compliant string.
     *
     * @return string
     */
    public function toString()
    {
        return $this->_fieldName.': '.$this->_value;
    }

    /**
     * Set the Header FieldName.
     *
     * @see Swift_Mime_Header::getFieldName()
     */
    public function getFieldName()
    {
        return $this->_fieldName;
    }

    /**
     * Ignored.
     */
    public function setCharset($charset)
    {
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An abstract base MIME Header.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_Headers_ParameterizedHeader extends Swift_Mime_Headers_UnstructuredHeader implements Swift_Mime_ParameterizedHeader
{
    /**
     * RFC 2231's definition of a token.
     *
     * @var string
     */
    const TOKEN_REGEX = '(?:[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+)';

    /**
     * The Encoder used to encode the parameters.
     *
     * @var Swift_Encoder
     */
    private $_paramEncoder;

    /**
     * The parameters as an associative array.
     *
     * @var string[]
     */
    private $_params = array();

    /**
     * Creates a new ParameterizedHeader with $name.
     *
     * @param string                   $name
     * @param Swift_Mime_HeaderEncoder $encoder
     * @param Swift_Encoder            $paramEncoder, optional
     * @param Swift_Mime_Grammar       $grammar
     */
    public function __construct($name, Swift_Mime_HeaderEncoder $encoder, Swift_Encoder $paramEncoder = null, Swift_Mime_Grammar $grammar)
    {
        parent::__construct($name, $encoder, $grammar);
        $this->_paramEncoder = $paramEncoder;
    }

    /**
     * Get the type of Header that this instance represents.
     *
     * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
     * @see TYPE_DATE, TYPE_ID, TYPE_PATH
     *
     * @return int
     */
    public function getFieldType()
    {
        return self::TYPE_PARAMETERIZED;
    }

    /**
     * Set the character set used in this Header.
     *
     * @param string $charset
     */
    public function setCharset($charset)
    {
        parent::setCharset($charset);
        if (isset($this->_paramEncoder)) {
            $this->_paramEncoder->charsetChanged($charset);
        }
    }

    /**
     * Set the value of $parameter.
     *
     * @param string $parameter
     * @param string $value
     */
    public function setParameter($parameter, $value)
    {
        $this->setParameters(array_merge($this->getParameters(), array($parameter => $value)));
    }

    /**
     * Get the value of $parameter.
     *
     * @param string $parameter
     *
     * @return string
     */
    public function getParameter($parameter)
    {
        $params = $this->getParameters();

        return array_key_exists($parameter, $params) ? $params[$parameter] : null;
    }

    /**
     * Set an associative array of parameter names mapped to values.
     *
     * @param string[] $parameters
     */
    public function setParameters(array $parameters)
    {
        $this->clearCachedValueIf($this->_params != $parameters);
        $this->_params = $parameters;
    }

    /**
     * Returns an associative array of parameter names mapped to values.
     *
     * @return string[]
     */
    public function getParameters()
    {
        return $this->_params;
    }

    /**
     * Get the value of this header prepared for rendering.
     *
     * @return string
     */
    public function getFieldBody() //TODO: Check caching here
    {
        $body = parent::getFieldBody();
        foreach ($this->_params as $name => $value) {
            if (!is_null($value)) {
                // Add the parameter
                $body .= '; '.$this->_createParameter($name, $value);
            }
        }

        return $body;
    }

    /**
     * Generate a list of all tokens in the final header.
     *
     * This doesn't need to be overridden in theory, but it is for implementation
     * reasons to prevent potential breakage of attributes.
     *
     * @param string $string The string to tokenize
     *
     * @return array An array of tokens as strings
     */
    protected function toTokens($string = null)
    {
        $tokens = parent::toTokens(parent::getFieldBody());

        // Try creating any parameters
        foreach ($this->_params as $name => $value) {
            if (!is_null($value)) {
                // Add the semi-colon separator
                $tokens[count($tokens) - 1] .= ';';
                $tokens = array_merge($tokens, $this->generateTokenLines(
                    ' '.$this->_createParameter($name, $value)
                    ));
            }
        }

        return $tokens;
    }

    /**
     * Render a RFC 2047 compliant header parameter from the $name and $value.
     *
     * @param string $name
     * @param string $value
     *
     * @return string
     */
    private function _createParameter($name, $value)
    {
        $origValue = $value;

        $encoded = false;
        // Allow room for parameter name, indices, "=" and DQUOTEs
        $maxValueLength = $this->getMaxLineLength() - strlen($name.'=*N"";') - 1;
        $firstLineOffset = 0;

        // If it's not already a valid parameter value...
        if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) {
            // TODO: text, or something else??
            // ... and it's not ascii
            if (!preg_match('/^'.$this->getGrammar()->getDefinition('text').'*$/D', $value)) {
                $encoded = true;
                // Allow space for the indices, charset and language
                $maxValueLength = $this->getMaxLineLength() - strlen($name.'*N*="";') - 1;
                $firstLineOffset = strlen(
                    $this->getCharset()."'".$this->getLanguage()."'"
                    );
            }
        }

        // Encode if we need to
        if ($encoded || strlen($value) > $maxValueLength) {
            if (isset($this->_paramEncoder)) {
                $value = $this->_paramEncoder->encodeString(
                    $origValue, $firstLineOffset, $maxValueLength, $this->getCharset()
                    );
            } else {
                // We have to go against RFC 2183/2231 in some areas for interoperability
                $value = $this->getTokenAsEncodedWord($origValue);
                $encoded = false;
            }
        }

        $valueLines = isset($this->_paramEncoder) ? explode("\r\n", $value) : array($value);

        // Need to add indices
        if (count($valueLines) > 1) {
            $paramLines = array();
            foreach ($valueLines as $i => $line) {
                $paramLines[] = $name.'*'.$i.
                    $this->_getEndOfParameterValue($line, true, $i == 0);
            }

            return implode(";\r\n ", $paramLines);
        } else {
            return $name.$this->_getEndOfParameterValue(
                $valueLines[0], $encoded, true
                );
        }
    }

    /**
     * Returns the parameter value from the "=" and beyond.
     *
     * @param string $value     to append
     * @param bool   $encoded
     * @param bool   $firstLine
     *
     * @return string
     */
    private function _getEndOfParameterValue($value, $encoded = false, $firstLine = false)
    {
        if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) {
            $value = '"'.$value.'"';
        }
        $prepend = '=';
        if ($encoded) {
            $prepend = '*=';
            if ($firstLine) {
                $prepend = '*='.$this->getCharset()."'".$this->getLanguage().
                    "'";
            }
        }

        return $prepend.$value;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A Path Header in Swift Mailer, such a Return-Path.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_Headers_PathHeader extends Swift_Mime_Headers_AbstractHeader
{
    /**
     * The address in this Header (if specified).
     *
     * @var string
     */
    private $_address;

    /**
     * Creates a new PathHeader with the given $name.
     *
     * @param string             $name
     * @param Swift_Mime_Grammar $grammar
     */
    public function __construct($name, Swift_Mime_Grammar $grammar)
    {
        $this->setFieldName($name);
        parent::__construct($grammar);
    }

    /**
     * Get the type of Header that this instance represents.
     *
     * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
     * @see TYPE_DATE, TYPE_ID, TYPE_PATH
     *
     * @return int
     */
    public function getFieldType()
    {
        return self::TYPE_PATH;
    }

    /**
     * Set the model for the field body.
     * This method takes a string for an address.
     *
     * @param string $model
     *
     * @throws Swift_RfcComplianceException
     */
    public function setFieldBodyModel($model)
    {
        $this->setAddress($model);
    }

    /**
     * Get the model for the field body.
     * This method returns a string email address.
     *
     * @return mixed
     */
    public function getFieldBodyModel()
    {
        return $this->getAddress();
    }

    /**
     * Set the Address which should appear in this Header.
     *
     * @param string $address
     *
     * @throws Swift_RfcComplianceException
     */
    public function setAddress($address)
    {
        if (is_null($address)) {
            $this->_address = null;
        } elseif ('' == $address) {
            $this->_address = '';
        } else {
            $this->_assertValidAddress($address);
            $this->_address = $address;
        }
        $this->setCachedValue(null);
    }

    /**
     * Get the address which is used in this Header (if any).
     *
     * Null is returned if no address is set.
     *
     * @return string
     */
    public function getAddress()
    {
        return $this->_address;
    }

    /**
     * Get the string value of the body in this Header.
     *
     * This is not necessarily RFC 2822 compliant since folding white space will
     * not be added at this stage (see {@link toString()} for that).
     *
     * @see toString()
     *
     * @return string
     */
    public function getFieldBody()
    {
        if (!$this->getCachedValue()) {
            if (isset($this->_address)) {
                $this->setCachedValue('<'.$this->_address.'>');
            }
        }

        return $this->getCachedValue();
    }

    /**
     * Throws an Exception if the address passed does not comply with RFC 2822.
     *
     * @param string $address
     *
     * @throws Swift_RfcComplianceException If address is invalid
     */
    private function _assertValidAddress($address)
    {
        if (!preg_match('/^'.$this->getGrammar()->getDefinition('addr-spec').'$/D',
            $address)) {
            throw new Swift_RfcComplianceException(
                'Address set in PathHeader does not comply with addr-spec of RFC 2822.'
                );
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A Simple MIME Header.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_Headers_UnstructuredHeader extends Swift_Mime_Headers_AbstractHeader
{
    /**
     * The value of this Header.
     *
     * @var string
     */
    private $_value;

    /**
     * Creates a new SimpleHeader with $name.
     *
     * @param string                   $name
     * @param Swift_Mime_HeaderEncoder $encoder
     * @param Swift_Mime_Grammar       $grammar
     */
    public function __construct($name, Swift_Mime_HeaderEncoder $encoder, Swift_Mime_Grammar $grammar)
    {
        $this->setFieldName($name);
        $this->setEncoder($encoder);
        parent::__construct($grammar);
    }

    /**
     * Get the type of Header that this instance represents.
     *
     * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
     * @see TYPE_DATE, TYPE_ID, TYPE_PATH
     *
     * @return int
     */
    public function getFieldType()
    {
        return self::TYPE_TEXT;
    }

    /**
     * Set the model for the field body.
     *
     * This method takes a string for the field value.
     *
     * @param string $model
     */
    public function setFieldBodyModel($model)
    {
        $this->setValue($model);
    }

    /**
     * Get the model for the field body.
     *
     * This method returns a string.
     *
     * @return string
     */
    public function getFieldBodyModel()
    {
        return $this->getValue();
    }

    /**
     * Get the (unencoded) value of this header.
     *
     * @return string
     */
    public function getValue()
    {
        return $this->_value;
    }

    /**
     * Set the (unencoded) value of this header.
     *
     * @param string $value
     */
    public function setValue($value)
    {
        $this->clearCachedValueIf($this->_value != $value);
        $this->_value = $value;
    }

    /**
     * Get the value of this header prepared for rendering.
     *
     * @return string
     */
    public function getFieldBody()
    {
        if (!$this->getCachedValue()) {
            $this->setCachedValue(
                $this->encodeWords($this, $this->_value)
                );
        }

        return $this->getCachedValue();
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A collection of MIME headers.
 *
 * @author Chris Corbyn
 */
interface Swift_Mime_HeaderSet extends Swift_Mime_CharsetObserver
{
    /**
     * Add a new Mailbox Header with a list of $addresses.
     *
     * @param string       $name
     * @param array|string $addresses
     */
    public function addMailboxHeader($name, $addresses = null);

    /**
     * Add a new Date header using $timestamp (UNIX time).
     *
     * @param string $name
     * @param int    $timestamp
     */
    public function addDateHeader($name, $timestamp = null);

    /**
     * Add a new basic text header with $name and $value.
     *
     * @param string $name
     * @param string $value
     */
    public function addTextHeader($name, $value = null);

    /**
     * Add a new ParameterizedHeader with $name, $value and $params.
     *
     * @param string $name
     * @param string $value
     * @param array  $params
     */
    public function addParameterizedHeader($name, $value = null, $params = array());

    /**
     * Add a new ID header for Message-ID or Content-ID.
     *
     * @param string       $name
     * @param string|array $ids
     */
    public function addIdHeader($name, $ids = null);

    /**
     * Add a new Path header with an address (path) in it.
     *
     * @param string $name
     * @param string $path
     */
    public function addPathHeader($name, $path = null);

    /**
     * Returns true if at least one header with the given $name exists.
     *
     * If multiple headers match, the actual one may be specified by $index.
     *
     * @param string $name
     * @param int    $index
     *
     * @return bool
     */
    public function has($name, $index = 0);

    /**
     * Set a header in the HeaderSet.
     *
     * The header may be a previously fetched header via {@link get()} or it may
     * be one that has been created separately.
     *
     * If $index is specified, the header will be inserted into the set at this
     * offset.
     *
     * @param Swift_Mime_Header $header
     * @param int               $index
     */
    public function set(Swift_Mime_Header $header, $index = 0);

    /**
     * Get the header with the given $name.
     * If multiple headers match, the actual one may be specified by $index.
     * Returns NULL if none present.
     *
     * @param string $name
     * @param int    $index
     *
     * @return Swift_Mime_Header
     */
    public function get($name, $index = 0);

    /**
     * Get all headers with the given $name.
     *
     * @param string $name
     *
     * @return array
     */
    public function getAll($name = null);

    /**
     * Return the name of all Headers.
     *
     * @return array
     */
    public function listAll();

    /**
     * Remove the header with the given $name if it's set.
     *
     * If multiple headers match, the actual one may be specified by $index.
     *
     * @param string $name
     * @param int    $index
     */
    public function remove($name, $index = 0);

    /**
     * Remove all headers with the given $name.
     *
     * @param string $name
     */
    public function removeAll($name);

    /**
     * Create a new instance of this HeaderSet.
     *
     * @return Swift_Mime_HeaderSet
     */
    public function newInstance();

    /**
     * Define a list of Header names as an array in the correct order.
     *
     * These Headers will be output in the given order where present.
     *
     * @param array $sequence
     */
    public function defineOrdering(array $sequence);

    /**
     * Set a list of header names which must always be displayed when set.
     *
     * Usually headers without a field value won't be output unless set here.
     *
     * @param array $names
     */
    public function setAlwaysDisplayed(array $names);

    /**
     * Returns a string with a representation of all headers.
     *
     * @return string
     */
    public function toString();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A Message (RFC 2822) object.
 *
 * @author Chris Corbyn
 */
interface Swift_Mime_Message extends Swift_Mime_MimeEntity
{
    /**
     * Generates a valid Message-ID and switches to it.
     *
     * @return string
     */
    public function generateId();

    /**
     * Set the subject of the message.
     *
     * @param string $subject
     */
    public function setSubject($subject);

    /**
     * Get the subject of the message.
     *
     * @return string
     */
    public function getSubject();

    /**
     * Set the origination date of the message as a UNIX timestamp.
     *
     * @param int $date
     */
    public function setDate($date);

    /**
     * Get the origination date of the message as a UNIX timestamp.
     *
     * @return int
     */
    public function getDate();

    /**
     * Set the return-path (bounce-detect) address.
     *
     * @param string $address
     */
    public function setReturnPath($address);

    /**
     * Get the return-path (bounce-detect) address.
     *
     * @return string
     */
    public function getReturnPath();

    /**
     * Set the sender of this message.
     *
     * If multiple addresses are present in the From field, this SHOULD be set.
     *
     * According to RFC 2822 it is a requirement when there are multiple From
     * addresses, but Swift itself does not require it directly.
     *
     * An associative array (with one element!) can be used to provide a display-
     * name: i.e. array('email@address' => 'Real Name').
     *
     * If the second parameter is provided and the first is a string, then $name
     * is associated with the address.
     *
     * @param mixed  $address
     * @param string $name    optional
     */
    public function setSender($address, $name = null);

    /**
     * Get the sender address for this message.
     *
     * This has a higher significance than the From address.
     *
     * @return string
     */
    public function getSender();

    /**
     * Set the From address of this message.
     *
     * It is permissible for multiple From addresses to be set using an array.
     *
     * If multiple From addresses are used, you SHOULD set the Sender address and
     * according to RFC 2822, MUST set the sender address.
     *
     * An array can be used if display names are to be provided: i.e.
     * array('email@address.com' => 'Real Name').
     *
     * If the second parameter is provided and the first is a string, then $name
     * is associated with the address.
     *
     * @param mixed  $addresses
     * @param string $name      optional
     */
    public function setFrom($addresses, $name = null);

    /**
     * Get the From address(es) of this message.
     *
     * This method always returns an associative array where the keys are the
     * addresses.
     *
     * @return string[]
     */
    public function getFrom();

    /**
     * Set the Reply-To address(es).
     *
     * Any replies from the receiver will be sent to this address.
     *
     * It is permissible for multiple reply-to addresses to be set using an array.
     *
     * This method has the same synopsis as {@link setFrom()} and {@link setTo()}.
     *
     * If the second parameter is provided and the first is a string, then $name
     * is associated with the address.
     *
     * @param mixed  $addresses
     * @param string $name      optional
     */
    public function setReplyTo($addresses, $name = null);

    /**
     * Get the Reply-To addresses for this message.
     *
     * This method always returns an associative array where the keys provide the
     * email addresses.
     *
     * @return string[]
     */
    public function getReplyTo();

    /**
     * Set the To address(es).
     *
     * Recipients set in this field will receive a copy of this message.
     *
     * This method has the same synopsis as {@link setFrom()} and {@link setCc()}.
     *
     * If the second parameter is provided and the first is a string, then $name
     * is associated with the address.
     *
     * @param mixed  $addresses
     * @param string $name      optional
     */
    public function setTo($addresses, $name = null);

    /**
     * Get the To addresses for this message.
     *
     * This method always returns an associative array, whereby the keys provide
     * the actual email addresses.
     *
     * @return string[]
     */
    public function getTo();

    /**
     * Set the Cc address(es).
     *
     * Recipients set in this field will receive a 'carbon-copy' of this message.
     *
     * This method has the same synopsis as {@link setFrom()} and {@link setTo()}.
     *
     * @param mixed  $addresses
     * @param string $name      optional
     */
    public function setCc($addresses, $name = null);

    /**
     * Get the Cc addresses for this message.
     *
     * This method always returns an associative array, whereby the keys provide
     * the actual email addresses.
     *
     * @return string[]
     */
    public function getCc();

    /**
     * Set the Bcc address(es).
     *
     * Recipients set in this field will receive a 'blind-carbon-copy' of this
     * message.
     *
     * In other words, they will get the message, but any other recipients of the
     * message will have no such knowledge of their receipt of it.
     *
     * This method has the same synopsis as {@link setFrom()} and {@link setTo()}.
     *
     * @param mixed  $addresses
     * @param string $name      optional
     */
    public function setBcc($addresses, $name = null);

    /**
     * Get the Bcc addresses for this message.
     *
     * This method always returns an associative array, whereby the keys provide
     * the actual email addresses.
     *
     * @return string[]
     */
    public function getBcc();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A MIME entity, such as an attachment.
 *
 * @author Chris Corbyn
 */
interface Swift_Mime_MimeEntity extends Swift_Mime_CharsetObserver, Swift_Mime_EncodingObserver
{
    /** Main message document; there can only be one of these */
    const LEVEL_TOP = 16;

    /** An entity which nests with the same precedence as an attachment */
    const LEVEL_MIXED = 256;

    /** An entity which nests with the same precedence as a mime part */
    const LEVEL_ALTERNATIVE = 4096;

    /** An entity which nests with the same precedence as embedded content */
    const LEVEL_RELATED = 65536;

    /**
     * Get the level at which this entity shall be nested in final document.
     *
     * The lower the value, the more outermost the entity will be nested.
     *
     * @see LEVEL_TOP, LEVEL_MIXED, LEVEL_RELATED, LEVEL_ALTERNATIVE
     *
     * @return int
     */
    public function getNestingLevel();

    /**
     * Get the qualified content-type of this mime entity.
     *
     * @return string
     */
    public function getContentType();

    /**
     * Returns a unique ID for this entity.
     *
     * For most entities this will likely be the Content-ID, though it has
     * no explicit semantic meaning and can be considered an identifier for
     * programming logic purposes.
     *
     * If a Content-ID header is present, this value SHOULD match the value of
     * the header.
     *
     * @return string
     */
    public function getId();

    /**
     * Get all children nested inside this entity.
     *
     * These are not just the immediate children, but all children.
     *
     * @return Swift_Mime_MimeEntity[]
     */
    public function getChildren();

    /**
     * Set all children nested inside this entity.
     *
     * This includes grandchildren.
     *
     * @param Swift_Mime_MimeEntity[] $children
     */
    public function setChildren(array $children);

    /**
     * Get the collection of Headers in this Mime entity.
     *
     * @return Swift_Mime_HeaderSet
     */
    public function getHeaders();

    /**
     * Get the body content of this entity as a string.
     *
     * Returns NULL if no body has been set.
     *
     * @return string|null
     */
    public function getBody();

    /**
     * Set the body content of this entity as a string.
     *
     * @param string $body
     * @param string $contentType optional
     */
    public function setBody($body, $contentType = null);

    /**
     * Get this entire entity in its string form.
     *
     * @return string
     */
    public function toString();

    /**
     * Get this entire entity as a ByteStream.
     *
     * @param Swift_InputByteStream $is to write to
     */
    public function toByteStream(Swift_InputByteStream $is);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A MIME part, in a multipart message.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_MimePart extends Swift_Mime_SimpleMimeEntity
{
    /** The format parameter last specified by the user */
    protected $_userFormat;

    /** The charset last specified by the user */
    protected $_userCharset;

    /** The delsp parameter last specified by the user */
    protected $_userDelSp;

    /** The nesting level of this MimePart */
    private $_nestingLevel = self::LEVEL_ALTERNATIVE;

    /**
     * Create a new MimePart with $headers, $encoder and $cache.
     *
     * @param Swift_Mime_HeaderSet      $headers
     * @param Swift_Mime_ContentEncoder $encoder
     * @param Swift_KeyCache            $cache
     * @param Swift_Mime_Grammar        $grammar
     * @param string                    $charset
     */
    public function __construct(Swift_Mime_HeaderSet $headers, Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache, Swift_Mime_Grammar $grammar, $charset = null)
    {
        parent::__construct($headers, $encoder, $cache, $grammar);
        $this->setContentType('text/plain');
        if (!is_null($charset)) {
            $this->setCharset($charset);
        }
    }

    /**
     * Set the body of this entity, either as a string, or as an instance of
     * {@link Swift_OutputByteStream}.
     *
     * @param mixed  $body
     * @param string $contentType optional
     * @param string $charset     optional
     *
     * @return Swift_Mime_MimePart
     */
    public function setBody($body, $contentType = null, $charset = null)
    {
        if (isset($charset)) {
            $this->setCharset($charset);
        }
        $body = $this->_convertString($body);

        parent::setBody($body, $contentType);

        return $this;
    }

    /**
     * Get the character set of this entity.
     *
     * @return string
     */
    public function getCharset()
    {
        return $this->_getHeaderParameter('Content-Type', 'charset');
    }

    /**
     * Set the character set of this entity.
     *
     * @param string $charset
     *
     * @return Swift_Mime_MimePart
     */
    public function setCharset($charset)
    {
        $this->_setHeaderParameter('Content-Type', 'charset', $charset);
        if ($charset !== $this->_userCharset) {
            $this->_clearCache();
        }
        $this->_userCharset = $charset;
        parent::charsetChanged($charset);

        return $this;
    }

    /**
     * Get the format of this entity (i.e. flowed or fixed).
     *
     * @return string
     */
    public function getFormat()
    {
        return $this->_getHeaderParameter('Content-Type', 'format');
    }

    /**
     * Set the format of this entity (flowed or fixed).
     *
     * @param string $format
     *
     * @return Swift_Mime_MimePart
     */
    public function setFormat($format)
    {
        $this->_setHeaderParameter('Content-Type', 'format', $format);
        $this->_userFormat = $format;

        return $this;
    }

    /**
     * Test if delsp is being used for this entity.
     *
     * @return bool
     */
    public function getDelSp()
    {
        return 'yes' == $this->_getHeaderParameter('Content-Type', 'delsp') ? true : false;
    }

    /**
     * Turn delsp on or off for this entity.
     *
     * @param bool $delsp
     *
     * @return Swift_Mime_MimePart
     */
    public function setDelSp($delsp = true)
    {
        $this->_setHeaderParameter('Content-Type', 'delsp', $delsp ? 'yes' : null);
        $this->_userDelSp = $delsp;

        return $this;
    }

    /**
     * Get the nesting level of this entity.
     *
     * @see LEVEL_TOP, LEVEL_ALTERNATIVE, LEVEL_MIXED, LEVEL_RELATED
     *
     * @return int
     */
    public function getNestingLevel()
    {
        return $this->_nestingLevel;
    }

    /**
     * Receive notification that the charset has changed on this document, or a
     * parent document.
     *
     * @param string $charset
     */
    public function charsetChanged($charset)
    {
        $this->setCharset($charset);
    }

    /** Fix the content-type and encoding of this entity */
    protected function _fixHeaders()
    {
        parent::_fixHeaders();
        if (count($this->getChildren())) {
            $this->_setHeaderParameter('Content-Type', 'charset', null);
            $this->_setHeaderParameter('Content-Type', 'format', null);
            $this->_setHeaderParameter('Content-Type', 'delsp', null);
        } else {
            $this->setCharset($this->_userCharset);
            $this->setFormat($this->_userFormat);
            $this->setDelSp($this->_userDelSp);
        }
    }

    /** Set the nesting level of this entity */
    protected function _setNestingLevel($level)
    {
        $this->_nestingLevel = $level;
    }

    /** Encode charset when charset is not utf-8 */
    protected function _convertString($string)
    {
        $charset = strtolower($this->getCharset());
        if (!in_array($charset, array('utf-8', 'iso-8859-1', 'iso-8859-15', ''))) {
            // mb_convert_encoding must be the first one to check, since iconv cannot convert some words.
            if (function_exists('mb_convert_encoding')) {
                $string = mb_convert_encoding($string, $charset, 'utf-8');
            } elseif (function_exists('iconv')) {
                $string = iconv('utf-8//TRANSLIT//IGNORE', $charset, $string);
            } else {
                throw new Swift_SwiftException('No suitable convert encoding function (use UTF-8 as your charset or install the mbstring or iconv extension).');
            }

            return $string;
        }

        return $string;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A MIME Header with parameters.
 *
 * @author Chris Corbyn
 */
interface Swift_Mime_ParameterizedHeader extends Swift_Mime_Header
{
    /**
     * Set the value of $parameter.
     *
     * @param string $parameter
     * @param string $value
     */
    public function setParameter($parameter, $value);

    /**
     * Get the value of $parameter.
     *
     * @param string $parameter
     *
     * @return string
     */
    public function getParameter($parameter);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Creates MIME headers.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_SimpleHeaderFactory implements Swift_Mime_HeaderFactory
{
    /** The HeaderEncoder used by these headers */
    private $_encoder;

    /** The Encoder used by parameters */
    private $_paramEncoder;

    /** The Grammar */
    private $_grammar;

    /** The charset of created Headers */
    private $_charset;

    /**
     * Creates a new SimpleHeaderFactory using $encoder and $paramEncoder.
     *
     * @param Swift_Mime_HeaderEncoder $encoder
     * @param Swift_Encoder            $paramEncoder
     * @param Swift_Mime_Grammar       $grammar
     * @param string|null              $charset
     */
    public function __construct(Swift_Mime_HeaderEncoder $encoder, Swift_Encoder $paramEncoder, Swift_Mime_Grammar $grammar, $charset = null)
    {
        $this->_encoder = $encoder;
        $this->_paramEncoder = $paramEncoder;
        $this->_grammar = $grammar;
        $this->_charset = $charset;
    }

    /**
     * Create a new Mailbox Header with a list of $addresses.
     *
     * @param string            $name
     * @param array|string|null $addresses
     *
     * @return Swift_Mime_Header
     */
    public function createMailboxHeader($name, $addresses = null)
    {
        $header = new Swift_Mime_Headers_MailboxHeader($name, $this->_encoder, $this->_grammar);
        if (isset($addresses)) {
            $header->setFieldBodyModel($addresses);
        }
        $this->_setHeaderCharset($header);

        return $header;
    }

    /**
     * Create a new Date header using $timestamp (UNIX time).
     *
     * @param string   $name
     * @param int|null $timestamp
     *
     * @return Swift_Mime_Header
     */
    public function createDateHeader($name, $timestamp = null)
    {
        $header = new Swift_Mime_Headers_DateHeader($name, $this->_grammar);
        if (isset($timestamp)) {
            $header->setFieldBodyModel($timestamp);
        }
        $this->_setHeaderCharset($header);

        return $header;
    }

    /**
     * Create a new basic text header with $name and $value.
     *
     * @param string $name
     * @param string $value
     *
     * @return Swift_Mime_Header
     */
    public function createTextHeader($name, $value = null)
    {
        $header = new Swift_Mime_Headers_UnstructuredHeader($name, $this->_encoder, $this->_grammar);
        if (isset($value)) {
            $header->setFieldBodyModel($value);
        }
        $this->_setHeaderCharset($header);

        return $header;
    }

    /**
     * Create a new ParameterizedHeader with $name, $value and $params.
     *
     * @param string $name
     * @param string $value
     * @param array  $params
     *
     * @return Swift_Mime_ParameterizedHeader
     */
    public function createParameterizedHeader($name, $value = null,
        $params = array())
    {
        $header = new Swift_Mime_Headers_ParameterizedHeader($name, $this->_encoder, strtolower($name) == 'content-disposition' ? $this->_paramEncoder : null, $this->_grammar);
        if (isset($value)) {
            $header->setFieldBodyModel($value);
        }
        foreach ($params as $k => $v) {
            $header->setParameter($k, $v);
        }
        $this->_setHeaderCharset($header);

        return $header;
    }

    /**
     * Create a new ID header for Message-ID or Content-ID.
     *
     * @param string       $name
     * @param string|array $ids
     *
     * @return Swift_Mime_Header
     */
    public function createIdHeader($name, $ids = null)
    {
        $header = new Swift_Mime_Headers_IdentificationHeader($name, $this->_grammar);
        if (isset($ids)) {
            $header->setFieldBodyModel($ids);
        }
        $this->_setHeaderCharset($header);

        return $header;
    }

    /**
     * Create a new Path header with an address (path) in it.
     *
     * @param string $name
     * @param string $path
     *
     * @return Swift_Mime_Header
     */
    public function createPathHeader($name, $path = null)
    {
        $header = new Swift_Mime_Headers_PathHeader($name, $this->_grammar);
        if (isset($path)) {
            $header->setFieldBodyModel($path);
        }
        $this->_setHeaderCharset($header);

        return $header;
    }

    /**
     * Notify this observer that the entity's charset has changed.
     *
     * @param string $charset
     */
    public function charsetChanged($charset)
    {
        $this->_charset = $charset;
        $this->_encoder->charsetChanged($charset);
        $this->_paramEncoder->charsetChanged($charset);
    }

    /**
     * Make a deep copy of object.
     */
    public function __clone()
    {
        $this->_encoder = clone $this->_encoder;
        $this->_paramEncoder = clone $this->_paramEncoder;
    }

    /** Apply the charset to the Header */
    private function _setHeaderCharset(Swift_Mime_Header $header)
    {
        if (isset($this->_charset)) {
            $header->setCharset($this->_charset);
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A collection of MIME headers.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_SimpleHeaderSet implements Swift_Mime_HeaderSet
{
    /** HeaderFactory */
    private $_factory;

    /** Collection of set Headers */
    private $_headers = array();

    /** Field ordering details */
    private $_order = array();

    /** List of fields which are required to be displayed */
    private $_required = array();

    /** The charset used by Headers */
    private $_charset;

    /**
     * Create a new SimpleHeaderSet with the given $factory.
     *
     * @param Swift_Mime_HeaderFactory $factory
     * @param string                   $charset
     */
    public function __construct(Swift_Mime_HeaderFactory $factory, $charset = null)
    {
        $this->_factory = $factory;
        if (isset($charset)) {
            $this->setCharset($charset);
        }
    }

    /**
     * Set the charset used by these headers.
     *
     * @param string $charset
     */
    public function setCharset($charset)
    {
        $this->_charset = $charset;
        $this->_factory->charsetChanged($charset);
        $this->_notifyHeadersOfCharset($charset);
    }

    /**
     * Add a new Mailbox Header with a list of $addresses.
     *
     * @param string       $name
     * @param array|string $addresses
     */
    public function addMailboxHeader($name, $addresses = null)
    {
        $this->_storeHeader($name,
        $this->_factory->createMailboxHeader($name, $addresses));
    }

    /**
     * Add a new Date header using $timestamp (UNIX time).
     *
     * @param string $name
     * @param int    $timestamp
     */
    public function addDateHeader($name, $timestamp = null)
    {
        $this->_storeHeader($name,
        $this->_factory->createDateHeader($name, $timestamp));
    }

    /**
     * Add a new basic text header with $name and $value.
     *
     * @param string $name
     * @param string $value
     */
    public function addTextHeader($name, $value = null)
    {
        $this->_storeHeader($name,
        $this->_factory->createTextHeader($name, $value));
    }

    /**
     * Add a new ParameterizedHeader with $name, $value and $params.
     *
     * @param string $name
     * @param string $value
     * @param array  $params
     */
    public function addParameterizedHeader($name, $value = null, $params = array())
    {
        $this->_storeHeader($name, $this->_factory->createParameterizedHeader($name, $value, $params));
    }

    /**
     * Add a new ID header for Message-ID or Content-ID.
     *
     * @param string       $name
     * @param string|array $ids
     */
    public function addIdHeader($name, $ids = null)
    {
        $this->_storeHeader($name, $this->_factory->createIdHeader($name, $ids));
    }

    /**
     * Add a new Path header with an address (path) in it.
     *
     * @param string $name
     * @param string $path
     */
    public function addPathHeader($name, $path = null)
    {
        $this->_storeHeader($name, $this->_factory->createPathHeader($name, $path));
    }

    /**
     * Returns true if at least one header with the given $name exists.
     *
     * If multiple headers match, the actual one may be specified by $index.
     *
     * @param string $name
     * @param int    $index
     *
     * @return bool
     */
    public function has($name, $index = 0)
    {
        $lowerName = strtolower($name);

        if (!array_key_exists($lowerName, $this->_headers)) {
            return false;
        }

        if (func_num_args() < 2) {
            // index was not specified, so we only need to check that there is at least one header value set
            return (bool) count($this->_headers[$lowerName]);
        }

        return array_key_exists($index, $this->_headers[$lowerName]);
    }

    /**
     * Set a header in the HeaderSet.
     *
     * The header may be a previously fetched header via {@link get()} or it may
     * be one that has been created separately.
     *
     * If $index is specified, the header will be inserted into the set at this
     * offset.
     *
     * @param Swift_Mime_Header $header
     * @param int               $index
     */
    public function set(Swift_Mime_Header $header, $index = 0)
    {
        $this->_storeHeader($header->getFieldName(), $header, $index);
    }

    /**
     * Get the header with the given $name.
     *
     * If multiple headers match, the actual one may be specified by $index.
     * Returns NULL if none present.
     *
     * @param string $name
     * @param int    $index
     *
     * @return Swift_Mime_Header
     */
    public function get($name, $index = 0)
    {
        $name = strtolower($name);

        if (func_num_args() < 2) {
            if ($this->has($name)) {
                $values = array_values($this->_headers[$name]);

                return array_shift($values);
            }
        } else {
            if ($this->has($name, $index)) {
                return $this->_headers[$name][$index];
            }
        }
    }

    /**
     * Get all headers with the given $name.
     *
     * @param string $name
     *
     * @return array
     */
    public function getAll($name = null)
    {
        if (!isset($name)) {
            $headers = array();
            foreach ($this->_headers as $collection) {
                $headers = array_merge($headers, $collection);
            }

            return $headers;
        }

        $lowerName = strtolower($name);
        if (!array_key_exists($lowerName, $this->_headers)) {
            return array();
        }

        return $this->_headers[$lowerName];
    }

    /**
     * Return the name of all Headers.
     *
     * @return array
     */
    public function listAll()
    {
        $headers = $this->_headers;
        if ($this->_canSort()) {
            uksort($headers, array($this, '_sortHeaders'));
        }

        return array_keys($headers);
    }

    /**
     * Remove the header with the given $name if it's set.
     *
     * If multiple headers match, the actual one may be specified by $index.
     *
     * @param string $name
     * @param int    $index
     */
    public function remove($name, $index = 0)
    {
        $lowerName = strtolower($name);
        unset($this->_headers[$lowerName][$index]);
    }

    /**
     * Remove all headers with the given $name.
     *
     * @param string $name
     */
    public function removeAll($name)
    {
        $lowerName = strtolower($name);
        unset($this->_headers[$lowerName]);
    }

    /**
     * Create a new instance of this HeaderSet.
     *
     * @return Swift_Mime_HeaderSet
     */
    public function newInstance()
    {
        return new self($this->_factory);
    }

    /**
     * Define a list of Header names as an array in the correct order.
     *
     * These Headers will be output in the given order where present.
     *
     * @param array $sequence
     */
    public function defineOrdering(array $sequence)
    {
        $this->_order = array_flip(array_map('strtolower', $sequence));
    }

    /**
     * Set a list of header names which must always be displayed when set.
     *
     * Usually headers without a field value won't be output unless set here.
     *
     * @param array $names
     */
    public function setAlwaysDisplayed(array $names)
    {
        $this->_required = array_flip(array_map('strtolower', $names));
    }

    /**
     * Notify this observer that the entity's charset has changed.
     *
     * @param string $charset
     */
    public function charsetChanged($charset)
    {
        $this->setCharset($charset);
    }

    /**
     * Returns a string with a representation of all headers.
     *
     * @return string
     */
    public function toString()
    {
        $string = '';
        $headers = $this->_headers;
        if ($this->_canSort()) {
            uksort($headers, array($this, '_sortHeaders'));
        }
        foreach ($headers as $collection) {
            foreach ($collection as $header) {
                if ($this->_isDisplayed($header) || $header->getFieldBody() != '') {
                    $string .= $header->toString();
                }
            }
        }

        return $string;
    }

    /**
     * Returns a string representation of this object.
     *
     * @return string
     *
     * @see toString()
     */
    public function __toString()
    {
        return $this->toString();
    }

    /** Save a Header to the internal collection */
    private function _storeHeader($name, Swift_Mime_Header $header, $offset = null)
    {
        if (!isset($this->_headers[strtolower($name)])) {
            $this->_headers[strtolower($name)] = array();
        }
        if (!isset($offset)) {
            $this->_headers[strtolower($name)][] = $header;
        } else {
            $this->_headers[strtolower($name)][$offset] = $header;
        }
    }

    /** Test if the headers can be sorted */
    private function _canSort()
    {
        return count($this->_order) > 0;
    }

    /** uksort() algorithm for Header ordering */
    private function _sortHeaders($a, $b)
    {
        $lowerA = strtolower($a);
        $lowerB = strtolower($b);
        $aPos = array_key_exists($lowerA, $this->_order) ? $this->_order[$lowerA] : -1;
        $bPos = array_key_exists($lowerB, $this->_order) ? $this->_order[$lowerB] : -1;

        if (-1 === $aPos && -1 === $bPos) {
            // just be sure to be determinist here
            return $a > $b ? -1 : 1;
        }

        if ($aPos == -1) {
            return 1;
        } elseif ($bPos == -1) {
            return -1;
        }

        return $aPos < $bPos ? -1 : 1;
    }

    /** Test if the given Header is always displayed */
    private function _isDisplayed(Swift_Mime_Header $header)
    {
        return array_key_exists(strtolower($header->getFieldName()), $this->_required);
    }

    /** Notify all Headers of the new charset */
    private function _notifyHeadersOfCharset($charset)
    {
        foreach ($this->_headers as $headerGroup) {
            foreach ($headerGroup as $header) {
                $header->setCharset($charset);
            }
        }
    }

    /**
     * Make a deep copy of object.
     */
    public function __clone()
    {
        $this->_factory = clone $this->_factory;
        foreach ($this->_headers as $groupKey => $headerGroup) {
            foreach ($headerGroup as $key => $header) {
                $this->_headers[$groupKey][$key] = clone $header;
            }
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * The default email message class.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_SimpleMessage extends Swift_Mime_MimePart implements Swift_Mime_Message
{
    const PRIORITY_HIGHEST = 1;
    const PRIORITY_HIGH = 2;
    const PRIORITY_NORMAL = 3;
    const PRIORITY_LOW = 4;
    const PRIORITY_LOWEST = 5;

    /**
     * Create a new SimpleMessage with $headers, $encoder and $cache.
     *
     * @param Swift_Mime_HeaderSet      $headers
     * @param Swift_Mime_ContentEncoder $encoder
     * @param Swift_KeyCache            $cache
     * @param Swift_Mime_Grammar        $grammar
     * @param string                    $charset
     */
    public function __construct(Swift_Mime_HeaderSet $headers, Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache, Swift_Mime_Grammar $grammar, $charset = null)
    {
        parent::__construct($headers, $encoder, $cache, $grammar, $charset);
        $this->getHeaders()->defineOrdering(array(
            'Return-Path',
            'Received',
            'DKIM-Signature',
            'DomainKey-Signature',
            'Sender',
            'Message-ID',
            'Date',
            'Subject',
            'From',
            'Reply-To',
            'To',
            'Cc',
            'Bcc',
            'MIME-Version',
            'Content-Type',
            'Content-Transfer-Encoding',
            ));
        $this->getHeaders()->setAlwaysDisplayed(array('Date', 'Message-ID', 'From'));
        $this->getHeaders()->addTextHeader('MIME-Version', '1.0');
        $this->setDate(time());
        $this->setId($this->getId());
        $this->getHeaders()->addMailboxHeader('From');
    }

    /**
     * Always returns {@link LEVEL_TOP} for a message instance.
     *
     * @return int
     */
    public function getNestingLevel()
    {
        return self::LEVEL_TOP;
    }

    /**
     * Set the subject of this message.
     *
     * @param string $subject
     *
     * @return $this
     */
    public function setSubject($subject)
    {
        if (!$this->_setHeaderFieldModel('Subject', $subject)) {
            $this->getHeaders()->addTextHeader('Subject', $subject);
        }

        return $this;
    }

    /**
     * Get the subject of this message.
     *
     * @return string
     */
    public function getSubject()
    {
        return $this->_getHeaderFieldModel('Subject');
    }

    /**
     * Set the date at which this message was created.
     *
     * @param int $date
     *
     * @return $this
     */
    public function setDate($date)
    {
        if (!$this->_setHeaderFieldModel('Date', $date)) {
            $this->getHeaders()->addDateHeader('Date', $date);
        }

        return $this;
    }

    /**
     * Get the date at which this message was created.
     *
     * @return int
     */
    public function getDate()
    {
        return $this->_getHeaderFieldModel('Date');
    }

    /**
     * Set the return-path (the bounce address) of this message.
     *
     * @param string $address
     *
     * @return $this
     */
    public function setReturnPath($address)
    {
        if (!$this->_setHeaderFieldModel('Return-Path', $address)) {
            $this->getHeaders()->addPathHeader('Return-Path', $address);
        }

        return $this;
    }

    /**
     * Get the return-path (bounce address) of this message.
     *
     * @return string
     */
    public function getReturnPath()
    {
        return $this->_getHeaderFieldModel('Return-Path');
    }

    /**
     * Set the sender of this message.
     *
     * This does not override the From field, but it has a higher significance.
     *
     * @param string $address
     * @param string $name    optional
     *
     * @return $this
     */
    public function setSender($address, $name = null)
    {
        if (!is_array($address) && isset($name)) {
            $address = array($address => $name);
        }

        if (!$this->_setHeaderFieldModel('Sender', (array) $address)) {
            $this->getHeaders()->addMailboxHeader('Sender', (array) $address);
        }

        return $this;
    }

    /**
     * Get the sender of this message.
     *
     * @return string
     */
    public function getSender()
    {
        return $this->_getHeaderFieldModel('Sender');
    }

    /**
     * Add a From: address to this message.
     *
     * If $name is passed this name will be associated with the address.
     *
     * @param string $address
     * @param string $name    optional
     *
     * @return $this
     */
    public function addFrom($address, $name = null)
    {
        $current = $this->getFrom();
        $current[$address] = $name;

        return $this->setFrom($current);
    }

    /**
     * Set the from address of this message.
     *
     * You may pass an array of addresses if this message is from multiple people.
     *
     * If $name is passed and the first parameter is a string, this name will be
     * associated with the address.
     *
     * @param string|array $addresses
     * @param string       $name      optional
     *
     * @return $this
     */
    public function setFrom($addresses, $name = null)
    {
        if (!is_array($addresses) && isset($name)) {
            $addresses = array($addresses => $name);
        }

        if (!$this->_setHeaderFieldModel('From', (array) $addresses)) {
            $this->getHeaders()->addMailboxHeader('From', (array) $addresses);
        }

        return $this;
    }

    /**
     * Get the from address of this message.
     *
     * @return mixed
     */
    public function getFrom()
    {
        return $this->_getHeaderFieldModel('From');
    }

    /**
     * Add a Reply-To: address to this message.
     *
     * If $name is passed this name will be associated with the address.
     *
     * @param string $address
     * @param string $name    optional
     *
     * @return $this
     */
    public function addReplyTo($address, $name = null)
    {
        $current = $this->getReplyTo();
        $current[$address] = $name;

        return $this->setReplyTo($current);
    }

    /**
     * Set the reply-to address of this message.
     *
     * You may pass an array of addresses if replies will go to multiple people.
     *
     * If $name is passed and the first parameter is a string, this name will be
     * associated with the address.
     *
     * @param mixed  $addresses
     * @param string $name      optional
     *
     * @return $this
     */
    public function setReplyTo($addresses, $name = null)
    {
        if (!is_array($addresses) && isset($name)) {
            $addresses = array($addresses => $name);
        }

        if (!$this->_setHeaderFieldModel('Reply-To', (array) $addresses)) {
            $this->getHeaders()->addMailboxHeader('Reply-To', (array) $addresses);
        }

        return $this;
    }

    /**
     * Get the reply-to address of this message.
     *
     * @return string
     */
    public function getReplyTo()
    {
        return $this->_getHeaderFieldModel('Reply-To');
    }

    /**
     * Add a To: address to this message.
     *
     * If $name is passed this name will be associated with the address.
     *
     * @param string $address
     * @param string $name    optional
     *
     * @return $this
     */
    public function addTo($address, $name = null)
    {
        $current = $this->getTo();
        $current[$address] = $name;

        return $this->setTo($current);
    }

    /**
     * Set the to addresses of this message.
     *
     * If multiple recipients will receive the message an array should be used.
     * Example: array('receiver@domain.org', 'other@domain.org' => 'A name')
     *
     * If $name is passed and the first parameter is a string, this name will be
     * associated with the address.
     *
     * @param mixed  $addresses
     * @param string $name      optional
     *
     * @return $this
     */
    public function setTo($addresses, $name = null)
    {
        if (!is_array($addresses) && isset($name)) {
            $addresses = array($addresses => $name);
        }

        if (!$this->_setHeaderFieldModel('To', (array) $addresses)) {
            $this->getHeaders()->addMailboxHeader('To', (array) $addresses);
        }

        return $this;
    }

    /**
     * Get the To addresses of this message.
     *
     * @return array
     */
    public function getTo()
    {
        return $this->_getHeaderFieldModel('To');
    }

    /**
     * Add a Cc: address to this message.
     *
     * If $name is passed this name will be associated with the address.
     *
     * @param string $address
     * @param string $name    optional
     *
     * @return $this
     */
    public function addCc($address, $name = null)
    {
        $current = $this->getCc();
        $current[$address] = $name;

        return $this->setCc($current);
    }

    /**
     * Set the Cc addresses of this message.
     *
     * If $name is passed and the first parameter is a string, this name will be
     * associated with the address.
     *
     * @param mixed  $addresses
     * @param string $name      optional
     *
     * @return $this
     */
    public function setCc($addresses, $name = null)
    {
        if (!is_array($addresses) && isset($name)) {
            $addresses = array($addresses => $name);
        }

        if (!$this->_setHeaderFieldModel('Cc', (array) $addresses)) {
            $this->getHeaders()->addMailboxHeader('Cc', (array) $addresses);
        }

        return $this;
    }

    /**
     * Get the Cc address of this message.
     *
     * @return array
     */
    public function getCc()
    {
        return $this->_getHeaderFieldModel('Cc');
    }

    /**
     * Add a Bcc: address to this message.
     *
     * If $name is passed this name will be associated with the address.
     *
     * @param string $address
     * @param string $name    optional
     *
     * @return $this
     */
    public function addBcc($address, $name = null)
    {
        $current = $this->getBcc();
        $current[$address] = $name;

        return $this->setBcc($current);
    }

    /**
     * Set the Bcc addresses of this message.
     *
     * If $name is passed and the first parameter is a string, this name will be
     * associated with the address.
     *
     * @param mixed  $addresses
     * @param string $name      optional
     *
     * @return $this
     */
    public function setBcc($addresses, $name = null)
    {
        if (!is_array($addresses) && isset($name)) {
            $addresses = array($addresses => $name);
        }

        if (!$this->_setHeaderFieldModel('Bcc', (array) $addresses)) {
            $this->getHeaders()->addMailboxHeader('Bcc', (array) $addresses);
        }

        return $this;
    }

    /**
     * Get the Bcc addresses of this message.
     *
     * @return array
     */
    public function getBcc()
    {
        return $this->_getHeaderFieldModel('Bcc');
    }

    /**
     * Set the priority of this message.
     *
     * The value is an integer where 1 is the highest priority and 5 is the lowest.
     *
     * @param int $priority
     *
     * @return $this
     */
    public function setPriority($priority)
    {
        $priorityMap = array(
            self::PRIORITY_HIGHEST => 'Highest',
            self::PRIORITY_HIGH => 'High',
            self::PRIORITY_NORMAL => 'Normal',
            self::PRIORITY_LOW => 'Low',
            self::PRIORITY_LOWEST => 'Lowest',
            );
        $pMapKeys = array_keys($priorityMap);
        if ($priority > max($pMapKeys)) {
            $priority = max($pMapKeys);
        } elseif ($priority < min($pMapKeys)) {
            $priority = min($pMapKeys);
        }
        if (!$this->_setHeaderFieldModel('X-Priority',
            sprintf('%d (%s)', $priority, $priorityMap[$priority]))) {
            $this->getHeaders()->addTextHeader('X-Priority',
                sprintf('%d (%s)', $priority, $priorityMap[$priority]));
        }

        return $this;
    }

    /**
     * Get the priority of this message.
     *
     * The returned value is an integer where 1 is the highest priority and 5
     * is the lowest.
     *
     * @return int
     */
    public function getPriority()
    {
        list($priority) = sscanf($this->_getHeaderFieldModel('X-Priority'),
            '%[1-5]'
            );

        return isset($priority) ? $priority : 3;
    }

    /**
     * Ask for a delivery receipt from the recipient to be sent to $addresses.
     *
     * @param array $addresses
     *
     * @return $this
     */
    public function setReadReceiptTo($addresses)
    {
        if (!$this->_setHeaderFieldModel('Disposition-Notification-To', $addresses)) {
            $this->getHeaders()
                ->addMailboxHeader('Disposition-Notification-To', $addresses);
        }

        return $this;
    }

    /**
     * Get the addresses to which a read-receipt will be sent.
     *
     * @return string
     */
    public function getReadReceiptTo()
    {
        return $this->_getHeaderFieldModel('Disposition-Notification-To');
    }

    /**
     * Attach a {@link Swift_Mime_MimeEntity} such as an Attachment or MimePart.
     *
     * @param Swift_Mime_MimeEntity $entity
     *
     * @return $this
     */
    public function attach(Swift_Mime_MimeEntity $entity)
    {
        $this->setChildren(array_merge($this->getChildren(), array($entity)));

        return $this;
    }

    /**
     * Remove an already attached entity.
     *
     * @param Swift_Mime_MimeEntity $entity
     *
     * @return $this
     */
    public function detach(Swift_Mime_MimeEntity $entity)
    {
        $newChildren = array();
        foreach ($this->getChildren() as $child) {
            if ($entity !== $child) {
                $newChildren[] = $child;
            }
        }
        $this->setChildren($newChildren);

        return $this;
    }

    /**
     * Attach a {@link Swift_Mime_MimeEntity} and return it's CID source.
     * This method should be used when embedding images or other data in a message.
     *
     * @param Swift_Mime_MimeEntity $entity
     *
     * @return string
     */
    public function embed(Swift_Mime_MimeEntity $entity)
    {
        $this->attach($entity);

        return 'cid:'.$entity->getId();
    }

    /**
     * Get this message as a complete string.
     *
     * @return string
     */
    public function toString()
    {
        if (count($children = $this->getChildren()) > 0 && $this->getBody() != '') {
            $this->setChildren(array_merge(array($this->_becomeMimePart()), $children));
            $string = parent::toString();
            $this->setChildren($children);
        } else {
            $string = parent::toString();
        }

        return $string;
    }

    /**
     * Returns a string representation of this object.
     *
     * @see toString()
     *
     * @return string
     */
    public function __toString()
    {
        return $this->toString();
    }

    /**
     * Write this message to a {@link Swift_InputByteStream}.
     *
     * @param Swift_InputByteStream $is
     */
    public function toByteStream(Swift_InputByteStream $is)
    {
        if (count($children = $this->getChildren()) > 0 && $this->getBody() != '') {
            $this->setChildren(array_merge(array($this->_becomeMimePart()), $children));
            parent::toByteStream($is);
            $this->setChildren($children);
        } else {
            parent::toByteStream($is);
        }
    }

    /** @see Swift_Mime_SimpleMimeEntity::_getIdField() */
    protected function _getIdField()
    {
        return 'Message-ID';
    }

    /** Turn the body of this message into a child of itself if needed */
    protected function _becomeMimePart()
    {
        $part = new parent($this->getHeaders()->newInstance(), $this->getEncoder(),
            $this->_getCache(), $this->_getGrammar(), $this->_userCharset
            );
        $part->setContentType($this->_userContentType);
        $part->setBody($this->getBody());
        $part->setFormat($this->_userFormat);
        $part->setDelSp($this->_userDelSp);
        $part->_setNestingLevel($this->_getTopNestingLevel());

        return $part;
    }

    /** Get the highest nesting level nested inside this message */
    private function _getTopNestingLevel()
    {
        $highestLevel = $this->getNestingLevel();
        foreach ($this->getChildren() as $child) {
            $childLevel = $child->getNestingLevel();
            if ($highestLevel < $childLevel) {
                $highestLevel = $childLevel;
            }
        }

        return $highestLevel;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A MIME entity, in a multipart message.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_SimpleMimeEntity implements Swift_Mime_MimeEntity
{
    /** A collection of Headers for this mime entity */
    private $_headers;

    /** The body as a string, or a stream */
    private $_body;

    /** The encoder that encodes the body into a streamable format */
    private $_encoder;

    /** The grammar to use for id validation */
    private $_grammar;

    /** A mime boundary, if any is used */
    private $_boundary;

    /** Mime types to be used based on the nesting level */
    private $_compositeRanges = array(
        'multipart/mixed' => array(self::LEVEL_TOP, self::LEVEL_MIXED),
        'multipart/alternative' => array(self::LEVEL_MIXED, self::LEVEL_ALTERNATIVE),
        'multipart/related' => array(self::LEVEL_ALTERNATIVE, self::LEVEL_RELATED),
    );

    /** A set of filter rules to define what level an entity should be nested at */
    private $_compoundLevelFilters = array();

    /** The nesting level of this entity */
    private $_nestingLevel = self::LEVEL_ALTERNATIVE;

    /** A KeyCache instance used during encoding and streaming */
    private $_cache;

    /** Direct descendants of this entity */
    private $_immediateChildren = array();

    /** All descendants of this entity */
    private $_children = array();

    /** The maximum line length of the body of this entity */
    private $_maxLineLength = 78;

    /** The order in which alternative mime types should appear */
    private $_alternativePartOrder = array(
        'text/plain' => 1,
        'text/html' => 2,
        'multipart/related' => 3,
    );

    /** The CID of this entity */
    private $_id;

    /** The key used for accessing the cache */
    private $_cacheKey;

    protected $_userContentType;

    /**
     * Create a new SimpleMimeEntity with $headers, $encoder and $cache.
     *
     * @param Swift_Mime_HeaderSet      $headers
     * @param Swift_Mime_ContentEncoder $encoder
     * @param Swift_KeyCache            $cache
     * @param Swift_Mime_Grammar        $grammar
     */
    public function __construct(Swift_Mime_HeaderSet $headers, Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache, Swift_Mime_Grammar $grammar)
    {
        $this->_cacheKey = md5(uniqid(getmypid().mt_rand(), true));
        $this->_cache = $cache;
        $this->_headers = $headers;
        $this->_grammar = $grammar;
        $this->setEncoder($encoder);
        $this->_headers->defineOrdering(array('Content-Type', 'Content-Transfer-Encoding'));

        // This array specifies that, when the entire MIME document contains
        // $compoundLevel, then for each child within $level, if its Content-Type
        // is $contentType then it should be treated as if it's level is
        // $neededLevel instead.  I tried to write that unambiguously! :-\
        // Data Structure:
        // array (
        //   $compoundLevel => array(
        //     $level => array(
        //       $contentType => $neededLevel
        //     )
        //   )
        // )

        $this->_compoundLevelFilters = array(
            (self::LEVEL_ALTERNATIVE + self::LEVEL_RELATED) => array(
                self::LEVEL_ALTERNATIVE => array(
                    'text/plain' => self::LEVEL_ALTERNATIVE,
                    'text/html' => self::LEVEL_RELATED,
                    ),
                ),
            );

        $this->_id = $this->getRandomId();
    }

    /**
     * Generate a new Content-ID or Message-ID for this MIME entity.
     *
     * @return string
     */
    public function generateId()
    {
        $this->setId($this->getRandomId());

        return $this->_id;
    }

    /**
     * Get the {@link Swift_Mime_HeaderSet} for this entity.
     *
     * @return Swift_Mime_HeaderSet
     */
    public function getHeaders()
    {
        return $this->_headers;
    }

    /**
     * Get the nesting level of this entity.
     *
     * @see LEVEL_TOP, LEVEL_MIXED, LEVEL_RELATED, LEVEL_ALTERNATIVE
     *
     * @return int
     */
    public function getNestingLevel()
    {
        return $this->_nestingLevel;
    }

    /**
     * Get the Content-type of this entity.
     *
     * @return string
     */
    public function getContentType()
    {
        return $this->_getHeaderFieldModel('Content-Type');
    }

    /**
     * Set the Content-type of this entity.
     *
     * @param string $type
     *
     * @return $this
     */
    public function setContentType($type)
    {
        $this->_setContentTypeInHeaders($type);
        // Keep track of the value so that if the content-type changes automatically
        // due to added child entities, it can be restored if they are later removed
        $this->_userContentType = $type;

        return $this;
    }

    /**
     * Get the CID of this entity.
     *
     * The CID will only be present in headers if a Content-ID header is present.
     *
     * @return string
     */
    public function getId()
    {
        $tmp = (array) $this->_getHeaderFieldModel($this->_getIdField());

        return $this->_headers->has($this->_getIdField()) ? current($tmp) : $this->_id;
    }

    /**
     * Set the CID of this entity.
     *
     * @param string $id
     *
     * @return $this
     */
    public function setId($id)
    {
        if (!$this->_setHeaderFieldModel($this->_getIdField(), $id)) {
            $this->_headers->addIdHeader($this->_getIdField(), $id);
        }
        $this->_id = $id;

        return $this;
    }

    /**
     * Get the description of this entity.
     *
     * This value comes from the Content-Description header if set.
     *
     * @return string
     */
    public function getDescription()
    {
        return $this->_getHeaderFieldModel('Content-Description');
    }

    /**
     * Set the description of this entity.
     *
     * This method sets a value in the Content-ID header.
     *
     * @param string $description
     *
     * @return $this
     */
    public function setDescription($description)
    {
        if (!$this->_setHeaderFieldModel('Content-Description', $description)) {
            $this->_headers->addTextHeader('Content-Description', $description);
        }

        return $this;
    }

    /**
     * Get the maximum line length of the body of this entity.
     *
     * @return int
     */
    public function getMaxLineLength()
    {
        return $this->_maxLineLength;
    }

    /**
     * Set the maximum line length of lines in this body.
     *
     * Though not enforced by the library, lines should not exceed 1000 chars.
     *
     * @param int $length
     *
     * @return $this
     */
    public function setMaxLineLength($length)
    {
        $this->_maxLineLength = $length;

        return $this;
    }

    /**
     * Get all children added to this entity.
     *
     * @return Swift_Mime_MimeEntity[]
     */
    public function getChildren()
    {
        return $this->_children;
    }

    /**
     * Set all children of this entity.
     *
     * @param Swift_Mime_MimeEntity[] $children
     * @param int                     $compoundLevel For internal use only
     *
     * @return $this
     */
    public function setChildren(array $children, $compoundLevel = null)
    {
        // TODO: Try to refactor this logic

        $compoundLevel = isset($compoundLevel) ? $compoundLevel : $this->_getCompoundLevel($children);
        $immediateChildren = array();
        $grandchildren = array();
        $newContentType = $this->_userContentType;

        foreach ($children as $child) {
            $level = $this->_getNeededChildLevel($child, $compoundLevel);
            if (empty($immediateChildren)) {
                //first iteration
                $immediateChildren = array($child);
            } else {
                $nextLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel);
                if ($nextLevel == $level) {
                    $immediateChildren[] = $child;
                } elseif ($level < $nextLevel) {
                    // Re-assign immediateChildren to grandchildren
                    $grandchildren = array_merge($grandchildren, $immediateChildren);
                    // Set new children
                    $immediateChildren = array($child);
                } else {
                    $grandchildren[] = $child;
                }
            }
        }

        if ($immediateChildren) {
            $lowestLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel);

            // Determine which composite media type is needed to accommodate the
            // immediate children
            foreach ($this->_compositeRanges as $mediaType => $range) {
                if ($lowestLevel > $range[0] && $lowestLevel <= $range[1]) {
                    $newContentType = $mediaType;

                    break;
                }
            }

            // Put any grandchildren in a subpart
            if (!empty($grandchildren)) {
                $subentity = $this->_createChild();
                $subentity->_setNestingLevel($lowestLevel);
                $subentity->setChildren($grandchildren, $compoundLevel);
                array_unshift($immediateChildren, $subentity);
            }
        }

        $this->_immediateChildren = $immediateChildren;
        $this->_children = $children;
        $this->_setContentTypeInHeaders($newContentType);
        $this->_fixHeaders();
        $this->_sortChildren();

        return $this;
    }

    /**
     * Get the body of this entity as a string.
     *
     * @return string
     */
    public function getBody()
    {
        return $this->_body instanceof Swift_OutputByteStream ? $this->_readStream($this->_body) : $this->_body;
    }

    /**
     * Set the body of this entity, either as a string, or as an instance of
     * {@link Swift_OutputByteStream}.
     *
     * @param mixed  $body
     * @param string $contentType optional
     *
     * @return $this
     */
    public function setBody($body, $contentType = null)
    {
        if ($body !== $this->_body) {
            $this->_clearCache();
        }

        $this->_body = $body;
        if (isset($contentType)) {
            $this->setContentType($contentType);
        }

        return $this;
    }

    /**
     * Get the encoder used for the body of this entity.
     *
     * @return Swift_Mime_ContentEncoder
     */
    public function getEncoder()
    {
        return $this->_encoder;
    }

    /**
     * Set the encoder used for the body of this entity.
     *
     * @param Swift_Mime_ContentEncoder $encoder
     *
     * @return $this
     */
    public function setEncoder(Swift_Mime_ContentEncoder $encoder)
    {
        if ($encoder !== $this->_encoder) {
            $this->_clearCache();
        }

        $this->_encoder = $encoder;
        $this->_setEncoding($encoder->getName());
        $this->_notifyEncoderChanged($encoder);

        return $this;
    }

    /**
     * Get the boundary used to separate children in this entity.
     *
     * @return string
     */
    public function getBoundary()
    {
        if (!isset($this->_boundary)) {
            $this->_boundary = '_=_swift_v4_'.time().'_'.md5(getmypid().mt_rand().uniqid('', true)).'_=_';
        }

        return $this->_boundary;
    }

    /**
     * Set the boundary used to separate children in this entity.
     *
     * @param string $boundary
     *
     * @throws Swift_RfcComplianceException
     *
     * @return $this
     */
    public function setBoundary($boundary)
    {
        $this->_assertValidBoundary($boundary);
        $this->_boundary = $boundary;

        return $this;
    }

    /**
     * Receive notification that the charset of this entity, or a parent entity
     * has changed.
     *
     * @param string $charset
     */
    public function charsetChanged($charset)
    {
        $this->_notifyCharsetChanged($charset);
    }

    /**
     * Receive notification that the encoder of this entity or a parent entity
     * has changed.
     *
     * @param Swift_Mime_ContentEncoder $encoder
     */
    public function encoderChanged(Swift_Mime_ContentEncoder $encoder)
    {
        $this->_notifyEncoderChanged($encoder);
    }

    /**
     * Get this entire entity as a string.
     *
     * @return string
     */
    public function toString()
    {
        $string = $this->_headers->toString();
        $string .= $this->_bodyToString();

        return $string;
    }

    /**
     * Get this entire entity as a string.
     *
     * @return string
     */
    protected function _bodyToString()
    {
        $string = '';

        if (isset($this->_body) && empty($this->_immediateChildren)) {
            if ($this->_cache->hasKey($this->_cacheKey, 'body')) {
                $body = $this->_cache->getString($this->_cacheKey, 'body');
            } else {
                $body = "\r\n".$this->_encoder->encodeString($this->getBody(), 0, $this->getMaxLineLength());
                $this->_cache->setString($this->_cacheKey, 'body', $body, Swift_KeyCache::MODE_WRITE);
            }
            $string .= $body;
        }

        if (!empty($this->_immediateChildren)) {
            foreach ($this->_immediateChildren as $child) {
                $string .= "\r\n\r\n--".$this->getBoundary()."\r\n";
                $string .= $child->toString();
            }
            $string .= "\r\n\r\n--".$this->getBoundary()."--\r\n";
        }

        return $string;
    }

    /**
     * Returns a string representation of this object.
     *
     * @see toString()
     *
     * @return string
     */
    public function __toString()
    {
        return $this->toString();
    }

    /**
     * Write this entire entity to a {@see Swift_InputByteStream}.
     *
     * @param Swift_InputByteStream
     */
    public function toByteStream(Swift_InputByteStream $is)
    {
        $is->write($this->_headers->toString());
        $is->commit();

        $this->_bodyToByteStream($is);
    }

    /**
     * Write this entire entity to a {@link Swift_InputByteStream}.
     *
     * @param Swift_InputByteStream
     */
    protected function _bodyToByteStream(Swift_InputByteStream $is)
    {
        if (empty($this->_immediateChildren)) {
            if (isset($this->_body)) {
                if ($this->_cache->hasKey($this->_cacheKey, 'body')) {
                    $this->_cache->exportToByteStream($this->_cacheKey, 'body', $is);
                } else {
                    $cacheIs = $this->_cache->getInputByteStream($this->_cacheKey, 'body');
                    if ($cacheIs) {
                        $is->bind($cacheIs);
                    }

                    $is->write("\r\n");

                    if ($this->_body instanceof Swift_OutputByteStream) {
                        $this->_body->setReadPointer(0);

                        $this->_encoder->encodeByteStream($this->_body, $is, 0, $this->getMaxLineLength());
                    } else {
                        $is->write($this->_encoder->encodeString($this->getBody(), 0, $this->getMaxLineLength()));
                    }

                    if ($cacheIs) {
                        $is->unbind($cacheIs);
                    }
                }
            }
        }

        if (!empty($this->_immediateChildren)) {
            foreach ($this->_immediateChildren as $child) {
                $is->write("\r\n\r\n--".$this->getBoundary()."\r\n");
                $child->toByteStream($is);
            }
            $is->write("\r\n\r\n--".$this->getBoundary()."--\r\n");
        }
    }

    /**
     * Get the name of the header that provides the ID of this entity.
     */
    protected function _getIdField()
    {
        return 'Content-ID';
    }

    /**
     * Get the model data (usually an array or a string) for $field.
     */
    protected function _getHeaderFieldModel($field)
    {
        if ($this->_headers->has($field)) {
            return $this->_headers->get($field)->getFieldBodyModel();
        }
    }

    /**
     * Set the model data for $field.
     */
    protected function _setHeaderFieldModel($field, $model)
    {
        if ($this->_headers->has($field)) {
            $this->_headers->get($field)->setFieldBodyModel($model);

            return true;
        }

        return false;
    }

    /**
     * Get the parameter value of $parameter on $field header.
     */
    protected function _getHeaderParameter($field, $parameter)
    {
        if ($this->_headers->has($field)) {
            return $this->_headers->get($field)->getParameter($parameter);
        }
    }

    /**
     * Set the parameter value of $parameter on $field header.
     */
    protected function _setHeaderParameter($field, $parameter, $value)
    {
        if ($this->_headers->has($field)) {
            $this->_headers->get($field)->setParameter($parameter, $value);

            return true;
        }

        return false;
    }

    /**
     * Re-evaluate what content type and encoding should be used on this entity.
     */
    protected function _fixHeaders()
    {
        if (count($this->_immediateChildren)) {
            $this->_setHeaderParameter('Content-Type', 'boundary',
                $this->getBoundary()
                );
            $this->_headers->remove('Content-Transfer-Encoding');
        } else {
            $this->_setHeaderParameter('Content-Type', 'boundary', null);
            $this->_setEncoding($this->_encoder->getName());
        }
    }

    /**
     * Get the KeyCache used in this entity.
     *
     * @return Swift_KeyCache
     */
    protected function _getCache()
    {
        return $this->_cache;
    }

    /**
     * Get the grammar used for validation.
     *
     * @return Swift_Mime_Grammar
     */
    protected function _getGrammar()
    {
        return $this->_grammar;
    }

    /**
     * Empty the KeyCache for this entity.
     */
    protected function _clearCache()
    {
        $this->_cache->clearKey($this->_cacheKey, 'body');
    }

    /**
     * Returns a random Content-ID or Message-ID.
     *
     * @return string
     */
    protected function getRandomId()
    {
        $idLeft = md5(getmypid().'.'.time().'.'.uniqid(mt_rand(), true));
        $idRight = !empty($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'swift.generated';
        $id = $idLeft.'@'.$idRight;

        try {
            $this->_assertValidId($id);
        } catch (Swift_RfcComplianceException $e) {
            $id = $idLeft.'@swift.generated';
        }

        return $id;
    }

    private function _readStream(Swift_OutputByteStream $os)
    {
        $string = '';
        while (false !== $bytes = $os->read(8192)) {
            $string .= $bytes;
        }

        $os->setReadPointer(0);

        return $string;
    }

    private function _setEncoding($encoding)
    {
        if (!$this->_setHeaderFieldModel('Content-Transfer-Encoding', $encoding)) {
            $this->_headers->addTextHeader('Content-Transfer-Encoding', $encoding);
        }
    }

    private function _assertValidBoundary($boundary)
    {
        if (!preg_match('/^[a-z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-z0-9\'\(\)\+_\-,\.\/:=\?]$/Di', $boundary)) {
            throw new Swift_RfcComplianceException('Mime boundary set is not RFC 2046 compliant.');
        }
    }

    private function _setContentTypeInHeaders($type)
    {
        if (!$this->_setHeaderFieldModel('Content-Type', $type)) {
            $this->_headers->addParameterizedHeader('Content-Type', $type);
        }
    }

    private function _setNestingLevel($level)
    {
        $this->_nestingLevel = $level;
    }

    private function _getCompoundLevel($children)
    {
        $level = 0;
        foreach ($children as $child) {
            $level |= $child->getNestingLevel();
        }

        return $level;
    }

    private function _getNeededChildLevel($child, $compoundLevel)
    {
        $filter = array();
        foreach ($this->_compoundLevelFilters as $bitmask => $rules) {
            if (($compoundLevel & $bitmask) === $bitmask) {
                $filter = $rules + $filter;
            }
        }

        $realLevel = $child->getNestingLevel();
        $lowercaseType = strtolower($child->getContentType());

        if (isset($filter[$realLevel]) && isset($filter[$realLevel][$lowercaseType])) {
            return $filter[$realLevel][$lowercaseType];
        }

        return $realLevel;
    }

    private function _createChild()
    {
        return new self($this->_headers->newInstance(), $this->_encoder, $this->_cache, $this->_grammar);
    }

    private function _notifyEncoderChanged(Swift_Mime_ContentEncoder $encoder)
    {
        foreach ($this->_immediateChildren as $child) {
            $child->encoderChanged($encoder);
        }
    }

    private function _notifyCharsetChanged($charset)
    {
        $this->_encoder->charsetChanged($charset);
        $this->_headers->charsetChanged($charset);
        foreach ($this->_immediateChildren as $child) {
            $child->charsetChanged($charset);
        }
    }

    private function _sortChildren()
    {
        $shouldSort = false;
        foreach ($this->_immediateChildren as $child) {
            // NOTE: This include alternative parts moved into a related part
            if ($child->getNestingLevel() == self::LEVEL_ALTERNATIVE) {
                $shouldSort = true;
                break;
            }
        }

        // Sort in order of preference, if there is one
        if ($shouldSort) {
            usort($this->_immediateChildren, array($this, '_childSortAlgorithm'));
        }
    }

    private function _childSortAlgorithm($a, $b)
    {
        $typePrefs = array();
        $types = array(strtolower($a->getContentType()), strtolower($b->getContentType()));

        foreach ($types as $type) {
            $typePrefs[] = array_key_exists($type, $this->_alternativePartOrder) ? $this->_alternativePartOrder[$type] : max($this->_alternativePartOrder) + 1;
        }

        return $typePrefs[0] >= $typePrefs[1] ? 1 : -1;
    }

    // -- Destructor

    /**
     * Empties it's own contents from the cache.
     */
    public function __destruct()
    {
        $this->_cache->clearAll($this->_cacheKey);
    }

    /**
     * Throws an Exception if the id passed does not comply with RFC 2822.
     *
     * @param string $id
     *
     * @throws Swift_RfcComplianceException
     */
    private function _assertValidId($id)
    {
        if (!preg_match('/^'.$this->_grammar->getDefinition('id-left').'@'.$this->_grammar->getDefinition('id-right').'$/D', $id)) {
            throw new Swift_RfcComplianceException('Invalid ID given <'.$id.'>');
        }
    }

    /**
     * Make a deep copy of object.
     */
    public function __clone()
    {
        $this->_headers = clone $this->_headers;
        $this->_encoder = clone $this->_encoder;
        $this->_cacheKey = md5(uniqid(getmypid().mt_rand(), true));
        $children = array();
        foreach ($this->_children as $pos => $child) {
            $children[$pos] = clone $child;
        }
        $this->setChildren($children);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A MIME part, in a multipart message.
 *
 * @author Chris Corbyn
 */
class Swift_MimePart extends Swift_Mime_MimePart
{
    /**
     * Create a new MimePart.
     *
     * Details may be optionally passed into the constructor.
     *
     * @param string $body
     * @param string $contentType
     * @param string $charset
     */
    public function __construct($body = null, $contentType = null, $charset = null)
    {
        call_user_func_array(
            array($this, 'Swift_Mime_MimePart::__construct'),
            Swift_DependencyContainer::getInstance()
                ->createDependenciesFor('mime.part')
            );

        if (!isset($charset)) {
            $charset = Swift_DependencyContainer::getInstance()
                ->lookup('properties.charset');
        }
        $this->setBody($body);
        $this->setCharset($charset);
        if ($contentType) {
            $this->setContentType($contentType);
        }
    }

    /**
     * Create a new MimePart.
     *
     * @param string $body
     * @param string $contentType
     * @param string $charset
     *
     * @return Swift_Mime_MimePart
     */
    public static function newInstance($body = null, $contentType = null, $charset = null)
    {
        return new self($body, $contentType, $charset);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Pretends messages have been sent, but just ignores them.
 *
 * @author Fabien Potencier
 */
class Swift_NullTransport extends Swift_Transport_NullTransport
{
    /**
     * Create a new NullTransport.
     */
    public function __construct()
    {
        call_user_func_array(
            array($this, 'Swift_Transport_NullTransport::__construct'),
            Swift_DependencyContainer::getInstance()
                ->createDependenciesFor('transport.null')
        );
    }

    /**
     * Create a new NullTransport instance.
     *
     * @return Swift_NullTransport
     */
    public static function newInstance()
    {
        return new self();
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An abstract means of reading data.
 *
 * Classes implementing this interface may use a subsystem which requires less
 * memory than working with large strings of data.
 *
 * @author Chris Corbyn
 */
interface Swift_OutputByteStream
{
    /**
     * Reads $length bytes from the stream into a string and moves the pointer
     * through the stream by $length.
     *
     * If less bytes exist than are requested the remaining bytes are given instead.
     * If no bytes are remaining at all, boolean false is returned.
     *
     * @param int $length
     *
     * @throws Swift_IoException
     *
     * @return string|bool
     */
    public function read($length);

    /**
     * Move the internal read pointer to $byteOffset in the stream.
     *
     * @param int $byteOffset
     *
     * @throws Swift_IoException
     *
     * @return bool
     */
    public function setReadPointer($byteOffset);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Reduces network flooding when sending large amounts of mail.
 *
 * @author Chris Corbyn
 */
class Swift_Plugins_AntiFloodPlugin implements Swift_Events_SendListener, Swift_Plugins_Sleeper
{
    /**
     * The number of emails to send before restarting Transport.
     *
     * @var int
     */
    private $_threshold;

    /**
     * The number of seconds to sleep for during a restart.
     *
     * @var int
     */
    private $_sleep;

    /**
     * The internal counter.
     *
     * @var int
     */
    private $_counter = 0;

    /**
     * The Sleeper instance for sleeping.
     *
     * @var Swift_Plugins_Sleeper
     */
    private $_sleeper;

    /**
     * Create a new AntiFloodPlugin with $threshold and $sleep time.
     *
     * @param int                   $threshold
     * @param int                   $sleep     time
     * @param Swift_Plugins_Sleeper $sleeper   (not needed really)
     */
    public function __construct($threshold = 99, $sleep = 0, Swift_Plugins_Sleeper $sleeper = null)
    {
        $this->setThreshold($threshold);
        $this->setSleepTime($sleep);
        $this->_sleeper = $sleeper;
    }

    /**
     * Set the number of emails to send before restarting.
     *
     * @param int $threshold
     */
    public function setThreshold($threshold)
    {
        $this->_threshold = $threshold;
    }

    /**
     * Get the number of emails to send before restarting.
     *
     * @return int
     */
    public function getThreshold()
    {
        return $this->_threshold;
    }

    /**
     * Set the number of seconds to sleep for during a restart.
     *
     * @param int $sleep time
     */
    public function setSleepTime($sleep)
    {
        $this->_sleep = $sleep;
    }

    /**
     * Get the number of seconds to sleep for during a restart.
     *
     * @return int
     */
    public function getSleepTime()
    {
        return $this->_sleep;
    }

    /**
     * Invoked immediately before the Message is sent.
     *
     * @param Swift_Events_SendEvent $evt
     */
    public function beforeSendPerformed(Swift_Events_SendEvent $evt)
    {
    }

    /**
     * Invoked immediately after the Message is sent.
     *
     * @param Swift_Events_SendEvent $evt
     */
    public function sendPerformed(Swift_Events_SendEvent $evt)
    {
        ++$this->_counter;
        if ($this->_counter >= $this->_threshold) {
            $transport = $evt->getTransport();
            $transport->stop();
            if ($this->_sleep) {
                $this->sleep($this->_sleep);
            }
            $transport->start();
            $this->_counter = 0;
        }
    }

    /**
     * Sleep for $seconds.
     *
     * @param int $seconds
     */
    public function sleep($seconds)
    {
        if (isset($this->_sleeper)) {
            $this->_sleeper->sleep($seconds);
        } else {
            sleep($seconds);
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Reduces network flooding when sending large amounts of mail.
 *
 * @author Chris Corbyn
 */
class Swift_Plugins_BandwidthMonitorPlugin implements Swift_Events_SendListener, Swift_Events_CommandListener, Swift_Events_ResponseListener, Swift_InputByteStream
{
    /**
     * The outgoing traffic counter.
     *
     * @var int
     */
    private $_out = 0;

    /**
     * The incoming traffic counter.
     *
     * @var int
     */
    private $_in = 0;

    /** Bound byte streams */
    private $_mirrors = array();

    /**
     * Not used.
     */
    public function beforeSendPerformed(Swift_Events_SendEvent $evt)
    {
    }

    /**
     * Invoked immediately after the Message is sent.
     *
     * @param Swift_Events_SendEvent $evt
     */
    public function sendPerformed(Swift_Events_SendEvent $evt)
    {
        $message = $evt->getMessage();
        $message->toByteStream($this);
    }

    /**
     * Invoked immediately following a command being sent.
     *
     * @param Swift_Events_CommandEvent $evt
     */
    public function commandSent(Swift_Events_CommandEvent $evt)
    {
        $command = $evt->getCommand();
        $this->_out += strlen($command);
    }

    /**
     * Invoked immediately following a response coming back.
     *
     * @param Swift_Events_ResponseEvent $evt
     */
    public function responseReceived(Swift_Events_ResponseEvent $evt)
    {
        $response = $evt->getResponse();
        $this->_in += strlen($response);
    }

    /**
     * Called when a message is sent so that the outgoing counter can be increased.
     *
     * @param string $bytes
     */
    public function write($bytes)
    {
        $this->_out += strlen($bytes);
        foreach ($this->_mirrors as $stream) {
            $stream->write($bytes);
        }
    }

    /**
     * Not used.
     */
    public function commit()
    {
    }

    /**
     * Attach $is to this stream.
     *
     * The stream acts as an observer, receiving all data that is written.
     * All {@link write()} and {@link flushBuffers()} operations will be mirrored.
     *
     * @param Swift_InputByteStream $is
     */
    public function bind(Swift_InputByteStream $is)
    {
        $this->_mirrors[] = $is;
    }

    /**
     * Remove an already bound stream.
     *
     * If $is is not bound, no errors will be raised.
     * If the stream currently has any buffered data it will be written to $is
     * before unbinding occurs.
     *
     * @param Swift_InputByteStream $is
     */
    public function unbind(Swift_InputByteStream $is)
    {
        foreach ($this->_mirrors as $k => $stream) {
            if ($is === $stream) {
                unset($this->_mirrors[$k]);
            }
        }
    }

    /**
     * Not used.
     */
    public function flushBuffers()
    {
        foreach ($this->_mirrors as $stream) {
            $stream->flushBuffers();
        }
    }

    /**
     * Get the total number of bytes sent to the server.
     *
     * @return int
     */
    public function getBytesOut()
    {
        return $this->_out;
    }

    /**
     * Get the total number of bytes received from the server.
     *
     * @return int
     */
    public function getBytesIn()
    {
        return $this->_in;
    }

    /**
     * Reset the internal counters to zero.
     */
    public function reset()
    {
        $this->_out = 0;
        $this->_in = 0;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Allows customization of Messages on-the-fly.
 *
 * @author Chris Corbyn
 */
interface Swift_Plugins_Decorator_Replacements
{
    /**
     * Return the array of replacements for $address.
     *
     * This method is invoked once for every single recipient of a message.
     *
     * If no replacements can be found, an empty value (NULL) should be returned
     * and no replacements will then be made on the message.
     *
     * @param string $address
     *
     * @return array
     */
    public function getReplacementsFor($address);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Allows customization of Messages on-the-fly.
 *
 * @author Chris Corbyn
 * @author Fabien Potencier
 */
class Swift_Plugins_DecoratorPlugin implements Swift_Events_SendListener, Swift_Plugins_Decorator_Replacements
{
    /** The replacement map */
    private $_replacements;

    /** The body as it was before replacements */
    private $_originalBody;

    /** The original headers of the message, before replacements */
    private $_originalHeaders = array();

    /** Bodies of children before they are replaced */
    private $_originalChildBodies = array();

    /** The Message that was last replaced */
    private $_lastMessage;

    /**
     * Create a new DecoratorPlugin with $replacements.
     *
     * The $replacements can either be an associative array, or an implementation
     * of {@link Swift_Plugins_Decorator_Replacements}.
     *
     * When using an array, it should be of the form:
     * <code>
     * $replacements = array(
     *  "address1@domain.tld" => array("{a}" => "b", "{c}" => "d"),
     *  "address2@domain.tld" => array("{a}" => "x", "{c}" => "y")
     * )
     * </code>
     *
     * When using an instance of {@link Swift_Plugins_Decorator_Replacements},
     * the object should return just the array of replacements for the address
     * given to {@link Swift_Plugins_Decorator_Replacements::getReplacementsFor()}.
     *
     * @param mixed $replacements Array or Swift_Plugins_Decorator_Replacements
     */
    public function __construct($replacements)
    {
        $this->setReplacements($replacements);
    }

    /**
     * Sets replacements.
     *
     * @param mixed $replacements Array or Swift_Plugins_Decorator_Replacements
     *
     * @see __construct()
     */
    public function setReplacements($replacements)
    {
        if (!($replacements instanceof Swift_Plugins_Decorator_Replacements)) {
            $this->_replacements = (array) $replacements;
        } else {
            $this->_replacements = $replacements;
        }
    }

    /**
     * Invoked immediately before the Message is sent.
     *
     * @param Swift_Events_SendEvent $evt
     */
    public function beforeSendPerformed(Swift_Events_SendEvent $evt)
    {
        $message = $evt->getMessage();
        $this->_restoreMessage($message);
        $to = array_keys($message->getTo());
        $address = array_shift($to);
        if ($replacements = $this->getReplacementsFor($address)) {
            $body = $message->getBody();
            $search = array_keys($replacements);
            $replace = array_values($replacements);
            $bodyReplaced = str_replace(
                $search, $replace, $body
                );
            if ($body != $bodyReplaced) {
                $this->_originalBody = $body;
                $message->setBody($bodyReplaced);
            }

            foreach ($message->getHeaders()->getAll() as $header) {
                $body = $header->getFieldBodyModel();
                $count = 0;
                if (is_array($body)) {
                    $bodyReplaced = array();
                    foreach ($body as $key => $value) {
                        $count1 = 0;
                        $count2 = 0;
                        $key = is_string($key) ? str_replace($search, $replace, $key, $count1) : $key;
                        $value = is_string($value) ? str_replace($search, $replace, $value, $count2) : $value;
                        $bodyReplaced[$key] = $value;

                        if (!$count && ($count1 || $count2)) {
                            $count = 1;
                        }
                    }
                } else {
                    $bodyReplaced = str_replace($search, $replace, $body, $count);
                }

                if ($count) {
                    $this->_originalHeaders[$header->getFieldName()] = $body;
                    $header->setFieldBodyModel($bodyReplaced);
                }
            }

            $children = (array) $message->getChildren();
            foreach ($children as $child) {
                list($type) = sscanf($child->getContentType(), '%[^/]/%s');
                if ('text' == $type) {
                    $body = $child->getBody();
                    $bodyReplaced = str_replace(
                        $search, $replace, $body
                        );
                    if ($body != $bodyReplaced) {
                        $child->setBody($bodyReplaced);
                        $this->_originalChildBodies[$child->getId()] = $body;
                    }
                }
            }
            $this->_lastMessage = $message;
        }
    }

    /**
     * Find a map of replacements for the address.
     *
     * If this plugin was provided with a delegate instance of
     * {@link Swift_Plugins_Decorator_Replacements} then the call will be
     * delegated to it.  Otherwise, it will attempt to find the replacements
     * from the array provided in the constructor.
     *
     * If no replacements can be found, an empty value (NULL) is returned.
     *
     * @param string $address
     *
     * @return array
     */
    public function getReplacementsFor($address)
    {
        if ($this->_replacements instanceof Swift_Plugins_Decorator_Replacements) {
            return $this->_replacements->getReplacementsFor($address);
        }

        return isset($this->_replacements[$address]) ? $this->_replacements[$address] : null;
    }

    /**
     * Invoked immediately after the Message is sent.
     *
     * @param Swift_Events_SendEvent $evt
     */
    public function sendPerformed(Swift_Events_SendEvent $evt)
    {
        $this->_restoreMessage($evt->getMessage());
    }

    /** Restore a changed message back to its original state */
    private function _restoreMessage(Swift_Mime_Message $message)
    {
        if ($this->_lastMessage === $message) {
            if (isset($this->_originalBody)) {
                $message->setBody($this->_originalBody);
                $this->_originalBody = null;
            }
            if (!empty($this->_originalHeaders)) {
                foreach ($message->getHeaders()->getAll() as $header) {
                    if (array_key_exists($header->getFieldName(), $this->_originalHeaders)) {
                        $header->setFieldBodyModel($this->_originalHeaders[$header->getFieldName()]);
                    }
                }
                $this->_originalHeaders = array();
            }
            if (!empty($this->_originalChildBodies)) {
                $children = (array) $message->getChildren();
                foreach ($children as $child) {
                    $id = $child->getId();
                    if (array_key_exists($id, $this->_originalChildBodies)) {
                        $child->setBody($this->_originalChildBodies[$id]);
                    }
                }
                $this->_originalChildBodies = array();
            }
            $this->_lastMessage = null;
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2009 Fabien Potencier
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Replaces the sender of a message.
 *
 * @author Arjen Brouwer
 */
class Swift_Plugins_ImpersonatePlugin implements Swift_Events_SendListener
{
    /**
     * The sender to impersonate.
     *
     * @var String
     */
    private $_sender;

    /**
     * Create a new ImpersonatePlugin to impersonate $sender.
     *
     * @param string $sender address
     */
    public function __construct($sender)
    {
        $this->_sender = $sender;
    }

    /**
     * Invoked immediately before the Message is sent.
     *
     * @param Swift_Events_SendEvent $evt
     */
    public function beforeSendPerformed(Swift_Events_SendEvent $evt)
    {
        $message = $evt->getMessage();
        $headers = $message->getHeaders();

        // save current recipients
        $headers->addPathHeader('X-Swift-Return-Path', $message->getReturnPath());

        // replace them with the one to send to
        $message->setReturnPath($this->_sender);
    }

    /**
     * Invoked immediately after the Message is sent.
     *
     * @param Swift_Events_SendEvent $evt
     */
    public function sendPerformed(Swift_Events_SendEvent $evt)
    {
        $message = $evt->getMessage();

        // restore original headers
        $headers = $message->getHeaders();

        if ($headers->has('X-Swift-Return-Path')) {
            $message->setReturnPath($headers->get('X-Swift-Return-Path')->getAddress());
            $headers->removeAll('X-Swift-Return-Path');
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Logs events in the Transport system.
 *
 * @author Chris Corbyn
 */
interface Swift_Plugins_Logger
{
    /**
     * Add a log entry.
     *
     * @param string $entry
     */
    public function add($entry);

    /**
     * Clear the log contents.
     */
    public function clear();

    /**
     * Get this log as a string.
     *
     * @return string
     */
    public function dump();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Does real time logging of Transport level information.
 *
 * @author Chris Corbyn
 */
class Swift_Plugins_LoggerPlugin implements Swift_Events_CommandListener, Swift_Events_ResponseListener, Swift_Events_TransportChangeListener, Swift_Events_TransportExceptionListener, Swift_Plugins_Logger
{
    /** The logger which is delegated to */
    private $_logger;

    /**
     * Create a new LoggerPlugin using $logger.
     *
     * @param Swift_Plugins_Logger $logger
     */
    public function __construct(Swift_Plugins_Logger $logger)
    {
        $this->_logger = $logger;
    }

    /**
     * Add a log entry.
     *
     * @param string $entry
     */
    public function add($entry)
    {
        $this->_logger->add($entry);
    }

    /**
     * Clear the log contents.
     */
    public function clear()
    {
        $this->_logger->clear();
    }

    /**
     * Get this log as a string.
     *
     * @return string
     */
    public function dump()
    {
        return $this->_logger->dump();
    }

    /**
     * Invoked immediately following a command being sent.
     *
     * @param Swift_Events_CommandEvent $evt
     */
    public function commandSent(Swift_Events_CommandEvent $evt)
    {
        $command = $evt->getCommand();
        $this->_logger->add(sprintf('>> %s', $command));
    }

    /**
     * Invoked immediately following a response coming back.
     *
     * @param Swift_Events_ResponseEvent $evt
     */
    public function responseReceived(Swift_Events_ResponseEvent $evt)
    {
        $response = $evt->getResponse();
        $this->_logger->add(sprintf('<< %s', $response));
    }

    /**
     * Invoked just before a Transport is started.
     *
     * @param Swift_Events_TransportChangeEvent $evt
     */
    public function beforeTransportStarted(Swift_Events_TransportChangeEvent $evt)
    {
        $transportName = get_class($evt->getSource());
        $this->_logger->add(sprintf('++ Starting %s', $transportName));
    }

    /**
     * Invoked immediately after the Transport is started.
     *
     * @param Swift_Events_TransportChangeEvent $evt
     */
    public function transportStarted(Swift_Events_TransportChangeEvent $evt)
    {
        $transportName = get_class($evt->getSource());
        $this->_logger->add(sprintf('++ %s started', $transportName));
    }

    /**
     * Invoked just before a Transport is stopped.
     *
     * @param Swift_Events_TransportChangeEvent $evt
     */
    public function beforeTransportStopped(Swift_Events_TransportChangeEvent $evt)
    {
        $transportName = get_class($evt->getSource());
        $this->_logger->add(sprintf('++ Stopping %s', $transportName));
    }

    /**
     * Invoked immediately after the Transport is stopped.
     *
     * @param Swift_Events_TransportChangeEvent $evt
     */
    public function transportStopped(Swift_Events_TransportChangeEvent $evt)
    {
        $transportName = get_class($evt->getSource());
        $this->_logger->add(sprintf('++ %s stopped', $transportName));
    }

    /**
     * Invoked as a TransportException is thrown in the Transport system.
     *
     * @param Swift_Events_TransportExceptionEvent $evt
     */
    public function exceptionThrown(Swift_Events_TransportExceptionEvent $evt)
    {
        $e = $evt->getException();
        $message = $e->getMessage();
        $code = $e->getCode();
        $this->_logger->add(sprintf('!! %s (code: %s)', $message, $code));
        $message .= PHP_EOL;
        $message .= 'Log data:'.PHP_EOL;
        $message .= $this->_logger->dump();
        $evt->cancelBubble();
        throw new Swift_TransportException($message, $code, $e->getPrevious());
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Logs to an Array backend.
 *
 * @author Chris Corbyn
 */
class Swift_Plugins_Loggers_ArrayLogger implements Swift_Plugins_Logger
{
    /**
     * The log contents.
     *
     * @var array
     */
    private $_log = array();

    /**
     * Max size of the log.
     *
     * @var int
     */
    private $_size = 0;

    /**
     * Create a new ArrayLogger with a maximum of $size entries.
     *
     * @var int
     */
    public function __construct($size = 50)
    {
        $this->_size = $size;
    }

    /**
     * Add a log entry.
     *
     * @param string $entry
     */
    public function add($entry)
    {
        $this->_log[] = $entry;
        while (count($this->_log) > $this->_size) {
            array_shift($this->_log);
        }
    }

    /**
     * Clear the log contents.
     */
    public function clear()
    {
        $this->_log = array();
    }

    /**
     * Get this log as a string.
     *
     * @return string
     */
    public function dump()
    {
        return implode(PHP_EOL, $this->_log);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Prints all log messages in real time.
 *
 * @author Chris Corbyn
 */
class Swift_Plugins_Loggers_EchoLogger implements Swift_Plugins_Logger
{
    /** Whether or not HTML should be output */
    private $_isHtml;

    /**
     * Create a new EchoLogger.
     *
     * @param bool $isHtml
     */
    public function __construct($isHtml = true)
    {
        $this->_isHtml = $isHtml;
    }

    /**
     * Add a log entry.
     *
     * @param string $entry
     */
    public function add($entry)
    {
        if ($this->_isHtml) {
            printf('%s%s%s', htmlspecialchars($entry, ENT_QUOTES), '<br />', PHP_EOL);
        } else {
            printf('%s%s', $entry, PHP_EOL);
        }
    }

    /**
     * Not implemented.
     */
    public function clear()
    {
    }

    /**
     * Not implemented.
     */
    public function dump()
    {
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2011 Fabien Potencier
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Stores all sent emails for further usage.
 *
 * @author Fabien Potencier
 */
class Swift_Plugins_MessageLogger implements Swift_Events_SendListener
{
    /**
     * @var array
     */
    private $messages;

    public function __construct()
    {
        $this->messages = array();
    }

    /**
     * Get the message list.
     *
     * @return array
     */
    public function getMessages()
    {
        return $this->messages;
    }

    /**
     * Get the message count.
     *
     * @return int count
     */
    public function countMessages()
    {
        return count($this->messages);
    }

    /**
     * Empty the message list.
     */
    public function clear()
    {
        $this->messages = array();
    }

    /**
     * Invoked immediately before the Message is sent.
     *
     * @param Swift_Events_SendEvent $evt
     */
    public function beforeSendPerformed(Swift_Events_SendEvent $evt)
    {
        $this->messages[] = clone $evt->getMessage();
    }

    /**
     * Invoked immediately after the Message is sent.
     *
     * @param Swift_Events_SendEvent $evt
     */
    public function sendPerformed(Swift_Events_SendEvent $evt)
    {
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Pop3Connection interface for connecting and disconnecting to a POP3 host.
 *
 * @author Chris Corbyn
 */
interface Swift_Plugins_Pop_Pop3Connection
{
    /**
     * Connect to the POP3 host and throw an Exception if it fails.
     *
     * @throws Swift_Plugins_Pop_Pop3Exception
     */
    public function connect();

    /**
     * Disconnect from the POP3 host and throw an Exception if it fails.
     *
     * @throws Swift_Plugins_Pop_Pop3Exception
     */
    public function disconnect();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Pop3Exception thrown when an error occurs connecting to a POP3 host.
 *
 * @author Chris Corbyn
 */
class Swift_Plugins_Pop_Pop3Exception extends Swift_IoException
{
    /**
     * Create a new Pop3Exception with $message.
     *
     * @param string $message
     */
    public function __construct($message)
    {
        parent::__construct($message);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Makes sure a connection to a POP3 host has been established prior to connecting to SMTP.
 *
 * @author Chris Corbyn
 */
class Swift_Plugins_PopBeforeSmtpPlugin implements Swift_Events_TransportChangeListener, Swift_Plugins_Pop_Pop3Connection
{
    /** A delegate connection to use (mostly a test hook) */
    private $_connection;

    /** Hostname of the POP3 server */
    private $_host;

    /** Port number to connect on */
    private $_port;

    /** Encryption type to use (if any) */
    private $_crypto;

    /** Username to use (if any) */
    private $_username;

    /** Password to use (if any) */
    private $_password;

    /** Established connection via TCP socket */
    private $_socket;

    /** Connect timeout in seconds */
    private $_timeout = 10;

    /** SMTP Transport to bind to */
    private $_transport;

    /**
     * Create a new PopBeforeSmtpPlugin for $host and $port.
     *
     * @param string $host
     * @param int    $port
     * @param string $crypto as "tls" or "ssl"
     */
    public function __construct($host, $port = 110, $crypto = null)
    {
        $this->_host = $host;
        $this->_port = $port;
        $this->_crypto = $crypto;
    }

    /**
     * Create a new PopBeforeSmtpPlugin for $host and $port.
     *
     * @param string $host
     * @param int    $port
     * @param string $crypto as "tls" or "ssl"
     *
     * @return Swift_Plugins_PopBeforeSmtpPlugin
     */
    public static function newInstance($host, $port = 110, $crypto = null)
    {
        return new self($host, $port, $crypto);
    }

    /**
     * Set a Pop3Connection to delegate to instead of connecting directly.
     *
     * @param Swift_Plugins_Pop_Pop3Connection $connection
     *
     * @return Swift_Plugins_PopBeforeSmtpPlugin
     */
    public function setConnection(Swift_Plugins_Pop_Pop3Connection $connection)
    {
        $this->_connection = $connection;

        return $this;
    }

    /**
     * Bind this plugin to a specific SMTP transport instance.
     *
     * @param Swift_Transport
     */
    public function bindSmtp(Swift_Transport $smtp)
    {
        $this->_transport = $smtp;
    }

    /**
     * Set the connection timeout in seconds (default 10).
     *
     * @param int $timeout
     *
     * @return Swift_Plugins_PopBeforeSmtpPlugin
     */
    public function setTimeout($timeout)
    {
        $this->_timeout = (int) $timeout;

        return $this;
    }

    /**
     * Set the username to use when connecting (if needed).
     *
     * @param string $username
     *
     * @return Swift_Plugins_PopBeforeSmtpPlugin
     */
    public function setUsername($username)
    {
        $this->_username = $username;

        return $this;
    }

    /**
     * Set the password to use when connecting (if needed).
     *
     * @param string $password
     *
     * @return Swift_Plugins_PopBeforeSmtpPlugin
     */
    public function setPassword($password)
    {
        $this->_password = $password;

        return $this;
    }

    /**
     * Connect to the POP3 host and authenticate.
     *
     * @throws Swift_Plugins_Pop_Pop3Exception if connection fails
     */
    public function connect()
    {
        if (isset($this->_connection)) {
            $this->_connection->connect();
        } else {
            if (!isset($this->_socket)) {
                if (!$socket = fsockopen(
                    $this->_getHostString(), $this->_port, $errno, $errstr, $this->_timeout)) {
                    throw new Swift_Plugins_Pop_Pop3Exception(
                        sprintf('Failed to connect to POP3 host [%s]: %s', $this->_host, $errstr)
                    );
                }
                $this->_socket = $socket;

                if (false === $greeting = fgets($this->_socket)) {
                    throw new Swift_Plugins_Pop_Pop3Exception(
                        sprintf('Failed to connect to POP3 host [%s]', trim($greeting))
                    );
                }

                $this->_assertOk($greeting);

                if ($this->_username) {
                    $this->_command(sprintf("USER %s\r\n", $this->_username));
                    $this->_command(sprintf("PASS %s\r\n", $this->_password));
                }
            }
        }
    }

    /**
     * Disconnect from the POP3 host.
     */
    public function disconnect()
    {
        if (isset($this->_connection)) {
            $this->_connection->disconnect();
        } else {
            $this->_command("QUIT\r\n");
            if (!fclose($this->_socket)) {
                throw new Swift_Plugins_Pop_Pop3Exception(
                    sprintf('POP3 host [%s] connection could not be stopped', $this->_host)
                );
            }
            $this->_socket = null;
        }
    }

    /**
     * Invoked just before a Transport is started.
     *
     * @param Swift_Events_TransportChangeEvent $evt
     */
    public function beforeTransportStarted(Swift_Events_TransportChangeEvent $evt)
    {
        if (isset($this->_transport)) {
            if ($this->_transport !== $evt->getTransport()) {
                return;
            }
        }

        $this->connect();
        $this->disconnect();
    }

    /**
     * Not used.
     */
    public function transportStarted(Swift_Events_TransportChangeEvent $evt)
    {
    }

    /**
     * Not used.
     */
    public function beforeTransportStopped(Swift_Events_TransportChangeEvent $evt)
    {
    }

    /**
     * Not used.
     */
    public function transportStopped(Swift_Events_TransportChangeEvent $evt)
    {
    }

    private function _command($command)
    {
        if (!fwrite($this->_socket, $command)) {
            throw new Swift_Plugins_Pop_Pop3Exception(
                sprintf('Failed to write command [%s] to POP3 host', trim($command))
            );
        }

        if (false === $response = fgets($this->_socket)) {
            throw new Swift_Plugins_Pop_Pop3Exception(
                sprintf('Failed to read from POP3 host after command [%s]', trim($command))
            );
        }

        $this->_assertOk($response);

        return $response;
    }

    private function _assertOk($response)
    {
        if (substr($response, 0, 3) != '+OK') {
            throw new Swift_Plugins_Pop_Pop3Exception(
                sprintf('POP3 command failed [%s]', trim($response))
            );
        }
    }

    private function _getHostString()
    {
        $host = $this->_host;
        switch (strtolower($this->_crypto)) {
            case 'ssl':
                $host = 'ssl://'.$host;
                break;

            case 'tls':
                $host = 'tls://'.$host;
                break;
        }

        return $host;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2009 Fabien Potencier
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Redirects all email to a single recipient.
 *
 * @author Fabien Potencier
 */
class Swift_Plugins_RedirectingPlugin implements Swift_Events_SendListener
{
    /**
     * The recipient who will receive all messages.
     *
     * @var mixed
     */
    private $_recipient;

    /**
     * List of regular expression for recipient whitelisting.
     *
     * @var array
     */
    private $_whitelist = array();

    /**
     * Create a new RedirectingPlugin.
     *
     * @param mixed $recipient
     * @param array $whitelist
     */
    public function __construct($recipient, array $whitelist = array())
    {
        $this->_recipient = $recipient;
        $this->_whitelist = $whitelist;
    }

    /**
     * Set the recipient of all messages.
     *
     * @param mixed $recipient
     */
    public function setRecipient($recipient)
    {
        $this->_recipient = $recipient;
    }

    /**
     * Get the recipient of all messages.
     *
     * @return mixed
     */
    public function getRecipient()
    {
        return $this->_recipient;
    }

    /**
     * Set a list of regular expressions to whitelist certain recipients.
     *
     * @param array $whitelist
     */
    public function setWhitelist(array $whitelist)
    {
        $this->_whitelist = $whitelist;
    }

    /**
     * Get the whitelist.
     *
     * @return array
     */
    public function getWhitelist()
    {
        return $this->_whitelist;
    }

    /**
     * Invoked immediately before the Message is sent.
     *
     * @param Swift_Events_SendEvent $evt
     */
    public function beforeSendPerformed(Swift_Events_SendEvent $evt)
    {
        $message = $evt->getMessage();
        $headers = $message->getHeaders();

        // conditionally save current recipients

        if ($headers->has('to')) {
            $headers->addMailboxHeader('X-Swift-To', $message->getTo());
        }

        if ($headers->has('cc')) {
            $headers->addMailboxHeader('X-Swift-Cc', $message->getCc());
        }

        if ($headers->has('bcc')) {
            $headers->addMailboxHeader('X-Swift-Bcc', $message->getBcc());
        }

        // Filter remaining headers against whitelist
        $this->_filterHeaderSet($headers, 'To');
        $this->_filterHeaderSet($headers, 'Cc');
        $this->_filterHeaderSet($headers, 'Bcc');

        // Add each hard coded recipient
        $to = $message->getTo();
        if (null === $to) {
            $to = array();
        }

        foreach ((array) $this->_recipient as $recipient) {
            if (!array_key_exists($recipient, $to)) {
                $message->addTo($recipient);
            }
        }
    }

    /**
     * Filter header set against a whitelist of regular expressions.
     *
     * @param Swift_Mime_HeaderSet $headerSet
     * @param string               $type
     */
    private function _filterHeaderSet(Swift_Mime_HeaderSet $headerSet, $type)
    {
        foreach ($headerSet->getAll($type) as $headers) {
            $headers->setNameAddresses($this->_filterNameAddresses($headers->getNameAddresses()));
        }
    }

    /**
     * Filtered list of addresses => name pairs.
     *
     * @param array $recipients
     *
     * @return array
     */
    private function _filterNameAddresses(array $recipients)
    {
        $filtered = array();

        foreach ($recipients as $address => $name) {
            if ($this->_isWhitelisted($address)) {
                $filtered[$address] = $name;
            }
        }

        return $filtered;
    }

    /**
     * Matches address against whitelist of regular expressions.
     *
     * @param $recipient
     *
     * @return bool
     */
    protected function _isWhitelisted($recipient)
    {
        if (in_array($recipient, (array) $this->_recipient)) {
            return true;
        }

        foreach ($this->_whitelist as $pattern) {
            if (preg_match($pattern, $recipient)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Invoked immediately after the Message is sent.
     *
     * @param Swift_Events_SendEvent $evt
     */
    public function sendPerformed(Swift_Events_SendEvent $evt)
    {
        $this->_restoreMessage($evt->getMessage());
    }

    private function _restoreMessage(Swift_Mime_Message $message)
    {
        // restore original headers
        $headers = $message->getHeaders();

        if ($headers->has('X-Swift-To')) {
            $message->setTo($headers->get('X-Swift-To')->getNameAddresses());
            $headers->removeAll('X-Swift-To');
        } else {
            $message->setTo(null);
        }

        if ($headers->has('X-Swift-Cc')) {
            $message->setCc($headers->get('X-Swift-Cc')->getNameAddresses());
            $headers->removeAll('X-Swift-Cc');
        }

        if ($headers->has('X-Swift-Bcc')) {
            $message->setBcc($headers->get('X-Swift-Bcc')->getNameAddresses());
            $headers->removeAll('X-Swift-Bcc');
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * The Reporter plugin sends pass/fail notification to a Reporter.
 *
 * @author Chris Corbyn
 */
interface Swift_Plugins_Reporter
{
    /** The recipient was accepted for delivery */
    const RESULT_PASS = 0x01;

    /** The recipient could not be accepted */
    const RESULT_FAIL = 0x10;

    /**
     * Notifies this ReportNotifier that $address failed or succeeded.
     *
     * @param Swift_Mime_Message $message
     * @param string             $address
     * @param int                $result  from {@link RESULT_PASS, RESULT_FAIL}
     */
    public function notify(Swift_Mime_Message $message, $address, $result);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Does real time reporting of pass/fail for each recipient.
 *
 * @author Chris Corbyn
 */
class Swift_Plugins_ReporterPlugin implements Swift_Events_SendListener
{
    /**
     * The reporter backend which takes notifications.
     *
     * @var Swift_Plugins_Reporter
     */
    private $_reporter;

    /**
     * Create a new ReporterPlugin using $reporter.
     *
     * @param Swift_Plugins_Reporter $reporter
     */
    public function __construct(Swift_Plugins_Reporter $reporter)
    {
        $this->_reporter = $reporter;
    }

    /**
     * Not used.
     */
    public function beforeSendPerformed(Swift_Events_SendEvent $evt)
    {
    }

    /**
     * Invoked immediately after the Message is sent.
     *
     * @param Swift_Events_SendEvent $evt
     */
    public function sendPerformed(Swift_Events_SendEvent $evt)
    {
        $message = $evt->getMessage();
        $failures = array_flip($evt->getFailedRecipients());
        foreach ((array) $message->getTo() as $address => $null) {
            $this->_reporter->notify($message, $address, array_key_exists($address, $failures) ? Swift_Plugins_Reporter::RESULT_FAIL : Swift_Plugins_Reporter::RESULT_PASS);
        }
        foreach ((array) $message->getCc() as $address => $null) {
            $this->_reporter->notify($message, $address, array_key_exists($address, $failures) ? Swift_Plugins_Reporter::RESULT_FAIL : Swift_Plugins_Reporter::RESULT_PASS);
        }
        foreach ((array) $message->getBcc() as $address => $null) {
            $this->_reporter->notify($message, $address, array_key_exists($address, $failures) ? Swift_Plugins_Reporter::RESULT_FAIL : Swift_Plugins_Reporter::RESULT_PASS);
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A reporter which "collects" failures for the Reporter plugin.
 *
 * @author Chris Corbyn
 */
class Swift_Plugins_Reporters_HitReporter implements Swift_Plugins_Reporter
{
    /**
     * The list of failures.
     *
     * @var array
     */
    private $_failures = array();

    private $_failures_cache = array();

    /**
     * Notifies this ReportNotifier that $address failed or succeeded.
     *
     * @param Swift_Mime_Message $message
     * @param string             $address
     * @param int                $result  from {@link RESULT_PASS, RESULT_FAIL}
     */
    public function notify(Swift_Mime_Message $message, $address, $result)
    {
        if (self::RESULT_FAIL == $result && !isset($this->_failures_cache[$address])) {
            $this->_failures[] = $address;
            $this->_failures_cache[$address] = true;
        }
    }

    /**
     * Get an array of addresses for which delivery failed.
     *
     * @return array
     */
    public function getFailedRecipients()
    {
        return $this->_failures;
    }

    /**
     * Clear the buffer (empty the list).
     */
    public function clear()
    {
        $this->_failures = $this->_failures_cache = array();
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A HTML output reporter for the Reporter plugin.
 *
 * @author Chris Corbyn
 */
class Swift_Plugins_Reporters_HtmlReporter implements Swift_Plugins_Reporter
{
    /**
     * Notifies this ReportNotifier that $address failed or succeeded.
     *
     * @param Swift_Mime_Message $message
     * @param string             $address
     * @param int                $result  from {@see RESULT_PASS, RESULT_FAIL}
     */
    public function notify(Swift_Mime_Message $message, $address, $result)
    {
        if (self::RESULT_PASS == $result) {
            echo '<div style="color: #fff; background: #006600; padding: 2px; margin: 2px;">'.PHP_EOL;
            echo 'PASS '.$address.PHP_EOL;
            echo '</div>'.PHP_EOL;
            flush();
        } else {
            echo '<div style="color: #fff; background: #880000; padding: 2px; margin: 2px;">'.PHP_EOL;
            echo 'FAIL '.$address.PHP_EOL;
            echo '</div>'.PHP_EOL;
            flush();
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Sleeps for a duration of time.
 *
 * @author Chris Corbyn
 */
interface Swift_Plugins_Sleeper
{
    /**
     * Sleep for $seconds.
     *
     * @param int $seconds
     */
    public function sleep($seconds);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Throttles the rate at which emails are sent.
 *
 * @author Chris Corbyn
 */
class Swift_Plugins_ThrottlerPlugin extends Swift_Plugins_BandwidthMonitorPlugin implements Swift_Plugins_Sleeper, Swift_Plugins_Timer
{
    /** Flag for throttling in bytes per minute */
    const BYTES_PER_MINUTE = 0x01;

    /** Flag for throttling in emails per second (Amazon SES) */
    const MESSAGES_PER_SECOND = 0x11;

    /** Flag for throttling in emails per minute */
    const MESSAGES_PER_MINUTE = 0x10;

    /**
     * The Sleeper instance for sleeping.
     *
     * @var Swift_Plugins_Sleeper
     */
    private $_sleeper;

    /**
     * The Timer instance which provides the timestamp.
     *
     * @var Swift_Plugins_Timer
     */
    private $_timer;

    /**
     * The time at which the first email was sent.
     *
     * @var int
     */
    private $_start;

    /**
     * The rate at which messages should be sent.
     *
     * @var int
     */
    private $_rate;

    /**
     * The mode for throttling.
     *
     * This is {@link BYTES_PER_MINUTE} or {@link MESSAGES_PER_MINUTE}
     *
     * @var int
     */
    private $_mode;

    /**
     * An internal counter of the number of messages sent.
     *
     * @var int
     */
    private $_messages = 0;

    /**
     * Create a new ThrottlerPlugin.
     *
     * @param int                   $rate
     * @param int                   $mode,   defaults to {@link BYTES_PER_MINUTE}
     * @param Swift_Plugins_Sleeper $sleeper (only needed in testing)
     * @param Swift_Plugins_Timer   $timer   (only needed in testing)
     */
    public function __construct($rate, $mode = self::BYTES_PER_MINUTE, Swift_Plugins_Sleeper $sleeper = null, Swift_Plugins_Timer $timer = null)
    {
        $this->_rate = $rate;
        $this->_mode = $mode;
        $this->_sleeper = $sleeper;
        $this->_timer = $timer;
    }

    /**
     * Invoked immediately before the Message is sent.
     *
     * @param Swift_Events_SendEvent $evt
     */
    public function beforeSendPerformed(Swift_Events_SendEvent $evt)
    {
        $time = $this->getTimestamp();
        if (!isset($this->_start)) {
            $this->_start = $time;
        }
        $duration = $time - $this->_start;

        switch ($this->_mode) {
            case self::BYTES_PER_MINUTE:
                $sleep = $this->_throttleBytesPerMinute($duration);
                break;
            case self::MESSAGES_PER_SECOND:
                $sleep = $this->_throttleMessagesPerSecond($duration);
                break;
            case self::MESSAGES_PER_MINUTE:
                $sleep = $this->_throttleMessagesPerMinute($duration);
                break;
            default:
                $sleep = 0;
                break;
        }

        if ($sleep > 0) {
            $this->sleep($sleep);
        }
    }

    /**
     * Invoked when a Message is sent.
     *
     * @param Swift_Events_SendEvent $evt
     */
    public function sendPerformed(Swift_Events_SendEvent $evt)
    {
        parent::sendPerformed($evt);
        ++$this->_messages;
    }

    /**
     * Sleep for $seconds.
     *
     * @param int $seconds
     */
    public function sleep($seconds)
    {
        if (isset($this->_sleeper)) {
            $this->_sleeper->sleep($seconds);
        } else {
            sleep($seconds);
        }
    }

    /**
     * Get the current UNIX timestamp.
     *
     * @return int
     */
    public function getTimestamp()
    {
        if (isset($this->_timer)) {
            return $this->_timer->getTimestamp();
        }

        return time();
    }

    /**
     * Get a number of seconds to sleep for.
     *
     * @param int $timePassed
     *
     * @return int
     */
    private function _throttleBytesPerMinute($timePassed)
    {
        $expectedDuration = $this->getBytesOut() / ($this->_rate / 60);

        return (int) ceil($expectedDuration - $timePassed);
    }

    /**
     * Get a number of seconds to sleep for.
     *
     * @param int $timePassed
     *
     * @return int
     */
    private function _throttleMessagesPerSecond($timePassed)
    {
        $expectedDuration = $this->_messages / ($this->_rate);

        return (int) ceil($expectedDuration - $timePassed);
    }

    /**
     * Get a number of seconds to sleep for.
     *
     * @param int $timePassed
     *
     * @return int
     */
    private function _throttleMessagesPerMinute($timePassed)
    {
        $expectedDuration = $this->_messages / ($this->_rate / 60);

        return (int) ceil($expectedDuration - $timePassed);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Provides timestamp data.
 *
 * @author Chris Corbyn
 */
interface Swift_Plugins_Timer
{
    /**
     * Get the current UNIX timestamp.
     *
     * @return int
     */
    public function getTimestamp();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Changes some global preference settings in Swift Mailer.
 *
 * @author Chris Corbyn
 */
class Swift_Preferences
{
    /** Singleton instance */
    private static $_instance = null;

    /** Constructor not to be used */
    private function __construct()
    {
    }

    /**
     * Gets the instance of Preferences.
     *
     * @return Swift_Preferences
     */
    public static function getInstance()
    {
        if (!isset(self::$_instance)) {
            self::$_instance = new self();
        }

        return self::$_instance;
    }

    /**
     * Set the default charset used.
     *
     * @param string $charset
     *
     * @return Swift_Preferences
     */
    public function setCharset($charset)
    {
        Swift_DependencyContainer::getInstance()
            ->register('properties.charset')->asValue($charset);

        return $this;
    }

    /**
     * Set the directory where temporary files can be saved.
     *
     * @param string $dir
     *
     * @return Swift_Preferences
     */
    public function setTempDir($dir)
    {
        Swift_DependencyContainer::getInstance()
            ->register('tempdir')->asValue($dir);

        return $this;
    }

    /**
     * Set the type of cache to use (i.e. "disk" or "array").
     *
     * @param string $type
     *
     * @return Swift_Preferences
     */
    public function setCacheType($type)
    {
        Swift_DependencyContainer::getInstance()
            ->register('cache')->asAliasOf(sprintf('cache.%s', $type));

        return $this;
    }

    /**
     * Set the QuotedPrintable dot escaper preference.
     *
     * @param bool $dotEscape
     *
     * @return Swift_Preferences
     */
    public function setQPDotEscape($dotEscape)
    {
        $dotEscape = !empty($dotEscape);
        Swift_DependencyContainer::getInstance()
            ->register('mime.qpcontentencoder')
            ->asNewInstanceOf('Swift_Mime_ContentEncoder_QpContentEncoder')
            ->withDependencies(array('mime.charstream', 'mime.bytecanonicalizer'))
            ->addConstructorValue($dotEscape);

        return $this;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Creates StreamFilters.
 *
 * @author Chris Corbyn
 */
interface Swift_ReplacementFilterFactory
{
    /**
     * Create a filter to replace $search with $replace.
     *
     * @param mixed $search
     * @param mixed $replace
     *
     * @return Swift_StreamFilter
     */
    public function createFilter($search, $replace);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * RFC Compliance Exception class.
 *
 * @author Chris Corbyn
 */
class Swift_RfcComplianceException extends Swift_SwiftException
{
    /**
     * Create a new RfcComplianceException with $message.
     *
     * @param string $message
     */
    public function __construct($message)
    {
        parent::__construct($message);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * SendmailTransport for sending mail through a Sendmail/Postfix (etc..) binary.
 *
 * @author Chris Corbyn
 */
class Swift_SendmailTransport extends Swift_Transport_SendmailTransport
{
    /**
     * Create a new SendmailTransport, optionally using $command for sending.
     *
     * @param string $command
     */
    public function __construct($command = '/usr/sbin/sendmail -bs')
    {
        call_user_func_array(
            array($this, 'Swift_Transport_SendmailTransport::__construct'),
            Swift_DependencyContainer::getInstance()
                ->createDependenciesFor('transport.sendmail')
            );

        $this->setCommand($command);
    }

    /**
     * Create a new SendmailTransport instance.
     *
     * @param string $command
     *
     * @return Swift_SendmailTransport
     */
    public static function newInstance($command = '/usr/sbin/sendmail -bs')
    {
        return new self($command);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Signed Message, message that can be signed using a signer.
 *
 * This class is only kept for compatibility
 *
 *
 * @author     Xavier De Cock <xdecock@gmail.com>
 *
 * @deprecated
 */
class Swift_SignedMessage extends Swift_Message
{
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Base Class of Signer Infrastructure.
 *
 *
 * @author Xavier De Cock <xdecock@gmail.com>
 */
interface Swift_Signer
{
    public function reset();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Body Signer Interface used to apply Body-Based Signature to a message.
 *
 * @author Xavier De Cock <xdecock@gmail.com>
 */
interface Swift_Signers_BodySigner extends Swift_Signer
{
    /**
     * Change the Swift_Signed_Message to apply the singing.
     *
     * @param Swift_Message $message
     *
     * @return Swift_Signers_BodySigner
     */
    public function signMessage(Swift_Message $message);

    /**
     * Return the list of header a signer might tamper.
     *
     * @return array
     */
    public function getAlteredHeaders();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * DKIM Signer used to apply DKIM Signature to a message.
 *
 * @author Xavier De Cock <xdecock@gmail.com>
 */
class Swift_Signers_DKIMSigner implements Swift_Signers_HeaderSigner
{
    /**
     * PrivateKey.
     *
     * @var string
     */
    protected $_privateKey;

    /**
     * DomainName.
     *
     * @var string
     */
    protected $_domainName;

    /**
     * Selector.
     *
     * @var string
     */
    protected $_selector;

    /**
     * Hash algorithm used.
     *
     * @var string
     */
    protected $_hashAlgorithm = 'rsa-sha1';

    /**
     * Body canon method.
     *
     * @var string
     */
    protected $_bodyCanon = 'simple';

    /**
     * Header canon method.
     *
     * @var string
     */
    protected $_headerCanon = 'simple';

    /**
     * Headers not being signed.
     *
     * @var array
     */
    protected $_ignoredHeaders = array('return-path' => true);

    /**
     * Signer identity.
     *
     * @var string
     */
    protected $_signerIdentity;

    /**
     * BodyLength.
     *
     * @var int
     */
    protected $_bodyLen = 0;

    /**
     * Maximum signedLen.
     *
     * @var int
     */
    protected $_maxLen = PHP_INT_MAX;

    /**
     * Embbed bodyLen in signature.
     *
     * @var bool
     */
    protected $_showLen = false;

    /**
     * When the signature has been applied (true means time()), false means not embedded.
     *
     * @var mixed
     */
    protected $_signatureTimestamp = true;

    /**
     * When will the signature expires false means not embedded, if sigTimestamp is auto
     * Expiration is relative, otherwhise it's absolute.
     *
     * @var int
     */
    protected $_signatureExpiration = false;

    /**
     * Must we embed signed headers?
     *
     * @var bool
     */
    protected $_debugHeaders = false;

    // work variables
    /**
     * Headers used to generate hash.
     *
     * @var array
     */
    protected $_signedHeaders = array();

    /**
     * If debugHeaders is set store debugDatas here.
     *
     * @var string
     */
    private $_debugHeadersData = '';

    /**
     * Stores the bodyHash.
     *
     * @var string
     */
    private $_bodyHash = '';

    /**
     * Stores the signature header.
     *
     * @var Swift_Mime_Headers_ParameterizedHeader
     */
    protected $_dkimHeader;

    private $_bodyHashHandler;

    private $_headerHash;

    private $_headerCanonData = '';

    private $_bodyCanonEmptyCounter = 0;

    private $_bodyCanonIgnoreStart = 2;

    private $_bodyCanonSpace = false;

    private $_bodyCanonLastChar = null;

    private $_bodyCanonLine = '';

    private $_bound = array();

    /**
     * Constructor.
     *
     * @param string $privateKey
     * @param string $domainName
     * @param string $selector
     */
    public function __construct($privateKey, $domainName, $selector)
    {
        $this->_privateKey = $privateKey;
        $this->_domainName = $domainName;
        $this->_signerIdentity = '@'.$domainName;
        $this->_selector = $selector;
    }

    /**
     * Instanciate DKIMSigner.
     *
     * @param string $privateKey
     * @param string $domainName
     * @param string $selector
     *
     * @return Swift_Signers_DKIMSigner
     */
    public static function newInstance($privateKey, $domainName, $selector)
    {
        return new static($privateKey, $domainName, $selector);
    }

    /**
     * Reset the Signer.
     *
     * @see Swift_Signer::reset()
     */
    public function reset()
    {
        $this->_headerHash = null;
        $this->_signedHeaders = array();
        $this->_bodyHash = null;
        $this->_bodyHashHandler = null;
        $this->_bodyCanonIgnoreStart = 2;
        $this->_bodyCanonEmptyCounter = 0;
        $this->_bodyCanonLastChar = null;
        $this->_bodyCanonSpace = false;
    }

    /**
     * Writes $bytes to the end of the stream.
     *
     * Writing may not happen immediately if the stream chooses to buffer.  If
     * you want to write these bytes with immediate effect, call {@link commit()}
     * after calling write().
     *
     * This method returns the sequence ID of the write (i.e. 1 for first, 2 for
     * second, etc etc).
     *
     * @param string $bytes
     *
     * @throws Swift_IoException
     *
     * @return int
     */
    public function write($bytes)
    {
        $this->_canonicalizeBody($bytes);
        foreach ($this->_bound as $is) {
            $is->write($bytes);
        }
    }

    /**
     * For any bytes that are currently buffered inside the stream, force them
     * off the buffer.
     *
     * @throws Swift_IoException
     */
    public function commit()
    {
        // Nothing to do
        return;
    }

    /**
     * Attach $is to this stream.
     * The stream acts as an observer, receiving all data that is written.
     * All {@link write()} and {@link flushBuffers()} operations will be mirrored.
     *
     * @param Swift_InputByteStream $is
     */
    public function bind(Swift_InputByteStream $is)
    {
        // Don't have to mirror anything
        $this->_bound[] = $is;

        return;
    }

    /**
     * Remove an already bound stream.
     * If $is is not bound, no errors will be raised.
     * If the stream currently has any buffered data it will be written to $is
     * before unbinding occurs.
     *
     * @param Swift_InputByteStream $is
     */
    public function unbind(Swift_InputByteStream $is)
    {
        // Don't have to mirror anything
        foreach ($this->_bound as $k => $stream) {
            if ($stream === $is) {
                unset($this->_bound[$k]);

                return;
            }
        }

        return;
    }

    /**
     * Flush the contents of the stream (empty it) and set the internal pointer
     * to the beginning.
     *
     * @throws Swift_IoException
     */
    public function flushBuffers()
    {
        $this->reset();
    }

    /**
     * Set hash_algorithm, must be one of rsa-sha256 | rsa-sha1 defaults to rsa-sha256.
     *
     * @param string $hash
     *
     * @return Swift_Signers_DKIMSigner
     */
    public function setHashAlgorithm($hash)
    {
        // Unable to sign with rsa-sha256
        if ($hash == 'rsa-sha1') {
            $this->_hashAlgorithm = 'rsa-sha1';
        } else {
            $this->_hashAlgorithm = 'rsa-sha256';
        }

        return $this;
    }

    /**
     * Set the body canonicalization algorithm.
     *
     * @param string $canon
     *
     * @return Swift_Signers_DKIMSigner
     */
    public function setBodyCanon($canon)
    {
        if ($canon == 'relaxed') {
            $this->_bodyCanon = 'relaxed';
        } else {
            $this->_bodyCanon = 'simple';
        }

        return $this;
    }

    /**
     * Set the header canonicalization algorithm.
     *
     * @param string $canon
     *
     * @return Swift_Signers_DKIMSigner
     */
    public function setHeaderCanon($canon)
    {
        if ($canon == 'relaxed') {
            $this->_headerCanon = 'relaxed';
        } else {
            $this->_headerCanon = 'simple';
        }

        return $this;
    }

    /**
     * Set the signer identity.
     *
     * @param string $identity
     *
     * @return Swift_Signers_DKIMSigner
     */
    public function setSignerIdentity($identity)
    {
        $this->_signerIdentity = $identity;

        return $this;
    }

    /**
     * Set the length of the body to sign.
     *
     * @param mixed $len (bool or int)
     *
     * @return Swift_Signers_DKIMSigner
     */
    public function setBodySignedLen($len)
    {
        if ($len === true) {
            $this->_showLen = true;
            $this->_maxLen = PHP_INT_MAX;
        } elseif ($len === false) {
            $this->_showLen = false;
            $this->_maxLen = PHP_INT_MAX;
        } else {
            $this->_showLen = true;
            $this->_maxLen = (int) $len;
        }

        return $this;
    }

    /**
     * Set the signature timestamp.
     *
     * @param int $time A timestamp
     *
     * @return Swift_Signers_DKIMSigner
     */
    public function setSignatureTimestamp($time)
    {
        $this->_signatureTimestamp = $time;

        return $this;
    }

    /**
     * Set the signature expiration timestamp.
     *
     * @param int $time A timestamp
     *
     * @return Swift_Signers_DKIMSigner
     */
    public function setSignatureExpiration($time)
    {
        $this->_signatureExpiration = $time;

        return $this;
    }

    /**
     * Enable / disable the DebugHeaders.
     *
     * @param bool $debug
     *
     * @return Swift_Signers_DKIMSigner
     */
    public function setDebugHeaders($debug)
    {
        $this->_debugHeaders = (bool) $debug;

        return $this;
    }

    /**
     * Start Body.
     */
    public function startBody()
    {
        // Init
        switch ($this->_hashAlgorithm) {
            case 'rsa-sha256':
                $this->_bodyHashHandler = hash_init('sha256');
                break;
            case 'rsa-sha1':
                $this->_bodyHashHandler = hash_init('sha1');
                break;
        }
        $this->_bodyCanonLine = '';
    }

    /**
     * End Body.
     */
    public function endBody()
    {
        $this->_endOfBody();
    }

    /**
     * Returns the list of Headers Tampered by this plugin.
     *
     * @return array
     */
    public function getAlteredHeaders()
    {
        if ($this->_debugHeaders) {
            return array('DKIM-Signature', 'X-DebugHash');
        } else {
            return array('DKIM-Signature');
        }
    }

    /**
     * Adds an ignored Header.
     *
     * @param string $header_name
     *
     * @return Swift_Signers_DKIMSigner
     */
    public function ignoreHeader($header_name)
    {
        $this->_ignoredHeaders[strtolower($header_name)] = true;

        return $this;
    }

    /**
     * Set the headers to sign.
     *
     * @param Swift_Mime_HeaderSet $headers
     *
     * @return Swift_Signers_DKIMSigner
     */
    public function setHeaders(Swift_Mime_HeaderSet $headers)
    {
        $this->_headerCanonData = '';
        // Loop through Headers
        $listHeaders = $headers->listAll();
        foreach ($listHeaders as $hName) {
            // Check if we need to ignore Header
            if (!isset($this->_ignoredHeaders[strtolower($hName)])) {
                if ($headers->has($hName)) {
                    $tmp = $headers->getAll($hName);
                    foreach ($tmp as $header) {
                        if ($header->getFieldBody() != '') {
                            $this->_addHeader($header->toString());
                            $this->_signedHeaders[] = $header->getFieldName();
                        }
                    }
                }
            }
        }

        return $this;
    }

    /**
     * Add the signature to the given Headers.
     *
     * @param Swift_Mime_HeaderSet $headers
     *
     * @return Swift_Signers_DKIMSigner
     */
    public function addSignature(Swift_Mime_HeaderSet $headers)
    {
        // Prepare the DKIM-Signature
        $params = array('v' => '1', 'a' => $this->_hashAlgorithm, 'bh' => base64_encode($this->_bodyHash), 'd' => $this->_domainName, 'h' => implode(': ', $this->_signedHeaders), 'i' => $this->_signerIdentity, 's' => $this->_selector);
        if ($this->_bodyCanon != 'simple') {
            $params['c'] = $this->_headerCanon.'/'.$this->_bodyCanon;
        } elseif ($this->_headerCanon != 'simple') {
            $params['c'] = $this->_headerCanon;
        }
        if ($this->_showLen) {
            $params['l'] = $this->_bodyLen;
        }
        if ($this->_signatureTimestamp === true) {
            $params['t'] = time();
            if ($this->_signatureExpiration !== false) {
                $params['x'] = $params['t'] + $this->_signatureExpiration;
            }
        } else {
            if ($this->_signatureTimestamp !== false) {
                $params['t'] = $this->_signatureTimestamp;
            }
            if ($this->_signatureExpiration !== false) {
                $params['x'] = $this->_signatureExpiration;
            }
        }
        if ($this->_debugHeaders) {
            $params['z'] = implode('|', $this->_debugHeadersData);
        }
        $string = '';
        foreach ($params as $k => $v) {
            $string .= $k.'='.$v.'; ';
        }
        $string = trim($string);
        $headers->addTextHeader('DKIM-Signature', $string);
        // Add the last DKIM-Signature
        $tmp = $headers->getAll('DKIM-Signature');
        $this->_dkimHeader = end($tmp);
        $this->_addHeader(trim($this->_dkimHeader->toString())."\r\n b=", true);
        $this->_endOfHeaders();
        if ($this->_debugHeaders) {
            $headers->addTextHeader('X-DebugHash', base64_encode($this->_headerHash));
        }
        $this->_dkimHeader->setValue($string.' b='.trim(chunk_split(base64_encode($this->_getEncryptedHash()), 73, ' ')));

        return $this;
    }

    /* Private helpers */

    protected function _addHeader($header, $is_sig = false)
    {
        switch ($this->_headerCanon) {
            case 'relaxed':
                // Prepare Header and cascade
                $exploded = explode(':', $header, 2);
                $name = strtolower(trim($exploded[0]));
                $value = str_replace("\r\n", '', $exploded[1]);
                $value = preg_replace("/[ \t][ \t]+/", ' ', $value);
                $header = $name.':'.trim($value).($is_sig ? '' : "\r\n");
            case 'simple':
                // Nothing to do
        }
        $this->_addToHeaderHash($header);
    }

    /**
     * @deprecated This method is currently useless in this class but it must be
     *             kept for BC reasons due to its "protected" scope. This method
     *             might be overriden by custom client code.
     */
    protected function _endOfHeaders()
    {
    }

    protected function _canonicalizeBody($string)
    {
        $len = strlen($string);
        $canon = '';
        $method = ($this->_bodyCanon == 'relaxed');
        for ($i = 0; $i < $len; ++$i) {
            if ($this->_bodyCanonIgnoreStart > 0) {
                --$this->_bodyCanonIgnoreStart;
                continue;
            }
            switch ($string[$i]) {
                case "\r":
                    $this->_bodyCanonLastChar = "\r";
                    break;
                case "\n":
                    if ($this->_bodyCanonLastChar == "\r") {
                        if ($method) {
                            $this->_bodyCanonSpace = false;
                        }
                        if ($this->_bodyCanonLine == '') {
                            ++$this->_bodyCanonEmptyCounter;
                        } else {
                            $this->_bodyCanonLine = '';
                            $canon .= "\r\n";
                        }
                    } else {
                        // Wooops Error
                        // todo handle it but should never happen
                    }
                    break;
                case ' ':
                case "\t":
                    if ($method) {
                        $this->_bodyCanonSpace = true;
                        break;
                    }
                default:
                    if ($this->_bodyCanonEmptyCounter > 0) {
                        $canon .= str_repeat("\r\n", $this->_bodyCanonEmptyCounter);
                        $this->_bodyCanonEmptyCounter = 0;
                    }
                    if ($this->_bodyCanonSpace) {
                        $this->_bodyCanonLine .= ' ';
                        $canon .= ' ';
                        $this->_bodyCanonSpace = false;
                    }
                    $this->_bodyCanonLine .= $string[$i];
                    $canon .= $string[$i];
            }
        }
        $this->_addToBodyHash($canon);
    }

    protected function _endOfBody()
    {
        // Add trailing Line return if last line is non empty
        if (strlen($this->_bodyCanonLine) > 0) {
            $this->_addToBodyHash("\r\n");
        }
        $this->_bodyHash = hash_final($this->_bodyHashHandler, true);
    }

    private function _addToBodyHash($string)
    {
        $len = strlen($string);
        if ($len > ($new_len = ($this->_maxLen - $this->_bodyLen))) {
            $string = substr($string, 0, $new_len);
            $len = $new_len;
        }
        hash_update($this->_bodyHashHandler, $string);
        $this->_bodyLen += $len;
    }

    private function _addToHeaderHash($header)
    {
        if ($this->_debugHeaders) {
            $this->_debugHeadersData[] = trim($header);
        }
        $this->_headerCanonData .= $header;
    }

    /**
     * @throws Swift_SwiftException
     *
     * @return string
     */
    private function _getEncryptedHash()
    {
        $signature = '';
        switch ($this->_hashAlgorithm) {
            case 'rsa-sha1':
                $algorithm = OPENSSL_ALGO_SHA1;
                break;
            case 'rsa-sha256':
                $algorithm = OPENSSL_ALGO_SHA256;
                break;
        }
        $pkeyId = openssl_get_privatekey($this->_privateKey);
        if (!$pkeyId) {
            throw new Swift_SwiftException('Unable to load DKIM Private Key ['.openssl_error_string().']');
        }
        if (openssl_sign($this->_headerCanonData, $signature, $pkeyId, $algorithm)) {
            return $signature;
        }
        throw new Swift_SwiftException('Unable to sign DKIM Hash ['.openssl_error_string().']');
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * DomainKey Signer used to apply DomainKeys Signature to a message.
 *
 * @author Xavier De Cock <xdecock@gmail.com>
 */
class Swift_Signers_DomainKeySigner implements Swift_Signers_HeaderSigner
{
    /**
     * PrivateKey.
     *
     * @var string
     */
    protected $_privateKey;

    /**
     * DomainName.
     *
     * @var string
     */
    protected $_domainName;

    /**
     * Selector.
     *
     * @var string
     */
    protected $_selector;

    /**
     * Hash algorithm used.
     *
     * @var string
     */
    protected $_hashAlgorithm = 'rsa-sha1';

    /**
     * Canonisation method.
     *
     * @var string
     */
    protected $_canon = 'simple';

    /**
     * Headers not being signed.
     *
     * @var array
     */
    protected $_ignoredHeaders = array();

    /**
     * Signer identity.
     *
     * @var string
     */
    protected $_signerIdentity;

    /**
     * Must we embed signed headers?
     *
     * @var bool
     */
    protected $_debugHeaders = false;

    // work variables
    /**
     * Headers used to generate hash.
     *
     * @var array
     */
    private $_signedHeaders = array();

    /**
     * Stores the signature header.
     *
     * @var Swift_Mime_Headers_ParameterizedHeader
     */
    protected $_domainKeyHeader;

    /**
     * Hash Handler.
     *
     * @var resource|null
     */
    private $_hashHandler;

    private $_hash;

    private $_canonData = '';

    private $_bodyCanonEmptyCounter = 0;

    private $_bodyCanonIgnoreStart = 2;

    private $_bodyCanonSpace = false;

    private $_bodyCanonLastChar = null;

    private $_bodyCanonLine = '';

    private $_bound = array();

    /**
     * Constructor.
     *
     * @param string $privateKey
     * @param string $domainName
     * @param string $selector
     */
    public function __construct($privateKey, $domainName, $selector)
    {
        $this->_privateKey = $privateKey;
        $this->_domainName = $domainName;
        $this->_signerIdentity = '@'.$domainName;
        $this->_selector = $selector;
    }

    /**
     * Instanciate DomainKeySigner.
     *
     * @param string $privateKey
     * @param string $domainName
     * @param string $selector
     *
     * @return Swift_Signers_DomainKeySigner
     */
    public static function newInstance($privateKey, $domainName, $selector)
    {
        return new static($privateKey, $domainName, $selector);
    }

    /**
     * Resets internal states.
     *
     * @return Swift_Signers_DomainKeySigner
     */
    public function reset()
    {
        $this->_hash = null;
        $this->_hashHandler = null;
        $this->_bodyCanonIgnoreStart = 2;
        $this->_bodyCanonEmptyCounter = 0;
        $this->_bodyCanonLastChar = null;
        $this->_bodyCanonSpace = false;

        return $this;
    }

    /**
     * Writes $bytes to the end of the stream.
     *
     * Writing may not happen immediately if the stream chooses to buffer.  If
     * you want to write these bytes with immediate effect, call {@link commit()}
     * after calling write().
     *
     * This method returns the sequence ID of the write (i.e. 1 for first, 2 for
     * second, etc etc).
     *
     * @param string $bytes
     *
     * @throws Swift_IoException
     *
     * @return int
     * @return Swift_Signers_DomainKeySigner
     */
    public function write($bytes)
    {
        $this->_canonicalizeBody($bytes);
        foreach ($this->_bound as $is) {
            $is->write($bytes);
        }

        return $this;
    }

    /**
     * For any bytes that are currently buffered inside the stream, force them
     * off the buffer.
     *
     * @throws Swift_IoException
     *
     * @return Swift_Signers_DomainKeySigner
     */
    public function commit()
    {
        // Nothing to do
        return $this;
    }

    /**
     * Attach $is to this stream.
     * The stream acts as an observer, receiving all data that is written.
     * All {@link write()} and {@link flushBuffers()} operations will be mirrored.
     *
     * @param Swift_InputByteStream $is
     *
     * @return Swift_Signers_DomainKeySigner
     */
    public function bind(Swift_InputByteStream $is)
    {
        // Don't have to mirror anything
        $this->_bound[] = $is;

        return $this;
    }

    /**
     * Remove an already bound stream.
     * If $is is not bound, no errors will be raised.
     * If the stream currently has any buffered data it will be written to $is
     * before unbinding occurs.
     *
     * @param Swift_InputByteStream $is
     *
     * @return Swift_Signers_DomainKeySigner
     */
    public function unbind(Swift_InputByteStream $is)
    {
        // Don't have to mirror anything
        foreach ($this->_bound as $k => $stream) {
            if ($stream === $is) {
                unset($this->_bound[$k]);

                return;
            }
        }

        return $this;
    }

    /**
     * Flush the contents of the stream (empty it) and set the internal pointer
     * to the beginning.
     *
     * @throws Swift_IoException
     *
     * @return Swift_Signers_DomainKeySigner
     */
    public function flushBuffers()
    {
        $this->reset();

        return $this;
    }

    /**
     * Set hash_algorithm, must be one of rsa-sha256 | rsa-sha1 defaults to rsa-sha256.
     *
     * @param string $hash
     *
     * @return Swift_Signers_DomainKeySigner
     */
    public function setHashAlgorithm($hash)
    {
        $this->_hashAlgorithm = 'rsa-sha1';

        return $this;
    }

    /**
     * Set the canonicalization algorithm.
     *
     * @param string $canon simple | nofws defaults to simple
     *
     * @return Swift_Signers_DomainKeySigner
     */
    public function setCanon($canon)
    {
        if ($canon == 'nofws') {
            $this->_canon = 'nofws';
        } else {
            $this->_canon = 'simple';
        }

        return $this;
    }

    /**
     * Set the signer identity.
     *
     * @param string $identity
     *
     * @return Swift_Signers_DomainKeySigner
     */
    public function setSignerIdentity($identity)
    {
        $this->_signerIdentity = $identity;

        return $this;
    }

    /**
     * Enable / disable the DebugHeaders.
     *
     * @param bool $debug
     *
     * @return Swift_Signers_DomainKeySigner
     */
    public function setDebugHeaders($debug)
    {
        $this->_debugHeaders = (bool) $debug;

        return $this;
    }

    /**
     * Start Body.
     */
    public function startBody()
    {
    }

    /**
     * End Body.
     */
    public function endBody()
    {
        $this->_endOfBody();
    }

    /**
     * Returns the list of Headers Tampered by this plugin.
     *
     * @return array
     */
    public function getAlteredHeaders()
    {
        if ($this->_debugHeaders) {
            return array('DomainKey-Signature', 'X-DebugHash');
        }

        return array('DomainKey-Signature');
    }

    /**
     * Adds an ignored Header.
     *
     * @param string $header_name
     *
     * @return Swift_Signers_DomainKeySigner
     */
    public function ignoreHeader($header_name)
    {
        $this->_ignoredHeaders[strtolower($header_name)] = true;

        return $this;
    }

    /**
     * Set the headers to sign.
     *
     * @param Swift_Mime_HeaderSet $headers
     *
     * @return Swift_Signers_DomainKeySigner
     */
    public function setHeaders(Swift_Mime_HeaderSet $headers)
    {
        $this->_startHash();
        $this->_canonData = '';
        // Loop through Headers
        $listHeaders = $headers->listAll();
        foreach ($listHeaders as $hName) {
            // Check if we need to ignore Header
            if (!isset($this->_ignoredHeaders[strtolower($hName)])) {
                if ($headers->has($hName)) {
                    $tmp = $headers->getAll($hName);
                    foreach ($tmp as $header) {
                        if ($header->getFieldBody() != '') {
                            $this->_addHeader($header->toString());
                            $this->_signedHeaders[] = $header->getFieldName();
                        }
                    }
                }
            }
        }
        $this->_endOfHeaders();

        return $this;
    }

    /**
     * Add the signature to the given Headers.
     *
     * @param Swift_Mime_HeaderSet $headers
     *
     * @return Swift_Signers_DomainKeySigner
     */
    public function addSignature(Swift_Mime_HeaderSet $headers)
    {
        // Prepare the DomainKey-Signature Header
        $params = array('a' => $this->_hashAlgorithm, 'b' => chunk_split(base64_encode($this->_getEncryptedHash()), 73, ' '), 'c' => $this->_canon, 'd' => $this->_domainName, 'h' => implode(': ', $this->_signedHeaders), 'q' => 'dns', 's' => $this->_selector);
        $string = '';
        foreach ($params as $k => $v) {
            $string .= $k.'='.$v.'; ';
        }
        $string = trim($string);
        $headers->addTextHeader('DomainKey-Signature', $string);

        return $this;
    }

    /* Private helpers */

    protected function _addHeader($header)
    {
        switch ($this->_canon) {
            case 'nofws':
                // Prepare Header and cascade
                $exploded = explode(':', $header, 2);
                $name = strtolower(trim($exploded[0]));
                $value = str_replace("\r\n", '', $exploded[1]);
                $value = preg_replace("/[ \t][ \t]+/", ' ', $value);
                $header = $name.':'.trim($value)."\r\n";
            case 'simple':
                // Nothing to do
        }
        $this->_addToHash($header);
    }

    protected function _endOfHeaders()
    {
        $this->_bodyCanonEmptyCounter = 1;
    }

    protected function _canonicalizeBody($string)
    {
        $len = strlen($string);
        $canon = '';
        $nofws = ($this->_canon == 'nofws');
        for ($i = 0; $i < $len; ++$i) {
            if ($this->_bodyCanonIgnoreStart > 0) {
                --$this->_bodyCanonIgnoreStart;
                continue;
            }
            switch ($string[$i]) {
                case "\r":
                    $this->_bodyCanonLastChar = "\r";
                    break;
                case "\n":
                    if ($this->_bodyCanonLastChar == "\r") {
                        if ($nofws) {
                            $this->_bodyCanonSpace = false;
                        }
                        if ($this->_bodyCanonLine == '') {
                            ++$this->_bodyCanonEmptyCounter;
                        } else {
                            $this->_bodyCanonLine = '';
                            $canon .= "\r\n";
                        }
                    } else {
                        // Wooops Error
                        throw new Swift_SwiftException('Invalid new line sequence in mail found \n without preceding \r');
                    }
                    break;
                case ' ':
                case "\t":
                case "\x09": //HTAB
                    if ($nofws) {
                        $this->_bodyCanonSpace = true;
                        break;
                    }
                default:
                    if ($this->_bodyCanonEmptyCounter > 0) {
                        $canon .= str_repeat("\r\n", $this->_bodyCanonEmptyCounter);
                        $this->_bodyCanonEmptyCounter = 0;
                    }
                    $this->_bodyCanonLine .= $string[$i];
                    $canon .= $string[$i];
            }
        }
        $this->_addToHash($canon);
    }

    protected function _endOfBody()
    {
        if (strlen($this->_bodyCanonLine) > 0) {
            $this->_addToHash("\r\n");
        }
        $this->_hash = hash_final($this->_hashHandler, true);
    }

    private function _addToHash($string)
    {
        $this->_canonData .= $string;
        hash_update($this->_hashHandler, $string);
    }

    private function _startHash()
    {
        // Init
        switch ($this->_hashAlgorithm) {
            case 'rsa-sha1':
                $this->_hashHandler = hash_init('sha1');
                break;
        }
        $this->_bodyCanonLine = '';
    }

    /**
     * @throws Swift_SwiftException
     *
     * @return string
     */
    private function _getEncryptedHash()
    {
        $signature = '';
        $pkeyId = openssl_get_privatekey($this->_privateKey);
        if (!$pkeyId) {
            throw new Swift_SwiftException('Unable to load DomainKey Private Key ['.openssl_error_string().']');
        }
        if (openssl_sign($this->_canonData, $signature, $pkeyId, OPENSSL_ALGO_SHA1)) {
            return $signature;
        }
        throw new Swift_SwiftException('Unable to sign DomainKey Hash  ['.openssl_error_string().']');
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Header Signer Interface used to apply Header-Based Signature to a message.
 *
 * @author Xavier De Cock <xdecock@gmail.com>
 */
interface Swift_Signers_HeaderSigner extends Swift_Signer, Swift_InputByteStream
{
    /**
     * Exclude an header from the signed headers.
     *
     * @param string $header_name
     *
     * @return Swift_Signers_HeaderSigner
     */
    public function ignoreHeader($header_name);

    /**
     * Prepare the Signer to get a new Body.
     *
     * @return Swift_Signers_HeaderSigner
     */
    public function startBody();

    /**
     * Give the signal that the body has finished streaming.
     *
     * @return Swift_Signers_HeaderSigner
     */
    public function endBody();

    /**
     * Give the headers already given.
     *
     * @param Swift_Mime_SimpleHeaderSet $headers
     *
     * @return Swift_Signers_HeaderSigner
     */
    public function setHeaders(Swift_Mime_HeaderSet $headers);

    /**
     * Add the header(s) to the headerSet.
     *
     * @param Swift_Mime_HeaderSet $headers
     *
     * @return Swift_Signers_HeaderSigner
     */
    public function addSignature(Swift_Mime_HeaderSet $headers);

    /**
     * Return the list of header a signer might tamper.
     *
     * @return array
     */
    public function getAlteredHeaders();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * DKIM Signer used to apply DKIM Signature to a message
 * Takes advantage of pecl extension.
 *
 * @author Xavier De Cock <xdecock@gmail.com>
 */
class Swift_Signers_OpenDKIMSigner extends Swift_Signers_DKIMSigner
{
    private $_peclLoaded = false;

    private $_dkimHandler = null;

    private $dropFirstLF = true;

    const CANON_RELAXED = 1;
    const CANON_SIMPLE = 2;
    const SIG_RSA_SHA1 = 3;
    const SIG_RSA_SHA256 = 4;

    public function __construct($privateKey, $domainName, $selector)
    {
        if (!extension_loaded('opendkim')) {
            throw new Swift_SwiftException('php-opendkim extension not found');
        }

        $this->_peclLoaded = true;

        parent::__construct($privateKey, $domainName, $selector);
    }

    public static function newInstance($privateKey, $domainName, $selector)
    {
        return new static($privateKey, $domainName, $selector);
    }

    public function addSignature(Swift_Mime_HeaderSet $headers)
    {
        $header = new Swift_Mime_Headers_OpenDKIMHeader('DKIM-Signature');
        $headerVal = $this->_dkimHandler->getSignatureHeader();
        if (!$headerVal) {
            throw new Swift_SwiftException('OpenDKIM Error: '.$this->_dkimHandler->getError());
        }
        $header->setValue($headerVal);
        $headers->set($header);

        return $this;
    }

    public function setHeaders(Swift_Mime_HeaderSet $headers)
    {
        $bodyLen = $this->_bodyLen;
        if (is_bool($bodyLen)) {
            $bodyLen = -1;
        }
        $hash = $this->_hashAlgorithm == 'rsa-sha1' ? OpenDKIMSign::ALG_RSASHA1 : OpenDKIMSign::ALG_RSASHA256;
        $bodyCanon = $this->_bodyCanon == 'simple' ? OpenDKIMSign::CANON_SIMPLE : OpenDKIMSign::CANON_RELAXED;
        $headerCanon = $this->_headerCanon == 'simple' ? OpenDKIMSign::CANON_SIMPLE : OpenDKIMSign::CANON_RELAXED;
        $this->_dkimHandler = new OpenDKIMSign($this->_privateKey, $this->_selector, $this->_domainName, $headerCanon, $bodyCanon, $hash, $bodyLen);
        // Hardcode signature Margin for now
        $this->_dkimHandler->setMargin(78);

        if (!is_numeric($this->_signatureTimestamp)) {
            OpenDKIM::setOption(OpenDKIM::OPTS_FIXEDTIME, time());
        } else {
            if (!OpenDKIM::setOption(OpenDKIM::OPTS_FIXEDTIME, $this->_signatureTimestamp)) {
                throw new Swift_SwiftException('Unable to force signature timestamp ['.openssl_error_string().']');
            }
        }
        if (isset($this->_signerIdentity)) {
            $this->_dkimHandler->setSigner($this->_signerIdentity);
        }
        $listHeaders = $headers->listAll();
        foreach ($listHeaders as $hName) {
            // Check if we need to ignore Header
            if (!isset($this->_ignoredHeaders[strtolower($hName)])) {
                $tmp = $headers->getAll($hName);
                if ($headers->has($hName)) {
                    foreach ($tmp as $header) {
                        if ($header->getFieldBody() != '') {
                            $htosign = $header->toString();
                            $this->_dkimHandler->header($htosign);
                            $this->_signedHeaders[] = $header->getFieldName();
                        }
                    }
                }
            }
        }

        return $this;
    }

    public function startBody()
    {
        if (!$this->_peclLoaded) {
            return parent::startBody();
        }
        $this->dropFirstLF = true;
        $this->_dkimHandler->eoh();

        return $this;
    }

    public function endBody()
    {
        if (!$this->_peclLoaded) {
            return parent::endBody();
        }
        $this->_dkimHandler->eom();

        return $this;
    }

    public function reset()
    {
        $this->_dkimHandler = null;
        parent::reset();

        return $this;
    }

    /**
     * Set the signature timestamp.
     *
     * @param int $time
     *
     * @return Swift_Signers_DKIMSigner
     */
    public function setSignatureTimestamp($time)
    {
        $this->_signatureTimestamp = $time;

        return $this;
    }

    /**
     * Set the signature expiration timestamp.
     *
     * @param int $time
     *
     * @return Swift_Signers_DKIMSigner
     */
    public function setSignatureExpiration($time)
    {
        $this->_signatureExpiration = $time;

        return $this;
    }

    /**
     * Enable / disable the DebugHeaders.
     *
     * @param bool $debug
     *
     * @return Swift_Signers_DKIMSigner
     */
    public function setDebugHeaders($debug)
    {
        $this->_debugHeaders = (bool) $debug;

        return $this;
    }

    // Protected

    protected function _canonicalizeBody($string)
    {
        if (!$this->_peclLoaded) {
            return parent::_canonicalizeBody($string);
        }
        if (false && $this->dropFirstLF === true) {
            if ($string[0] == "\r" && $string[1] == "\n") {
                $string = substr($string, 2);
            }
        }
        $this->dropFirstLF = false;
        if (strlen($string)) {
            $this->_dkimHandler->body($string);
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * MIME Message Signer used to apply S/MIME Signature/Encryption to a message.
 *
 *
 * @author Romain-Geissler
 * @author Sebastiaan Stok <s.stok@rollerscapes.net>
 */
class Swift_Signers_SMimeSigner implements Swift_Signers_BodySigner
{
    protected $signCertificate;
    protected $signPrivateKey;
    protected $encryptCert;
    protected $signThenEncrypt = true;
    protected $signLevel;
    protected $encryptLevel;
    protected $signOptions;
    protected $encryptOptions;
    protected $encryptCipher;
    protected $extraCerts = null;

    /**
     * @var Swift_StreamFilters_StringReplacementFilterFactory
     */
    protected $replacementFactory;

    /**
     * @var Swift_Mime_HeaderFactory
     */
    protected $headerFactory;

    /**
     * Constructor.
     *
     * @param string|null $signCertificate
     * @param string|null $signPrivateKey
     * @param string|null $encryptCertificate
     */
    public function __construct($signCertificate = null, $signPrivateKey = null, $encryptCertificate = null)
    {
        if (null !== $signPrivateKey) {
            $this->setSignCertificate($signCertificate, $signPrivateKey);
        }

        if (null !== $encryptCertificate) {
            $this->setEncryptCertificate($encryptCertificate);
        }

        $this->replacementFactory = Swift_DependencyContainer::getInstance()
            ->lookup('transport.replacementfactory');

        $this->signOptions = PKCS7_DETACHED;

        // Supported since php5.4
        if (defined('OPENSSL_CIPHER_AES_128_CBC')) {
            $this->encryptCipher = OPENSSL_CIPHER_AES_128_CBC;
        } else {
            $this->encryptCipher = OPENSSL_CIPHER_RC2_128;
        }
    }

    /**
     * Returns an new Swift_Signers_SMimeSigner instance.
     *
     * @param string $certificate
     * @param string $privateKey
     *
     * @return Swift_Signers_SMimeSigner
     */
    public static function newInstance($certificate = null, $privateKey = null)
    {
        return new self($certificate, $privateKey);
    }

    /**
     * Set the certificate location to use for signing.
     *
     * @link http://www.php.net/manual/en/openssl.pkcs7.flags.php
     *
     * @param string       $certificate
     * @param string|array $privateKey  If the key needs an passphrase use array('file-location', 'passphrase') instead
     * @param int          $signOptions Bitwise operator options for openssl_pkcs7_sign()
     * @param string       $extraCerts  A file containing intermediate certificates needed by the signing certificate
     *
     * @return Swift_Signers_SMimeSigner
     */
    public function setSignCertificate($certificate, $privateKey = null, $signOptions = PKCS7_DETACHED, $extraCerts = null)
    {
        $this->signCertificate = 'file://'.str_replace('\\', '/', realpath($certificate));

        if (null !== $privateKey) {
            if (is_array($privateKey)) {
                $this->signPrivateKey = $privateKey;
                $this->signPrivateKey[0] = 'file://'.str_replace('\\', '/', realpath($privateKey[0]));
            } else {
                $this->signPrivateKey = 'file://'.str_replace('\\', '/', realpath($privateKey));
            }
        }

        $this->signOptions = $signOptions;
        if (null !== $extraCerts) {
            $this->extraCerts = str_replace('\\', '/', realpath($extraCerts));
        }

        return $this;
    }

    /**
     * Set the certificate location to use for encryption.
     *
     * @link http://www.php.net/manual/en/openssl.pkcs7.flags.php
     * @link http://nl3.php.net/manual/en/openssl.ciphers.php
     *
     * @param string|array $recipientCerts Either an single X.509 certificate, or an assoc array of X.509 certificates.
     * @param int          $cipher
     *
     * @return Swift_Signers_SMimeSigner
     */
    public function setEncryptCertificate($recipientCerts, $cipher = null)
    {
        if (is_array($recipientCerts)) {
            $this->encryptCert = array();

            foreach ($recipientCerts as $cert) {
                $this->encryptCert[] = 'file://'.str_replace('\\', '/', realpath($cert));
            }
        } else {
            $this->encryptCert = 'file://'.str_replace('\\', '/', realpath($recipientCerts));
        }

        if (null !== $cipher) {
            $this->encryptCipher = $cipher;
        }

        return $this;
    }

    /**
     * @return string
     */
    public function getSignCertificate()
    {
        return $this->signCertificate;
    }

    /**
     * @return string
     */
    public function getSignPrivateKey()
    {
        return $this->signPrivateKey;
    }

    /**
     * Set perform signing before encryption.
     *
     * The default is to first sign the message and then encrypt.
     * But some older mail clients, namely Microsoft Outlook 2000 will work when the message first encrypted.
     * As this goes against the official specs, its recommended to only use 'encryption -> signing' when specifically targeting these 'broken' clients.
     *
     * @param bool $signThenEncrypt
     *
     * @return Swift_Signers_SMimeSigner
     */
    public function setSignThenEncrypt($signThenEncrypt = true)
    {
        $this->signThenEncrypt = $signThenEncrypt;

        return $this;
    }

    /**
     * @return bool
     */
    public function isSignThenEncrypt()
    {
        return $this->signThenEncrypt;
    }

    /**
     * Resets internal states.
     *
     * @return Swift_Signers_SMimeSigner
     */
    public function reset()
    {
        return $this;
    }

    /**
     * Change the Swift_Message to apply the signing.
     *
     * @param Swift_Message $message
     *
     * @return Swift_Signers_SMimeSigner
     */
    public function signMessage(Swift_Message $message)
    {
        if (null === $this->signCertificate && null === $this->encryptCert) {
            return $this;
        }

        // Store the message using ByteStream to a file{1}
        // Remove all Children
        // Sign file{1}, parse the new MIME headers and set them on the primary MimeEntity
        // Set the singed-body as the new body (without boundary)

        $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
        $this->toSMimeByteStream($messageStream, $message);
        $message->setEncoder(Swift_DependencyContainer::getInstance()->lookup('mime.rawcontentencoder'));

        $message->setChildren(array());
        $this->streamToMime($messageStream, $message);
    }

    /**
     * Return the list of header a signer might tamper.
     *
     * @return array
     */
    public function getAlteredHeaders()
    {
        return array('Content-Type', 'Content-Transfer-Encoding', 'Content-Disposition');
    }

    /**
     * @param Swift_InputByteStream $inputStream
     * @param Swift_Message         $mimeEntity
     */
    protected function toSMimeByteStream(Swift_InputByteStream $inputStream, Swift_Message $message)
    {
        $mimeEntity = $this->createMessage($message);
        $messageStream = new Swift_ByteStream_TemporaryFileByteStream();

        $mimeEntity->toByteStream($messageStream);
        $messageStream->commit();

        if (null !== $this->signCertificate && null !== $this->encryptCert) {
            $temporaryStream = new Swift_ByteStream_TemporaryFileByteStream();

            if ($this->signThenEncrypt) {
                $this->messageStreamToSignedByteStream($messageStream, $temporaryStream);
                $this->messageStreamToEncryptedByteStream($temporaryStream, $inputStream);
            } else {
                $this->messageStreamToEncryptedByteStream($messageStream, $temporaryStream);
                $this->messageStreamToSignedByteStream($temporaryStream, $inputStream);
            }
        } elseif ($this->signCertificate !== null) {
            $this->messageStreamToSignedByteStream($messageStream, $inputStream);
        } else {
            $this->messageStreamToEncryptedByteStream($messageStream, $inputStream);
        }
    }

    /**
     * @param Swift_Message $message
     *
     * @return Swift_Message
     */
    protected function createMessage(Swift_Message $message)
    {
        $mimeEntity = new Swift_Message('', $message->getBody(), $message->getContentType(), $message->getCharset());
        $mimeEntity->setChildren($message->getChildren());

        $messageHeaders = $mimeEntity->getHeaders();
        $messageHeaders->remove('Message-ID');
        $messageHeaders->remove('Date');
        $messageHeaders->remove('Subject');
        $messageHeaders->remove('MIME-Version');
        $messageHeaders->remove('To');
        $messageHeaders->remove('From');

        return $mimeEntity;
    }

    /**
     * @param Swift_FileStream      $outputStream
     * @param Swift_InputByteStream $inputStream
     *
     * @throws Swift_IoException
     */
    protected function messageStreamToSignedByteStream(Swift_FileStream $outputStream, Swift_InputByteStream $inputStream)
    {
        $signedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();

        $args = array($outputStream->getPath(), $signedMessageStream->getPath(), $this->signCertificate, $this->signPrivateKey, array(), $this->signOptions);
        if (null !== $this->extraCerts) {
            $args[] = $this->extraCerts;
        }

        if (!call_user_func_array('openssl_pkcs7_sign', $args)) {
            throw new Swift_IoException(sprintf('Failed to sign S/Mime message. Error: "%s".', openssl_error_string()));
        }

        $this->copyFromOpenSSLOutput($signedMessageStream, $inputStream);
    }

    /**
     * @param Swift_FileStream      $outputStream
     * @param Swift_InputByteStream $is
     *
     * @throws Swift_IoException
     */
    protected function messageStreamToEncryptedByteStream(Swift_FileStream $outputStream, Swift_InputByteStream $is)
    {
        $encryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();

        if (!openssl_pkcs7_encrypt($outputStream->getPath(), $encryptedMessageStream->getPath(), $this->encryptCert, array(), 0, $this->encryptCipher)) {
            throw new Swift_IoException(sprintf('Failed to encrypt S/Mime message. Error: "%s".', openssl_error_string()));
        }

        $this->copyFromOpenSSLOutput($encryptedMessageStream, $is);
    }

    /**
     * @param Swift_OutputByteStream $fromStream
     * @param Swift_InputByteStream  $toStream
     */
    protected function copyFromOpenSSLOutput(Swift_OutputByteStream $fromStream, Swift_InputByteStream $toStream)
    {
        $bufferLength = 4096;
        $filteredStream = new Swift_ByteStream_TemporaryFileByteStream();
        $filteredStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF');
        $filteredStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF');

        while (false !== ($buffer = $fromStream->read($bufferLength))) {
            $filteredStream->write($buffer);
        }

        $filteredStream->flushBuffers();

        while (false !== ($buffer = $filteredStream->read($bufferLength))) {
            $toStream->write($buffer);
        }

        $toStream->commit();
    }

    /**
     * Merges an OutputByteStream to Swift_Message.
     *
     * @param Swift_OutputByteStream $fromStream
     * @param Swift_Message          $message
     */
    protected function streamToMime(Swift_OutputByteStream $fromStream, Swift_Message $message)
    {
        $bufferLength = 78;
        $headerData = '';

        $fromStream->setReadPointer(0);

        while (($buffer = $fromStream->read($bufferLength)) !== false) {
            $headerData .= $buffer;

            if (false !== strpos($buffer, "\r\n\r\n")) {
                break;
            }
        }

        $headersPosEnd = strpos($headerData, "\r\n\r\n");
        $headerData = trim($headerData);
        $headerData = substr($headerData, 0, $headersPosEnd);
        $headerLines = explode("\r\n", $headerData);
        unset($headerData);

        $headers = array();
        $currentHeaderName = '';

        foreach ($headerLines as $headerLine) {
            // Line separated
            if (ctype_space($headerLines[0]) || false === strpos($headerLine, ':')) {
                $headers[$currentHeaderName] .= ' '.trim($headerLine);
                continue;
            }

            $header = explode(':', $headerLine, 2);
            $currentHeaderName = strtolower($header[0]);
            $headers[$currentHeaderName] = trim($header[1]);
        }

        $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
        $messageStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF');
        $messageStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF');

        $messageHeaders = $message->getHeaders();

        // No need to check for 'application/pkcs7-mime', as this is always base64
        if ('multipart/signed;' === substr($headers['content-type'], 0, 17)) {
            if (!preg_match('/boundary=("[^"]+"|(?:[^\s]+|$))/is', $headers['content-type'], $contentTypeData)) {
                throw new Swift_SwiftException('Failed to find Boundary parameter');
            }

            $boundary = trim($contentTypeData['1'], '"');

            // Skip the header and CRLF CRLF
            $fromStream->setReadPointer($headersPosEnd + 4);

            while (false !== ($buffer = $fromStream->read($bufferLength))) {
                $messageStream->write($buffer);
            }

            $messageStream->commit();

            $messageHeaders->remove('Content-Transfer-Encoding');
            $message->setContentType($headers['content-type']);
            $message->setBoundary($boundary);
            $message->setBody($messageStream);
        } else {
            $fromStream->setReadPointer($headersPosEnd + 4);

            if (null === $this->headerFactory) {
                $this->headerFactory = Swift_DependencyContainer::getInstance()->lookup('mime.headerfactory');
            }

            $message->setContentType($headers['content-type']);
            $messageHeaders->set($this->headerFactory->createTextHeader('Content-Transfer-Encoding', $headers['content-transfer-encoding']));
            $messageHeaders->set($this->headerFactory->createTextHeader('Content-Disposition', $headers['content-disposition']));

            while (false !== ($buffer = $fromStream->read($bufferLength))) {
                $messageStream->write($buffer);
            }

            $messageStream->commit();
            $message->setBody($messageStream);
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Sends Messages over SMTP with ESMTP support.
 *
 * @author Chris Corbyn
 *
 * @method Swift_SmtpTransport setUsername(string $username) Set the username to authenticate with.
 * @method string              getUsername()                 Get the username to authenticate with.
 * @method Swift_SmtpTransport setPassword(string $password) Set the password to authenticate with.
 * @method string              getPassword()                 Get the password to authenticate with.
 * @method Swift_SmtpTransport setAuthMode(string $mode)     Set the auth mode to use to authenticate.
 * @method string              getAuthMode()                 Get the auth mode to use to authenticate.
 */
class Swift_SmtpTransport extends Swift_Transport_EsmtpTransport
{
    /**
     * Create a new SmtpTransport, optionally with $host, $port and $security.
     *
     * @param string $host
     * @param int    $port
     * @param string $security
     */
    public function __construct($host = 'localhost', $port = 25, $security = null)
    {
        call_user_func_array(
            array($this, 'Swift_Transport_EsmtpTransport::__construct'),
            Swift_DependencyContainer::getInstance()
                ->createDependenciesFor('transport.smtp')
            );

        $this->setHost($host);
        $this->setPort($port);
        $this->setEncryption($security);
    }

    /**
     * Create a new SmtpTransport instance.
     *
     * @param string $host
     * @param int    $port
     * @param string $security
     *
     * @return Swift_SmtpTransport
     */
    public static function newInstance($host = 'localhost', $port = 25, $security = null)
    {
        return new self($host, $port, $security);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Interface for spools.
 *
 * @author Fabien Potencier
 */
interface Swift_Spool
{
    /**
     * Starts this Spool mechanism.
     */
    public function start();

    /**
     * Stops this Spool mechanism.
     */
    public function stop();

    /**
     * Tests if this Spool mechanism has started.
     *
     * @return bool
     */
    public function isStarted();

    /**
     * Queues a message.
     *
     * @param Swift_Mime_Message $message The message to store
     *
     * @return bool Whether the operation has succeeded
     */
    public function queueMessage(Swift_Mime_Message $message);

    /**
     * Sends messages using the given transport instance.
     *
     * @param Swift_Transport $transport        A transport instance
     * @param string[]        $failedRecipients An array of failures by-reference
     *
     * @return int The number of sent emails
     */
    public function flushQueue(Swift_Transport $transport, &$failedRecipients = null);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Stores Messages in a queue.
 *
 * @author Fabien Potencier
 */
class Swift_SpoolTransport extends Swift_Transport_SpoolTransport
{
    /**
     * Create a new SpoolTransport.
     *
     * @param Swift_Spool $spool
     */
    public function __construct(Swift_Spool $spool)
    {
        $arguments = Swift_DependencyContainer::getInstance()
            ->createDependenciesFor('transport.spool');

        $arguments[] = $spool;

        call_user_func_array(
            array($this, 'Swift_Transport_SpoolTransport::__construct'),
            $arguments
        );
    }

    /**
     * Create a new SpoolTransport instance.
     *
     * @param Swift_Spool $spool
     *
     * @return Swift_SpoolTransport
     */
    public static function newInstance(Swift_Spool $spool)
    {
        return new self($spool);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Processes bytes as they pass through a stream and performs filtering.
 *
 * @author Chris Corbyn
 */
interface Swift_StreamFilter
{
    /**
     * Based on the buffer given, this returns true if more buffering is needed.
     *
     * @param mixed $buffer
     *
     * @return bool
     */
    public function shouldBuffer($buffer);

    /**
     * Filters $buffer and returns the changes.
     *
     * @param mixed $buffer
     *
     * @return mixed
     */
    public function filter($buffer);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Processes bytes as they pass through a buffer and replaces sequences in it.
 *
 * This stream filter deals with Byte arrays rather than simple strings.
 *
 * @author Chris Corbyn
 */
class Swift_StreamFilters_ByteArrayReplacementFilter implements Swift_StreamFilter
{
    /** The needle(s) to search for */
    private $_search;

    /** The replacement(s) to make */
    private $_replace;

    /** The Index for searching */
    private $_index;

    /** The Search Tree */
    private $_tree = array();

    /**  Gives the size of the largest search */
    private $_treeMaxLen = 0;

    private $_repSize;

    /**
     * Create a new ByteArrayReplacementFilter with $search and $replace.
     *
     * @param array $search
     * @param array $replace
     */
    public function __construct($search, $replace)
    {
        $this->_search = $search;
        $this->_index = array();
        $this->_tree = array();
        $this->_replace = array();
        $this->_repSize = array();

        $tree = null;
        $i = null;
        $last_size = $size = 0;
        foreach ($search as $i => $search_element) {
            if ($tree !== null) {
                $tree[-1] = min(count($replace) - 1, $i - 1);
                $tree[-2] = $last_size;
            }
            $tree = &$this->_tree;
            if (is_array($search_element)) {
                foreach ($search_element as $k => $char) {
                    $this->_index[$char] = true;
                    if (!isset($tree[$char])) {
                        $tree[$char] = array();
                    }
                    $tree = &$tree[$char];
                }
                $last_size = $k + 1;
                $size = max($size, $last_size);
            } else {
                $last_size = 1;
                if (!isset($tree[$search_element])) {
                    $tree[$search_element] = array();
                }
                $tree = &$tree[$search_element];
                $size = max($last_size, $size);
                $this->_index[$search_element] = true;
            }
        }
        if ($i !== null) {
            $tree[-1] = min(count($replace) - 1, $i);
            $tree[-2] = $last_size;
            $this->_treeMaxLen = $size;
        }
        foreach ($replace as $rep) {
            if (!is_array($rep)) {
                $rep = array($rep);
            }
            $this->_replace[] = $rep;
        }
        for ($i = count($this->_replace) - 1; $i >= 0; --$i) {
            $this->_replace[$i] = $rep = $this->filter($this->_replace[$i], $i);
            $this->_repSize[$i] = count($rep);
        }
    }

    /**
     * Returns true if based on the buffer passed more bytes should be buffered.
     *
     * @param array $buffer
     *
     * @return bool
     */
    public function shouldBuffer($buffer)
    {
        $endOfBuffer = end($buffer);

        return isset($this->_index[$endOfBuffer]);
    }

    /**
     * Perform the actual replacements on $buffer and return the result.
     *
     * @param array $buffer
     * @param int   $_minReplaces
     *
     * @return array
     */
    public function filter($buffer, $_minReplaces = -1)
    {
        if ($this->_treeMaxLen == 0) {
            return $buffer;
        }

        $newBuffer = array();
        $buf_size = count($buffer);
        $last_size = 0;
        for ($i = 0; $i < $buf_size; ++$i) {
            $search_pos = $this->_tree;
            $last_found = PHP_INT_MAX;
            // We try to find if the next byte is part of a search pattern
            for ($j = 0; $j <= $this->_treeMaxLen; ++$j) {
                // We have a new byte for a search pattern
                if (isset($buffer [$p = $i + $j]) && isset($search_pos[$buffer[$p]])) {
                    $search_pos = $search_pos[$buffer[$p]];
                    // We have a complete pattern, save, in case we don't find a better match later
                    if (isset($search_pos[-1]) && $search_pos[-1] < $last_found
                        && $search_pos[-1] > $_minReplaces) {
                        $last_found = $search_pos[-1];
                        $last_size = $search_pos[-2];
                    }
                }
                // We got a complete pattern
                elseif ($last_found !== PHP_INT_MAX) {
                    // Adding replacement datas to output buffer
                    $rep_size = $this->_repSize[$last_found];
                    for ($j = 0; $j < $rep_size; ++$j) {
                        $newBuffer[] = $this->_replace[$last_found][$j];
                    }
                    // We Move cursor forward
                    $i += $last_size - 1;
                    // Edge Case, last position in buffer
                    if ($i >= $buf_size) {
                        $newBuffer[] = $buffer[$i];
                    }

                    // We start the next loop
                    continue 2;
                } else {
                    // this byte is not in a pattern and we haven't found another pattern
                    break;
                }
            }
            // Normal byte, move it to output buffer
            $newBuffer[] = $buffer[$i];
        }

        return $newBuffer;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Processes bytes as they pass through a buffer and replaces sequences in it.
 *
 * @author Chris Corbyn
 */
class Swift_StreamFilters_StringReplacementFilter implements Swift_StreamFilter
{
    /** The needle(s) to search for */
    private $_search;

    /** The replacement(s) to make */
    private $_replace;

    /**
     * Create a new StringReplacementFilter with $search and $replace.
     *
     * @param string|array $search
     * @param string|array $replace
     */
    public function __construct($search, $replace)
    {
        $this->_search = $search;
        $this->_replace = $replace;
    }

    /**
     * Returns true if based on the buffer passed more bytes should be buffered.
     *
     * @param string $buffer
     *
     * @return bool
     */
    public function shouldBuffer($buffer)
    {
        $endOfBuffer = substr($buffer, -1);
        foreach ((array) $this->_search as $needle) {
            if (false !== strpos($needle, $endOfBuffer)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Perform the actual replacements on $buffer and return the result.
     *
     * @param string $buffer
     *
     * @return string
     */
    public function filter($buffer)
    {
        return str_replace($this->_search, $this->_replace, $buffer);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Creates filters for replacing needles in a string buffer.
 *
 * @author Chris Corbyn
 */
class Swift_StreamFilters_StringReplacementFilterFactory implements Swift_ReplacementFilterFactory
{
    /** Lazy-loaded filters */
    private $_filters = array();

    /**
     * Create a new StreamFilter to replace $search with $replace in a string.
     *
     * @param string $search
     * @param string $replace
     *
     * @return Swift_StreamFilter
     */
    public function createFilter($search, $replace)
    {
        if (!isset($this->_filters[$search][$replace])) {
            if (!isset($this->_filters[$search])) {
                $this->_filters[$search] = array();
            }

            if (!isset($this->_filters[$search][$replace])) {
                $this->_filters[$search][$replace] = array();
            }

            $this->_filters[$search][$replace] = new Swift_StreamFilters_StringReplacementFilter($search, $replace);
        }

        return $this->_filters[$search][$replace];
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Base Exception class.
 *
 * @author Chris Corbyn
 */
class Swift_SwiftException extends Exception
{
    /**
     * Create a new SwiftException with $message.
     *
     * @param string    $message
     * @param int       $code
     * @param Exception $previous
     */
    public function __construct($message, $code = 0, Exception $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Sends Messages over SMTP.
 *
 * @author Chris Corbyn
 */
abstract class Swift_Transport_AbstractSmtpTransport implements Swift_Transport
{
    /** Input-Output buffer for sending/receiving SMTP commands and responses */
    protected $_buffer;

    /** Connection status */
    protected $_started = false;

    /** The domain name to use in HELO command */
    protected $_domain = '[127.0.0.1]';

    /** The event dispatching layer */
    protected $_eventDispatcher;

    /** Source Ip */
    protected $_sourceIp;

    /** Return an array of params for the Buffer */
    abstract protected function _getBufferParams();

    /**
     * Creates a new EsmtpTransport using the given I/O buffer.
     *
     * @param Swift_Transport_IoBuffer     $buf
     * @param Swift_Events_EventDispatcher $dispatcher
     */
    public function __construct(Swift_Transport_IoBuffer $buf, Swift_Events_EventDispatcher $dispatcher)
    {
        $this->_eventDispatcher = $dispatcher;
        $this->_buffer = $buf;
        $this->_lookupHostname();
    }

    /**
     * Set the name of the local domain which Swift will identify itself as.
     *
     * This should be a fully-qualified domain name and should be truly the domain
     * you're using.
     *
     * If your server doesn't have a domain name, use the IP in square
     * brackets (i.e. [127.0.0.1]).
     *
     * @param string $domain
     *
     * @return Swift_Transport_AbstractSmtpTransport
     */
    public function setLocalDomain($domain)
    {
        $this->_domain = $domain;

        return $this;
    }

    /**
     * Get the name of the domain Swift will identify as.
     *
     * @return string
     */
    public function getLocalDomain()
    {
        return $this->_domain;
    }

    /**
     * Sets the source IP.
     *
     * @param string $source
     */
    public function setSourceIp($source)
    {
        $this->_sourceIp = $source;
    }

    /**
     * Returns the IP used to connect to the destination.
     *
     * @return string
     */
    public function getSourceIp()
    {
        return $this->_sourceIp;
    }

    /**
     * Start the SMTP connection.
     */
    public function start()
    {
        if (!$this->_started) {
            if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this)) {
                $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStarted');
                if ($evt->bubbleCancelled()) {
                    return;
                }
            }

            try {
                $this->_buffer->initialize($this->_getBufferParams());
            } catch (Swift_TransportException $e) {
                $this->_throwException($e);
            }
            $this->_readGreeting();
            $this->_doHeloCommand();

            if ($evt) {
                $this->_eventDispatcher->dispatchEvent($evt, 'transportStarted');
            }

            $this->_started = true;
        }
    }

    /**
     * Test if an SMTP connection has been established.
     *
     * @return bool
     */
    public function isStarted()
    {
        return $this->_started;
    }

    /**
     * Send the given Message.
     *
     * Recipient/sender data will be retrieved from the Message API.
     * The return value is the number of recipients who were accepted for delivery.
     *
     * @param Swift_Mime_Message $message
     * @param string[]           $failedRecipients An array of failures by-reference
     *
     * @return int
     */
    public function send(Swift_Mime_Message $message, &$failedRecipients = null)
    {
        $sent = 0;
        $failedRecipients = (array) $failedRecipients;

        if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) {
            $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
            if ($evt->bubbleCancelled()) {
                return 0;
            }
        }

        if (!$reversePath = $this->_getReversePath($message)) {
            $this->_throwException(new Swift_TransportException(
                'Cannot send message without a sender address'
                )
            );
        }

        $to = (array) $message->getTo();
        $cc = (array) $message->getCc();
        $tos = array_merge($to, $cc);
        $bcc = (array) $message->getBcc();

        $message->setBcc(array());

        try {
            $sent += $this->_sendTo($message, $reversePath, $tos, $failedRecipients);
            $sent += $this->_sendBcc($message, $reversePath, $bcc, $failedRecipients);
        } catch (Exception $e) {
            $message->setBcc($bcc);
            throw $e;
        }

        $message->setBcc($bcc);

        if ($evt) {
            if ($sent == count($to) + count($cc) + count($bcc)) {
                $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
            } elseif ($sent > 0) {
                $evt->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE);
            } else {
                $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED);
            }
            $evt->setFailedRecipients($failedRecipients);
            $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
        }

        $message->generateId(); //Make sure a new Message ID is used

        return $sent;
    }

    /**
     * Stop the SMTP connection.
     */
    public function stop()
    {
        if ($this->_started) {
            if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this)) {
                $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStopped');
                if ($evt->bubbleCancelled()) {
                    return;
                }
            }

            try {
                $this->executeCommand("QUIT\r\n", array(221));
            } catch (Swift_TransportException $e) {
            }

            try {
                $this->_buffer->terminate();

                if ($evt) {
                    $this->_eventDispatcher->dispatchEvent($evt, 'transportStopped');
                }
            } catch (Swift_TransportException $e) {
                $this->_throwException($e);
            }
        }
        $this->_started = false;
    }

    /**
     * Register a plugin.
     *
     * @param Swift_Events_EventListener $plugin
     */
    public function registerPlugin(Swift_Events_EventListener $plugin)
    {
        $this->_eventDispatcher->bindEventListener($plugin);
    }

    /**
     * Reset the current mail transaction.
     */
    public function reset()
    {
        $this->executeCommand("RSET\r\n", array(250));
    }

    /**
     * Get the IoBuffer where read/writes are occurring.
     *
     * @return Swift_Transport_IoBuffer
     */
    public function getBuffer()
    {
        return $this->_buffer;
    }

    /**
     * Run a command against the buffer, expecting the given response codes.
     *
     * If no response codes are given, the response will not be validated.
     * If codes are given, an exception will be thrown on an invalid response.
     *
     * @param string   $command
     * @param int[]    $codes
     * @param string[] $failures An array of failures by-reference
     *
     * @return string
     */
    public function executeCommand($command, $codes = array(), &$failures = null)
    {
        $failures = (array) $failures;
        $seq = $this->_buffer->write($command);
        $response = $this->_getFullResponse($seq);
        if ($evt = $this->_eventDispatcher->createCommandEvent($this, $command, $codes)) {
            $this->_eventDispatcher->dispatchEvent($evt, 'commandSent');
        }
        $this->_assertResponseCode($response, $codes);

        return $response;
    }

    /** Read the opening SMTP greeting */
    protected function _readGreeting()
    {
        $this->_assertResponseCode($this->_getFullResponse(0), array(220));
    }

    /** Send the HELO welcome */
    protected function _doHeloCommand()
    {
        $this->executeCommand(
            sprintf("HELO %s\r\n", $this->_domain), array(250)
            );
    }

    /** Send the MAIL FROM command */
    protected function _doMailFromCommand($address)
    {
        $this->executeCommand(
            sprintf("MAIL FROM:<%s>\r\n", $address), array(250)
            );
    }

    /** Send the RCPT TO command */
    protected function _doRcptToCommand($address)
    {
        $this->executeCommand(
            sprintf("RCPT TO:<%s>\r\n", $address), array(250, 251, 252)
            );
    }

    /** Send the DATA command */
    protected function _doDataCommand()
    {
        $this->executeCommand("DATA\r\n", array(354));
    }

    /** Stream the contents of the message over the buffer */
    protected function _streamMessage(Swift_Mime_Message $message)
    {
        $this->_buffer->setWriteTranslations(array("\r\n." => "\r\n.."));
        try {
            $message->toByteStream($this->_buffer);
            $this->_buffer->flushBuffers();
        } catch (Swift_TransportException $e) {
            $this->_throwException($e);
        }
        $this->_buffer->setWriteTranslations(array());
        $this->executeCommand("\r\n.\r\n", array(250));
    }

    /** Determine the best-use reverse path for this message */
    protected function _getReversePath(Swift_Mime_Message $message)
    {
        $return = $message->getReturnPath();
        $sender = $message->getSender();
        $from = $message->getFrom();
        $path = null;
        if (!empty($return)) {
            $path = $return;
        } elseif (!empty($sender)) {
            // Don't use array_keys
            reset($sender); // Reset Pointer to first pos
            $path = key($sender); // Get key
        } elseif (!empty($from)) {
            reset($from); // Reset Pointer to first pos
            $path = key($from); // Get key
        }

        return $path;
    }

    /** Throw a TransportException, first sending it to any listeners */
    protected function _throwException(Swift_TransportException $e)
    {
        if ($evt = $this->_eventDispatcher->createTransportExceptionEvent($this, $e)) {
            $this->_eventDispatcher->dispatchEvent($evt, 'exceptionThrown');
            if (!$evt->bubbleCancelled()) {
                throw $e;
            }
        } else {
            throw $e;
        }
    }

    /** Throws an Exception if a response code is incorrect */
    protected function _assertResponseCode($response, $wanted)
    {
        list($code) = sscanf($response, '%3d');
        $valid = (empty($wanted) || in_array($code, $wanted));

        if ($evt = $this->_eventDispatcher->createResponseEvent($this, $response,
            $valid)) {
            $this->_eventDispatcher->dispatchEvent($evt, 'responseReceived');
        }

        if (!$valid) {
            $this->_throwException(
                new Swift_TransportException(
                    'Expected response code '.implode('/', $wanted).' but got code '.
                    '"'.$code.'", with message "'.$response.'"',
                    $code)
                );
        }
    }

    /** Get an entire multi-line response using its sequence number */
    protected function _getFullResponse($seq)
    {
        $response = '';
        try {
            do {
                $line = $this->_buffer->readLine($seq);
                $response .= $line;
            } while (null !== $line && false !== $line && ' ' != $line{3});
        } catch (Swift_TransportException $e) {
            $this->_throwException($e);
        } catch (Swift_IoException $e) {
            $this->_throwException(
                new Swift_TransportException(
                    $e->getMessage())
                );
        }

        return $response;
    }

    /** Send an email to the given recipients from the given reverse path */
    private function _doMailTransaction($message, $reversePath, array $recipients, array &$failedRecipients)
    {
        $sent = 0;
        $this->_doMailFromCommand($reversePath);
        foreach ($recipients as $forwardPath) {
            try {
                $this->_doRcptToCommand($forwardPath);
                ++$sent;
            } catch (Swift_TransportException $e) {
                $failedRecipients[] = $forwardPath;
            }
        }

        if ($sent != 0) {
            $this->_doDataCommand();
            $this->_streamMessage($message);
        } else {
            $this->reset();
        }

        return $sent;
    }

    /** Send a message to the given To: recipients */
    private function _sendTo(Swift_Mime_Message $message, $reversePath, array $to, array &$failedRecipients)
    {
        if (empty($to)) {
            return 0;
        }

        return $this->_doMailTransaction($message, $reversePath, array_keys($to),
            $failedRecipients);
    }

    /** Send a message to all Bcc: recipients */
    private function _sendBcc(Swift_Mime_Message $message, $reversePath, array $bcc, array &$failedRecipients)
    {
        $sent = 0;
        foreach ($bcc as $forwardPath => $name) {
            $message->setBcc(array($forwardPath => $name));
            $sent += $this->_doMailTransaction(
                $message, $reversePath, array($forwardPath), $failedRecipients
                );
        }

        return $sent;
    }

    /** Try to determine the hostname of the server this is run on */
    private function _lookupHostname()
    {
        if (!empty($_SERVER['SERVER_NAME']) && $this->_isFqdn($_SERVER['SERVER_NAME'])) {
            $this->_domain = $_SERVER['SERVER_NAME'];
        } elseif (!empty($_SERVER['SERVER_ADDR'])) {
            // Set the address literal tag (See RFC 5321, section: 4.1.3)
            if (false === strpos($_SERVER['SERVER_ADDR'], ':')) {
                $prefix = ''; // IPv4 addresses are not tagged.
            } else {
                $prefix = 'IPv6:'; // Adding prefix in case of IPv6.
            }

            $this->_domain = sprintf('[%s%s]', $prefix, $_SERVER['SERVER_ADDR']);
        }
    }

    /** Determine is the $hostname is a fully-qualified name */
    private function _isFqdn($hostname)
    {
        // We could do a really thorough check, but there's really no point
        if (false !== $dotPos = strpos($hostname, '.')) {
            return ($dotPos > 0) && ($dotPos != strlen($hostname) - 1);
        }

        return false;
    }

    /**
     * Destructor.
     */
    public function __destruct()
    {
        $this->stop();
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Handles CRAM-MD5 authentication.
 *
 * @author Chris Corbyn
 */
class Swift_Transport_Esmtp_Auth_CramMd5Authenticator implements Swift_Transport_Esmtp_Authenticator
{
    /**
     * Get the name of the AUTH mechanism this Authenticator handles.
     *
     * @return string
     */
    public function getAuthKeyword()
    {
        return 'CRAM-MD5';
    }

    /**
     * Try to authenticate the user with $username and $password.
     *
     * @param Swift_Transport_SmtpAgent $agent
     * @param string                    $username
     * @param string                    $password
     *
     * @return bool
     */
    public function authenticate(Swift_Transport_SmtpAgent $agent, $username, $password)
    {
        try {
            $challenge = $agent->executeCommand("AUTH CRAM-MD5\r\n", array(334));
            $challenge = base64_decode(substr($challenge, 4));
            $message = base64_encode(
                $username.' '.$this->_getResponse($password, $challenge)
                );
            $agent->executeCommand(sprintf("%s\r\n", $message), array(235));

            return true;
        } catch (Swift_TransportException $e) {
            $agent->executeCommand("RSET\r\n", array(250));

            return false;
        }
    }

    /**
     * Generate a CRAM-MD5 response from a server challenge.
     *
     * @param string $secret
     * @param string $challenge
     *
     * @return string
     */
    private function _getResponse($secret, $challenge)
    {
        if (strlen($secret) > 64) {
            $secret = pack('H32', md5($secret));
        }

        if (strlen($secret) < 64) {
            $secret = str_pad($secret, 64, chr(0));
        }

        $k_ipad = substr($secret, 0, 64) ^ str_repeat(chr(0x36), 64);
        $k_opad = substr($secret, 0, 64) ^ str_repeat(chr(0x5C), 64);

        $inner = pack('H32', md5($k_ipad.$challenge));
        $digest = md5($k_opad.$inner);

        return $digest;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Handles LOGIN authentication.
 *
 * @author Chris Corbyn
 */
class Swift_Transport_Esmtp_Auth_LoginAuthenticator implements Swift_Transport_Esmtp_Authenticator
{
    /**
     * Get the name of the AUTH mechanism this Authenticator handles.
     *
     * @return string
     */
    public function getAuthKeyword()
    {
        return 'LOGIN';
    }

    /**
     * Try to authenticate the user with $username and $password.
     *
     * @param Swift_Transport_SmtpAgent $agent
     * @param string                    $username
     * @param string                    $password
     *
     * @return bool
     */
    public function authenticate(Swift_Transport_SmtpAgent $agent, $username, $password)
    {
        try {
            $agent->executeCommand("AUTH LOGIN\r\n", array(334));
            $agent->executeCommand(sprintf("%s\r\n", base64_encode($username)), array(334));
            $agent->executeCommand(sprintf("%s\r\n", base64_encode($password)), array(235));

            return true;
        } catch (Swift_TransportException $e) {
            $agent->executeCommand("RSET\r\n", array(250));

            return false;
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * This authentication is for Exchange servers. We support version 1 & 2.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Handles NTLM authentication.
 *
 * @author Ward Peeters <ward@coding-tech.com>
 */
class Swift_Transport_Esmtp_Auth_NTLMAuthenticator implements Swift_Transport_Esmtp_Authenticator
{
    const NTLMSIG = "NTLMSSP\x00";
    const DESCONST = 'KGS!@#$%';

    /**
     * Get the name of the AUTH mechanism this Authenticator handles.
     *
     * @return string
     */
    public function getAuthKeyword()
    {
        return 'NTLM';
    }

    /**
     * Try to authenticate the user with $username and $password.
     *
     * @param Swift_Transport_SmtpAgent $agent
     * @param string                    $username
     * @param string                    $password
     *
     * @return bool
     */
    public function authenticate(Swift_Transport_SmtpAgent $agent, $username, $password)
    {
        if (!function_exists('openssl_random_pseudo_bytes') || !function_exists('openssl_encrypt')) {
            throw new LogicException('The OpenSSL extension must be enabled to use the NTLM authenticator.');
        }

        if (!function_exists('bcmul')) {
            throw new LogicException('The BCMath functions must be enabled to use the NTLM authenticator.');
        }

        try {
            // execute AUTH command and filter out the code at the beginning
            // AUTH NTLM xxxx
            $response = base64_decode(substr(trim($this->sendMessage1($agent)), 4));

            // extra parameters for our unit cases
            $timestamp = func_num_args() > 3 ? func_get_arg(3) : $this->getCorrectTimestamp(bcmul(microtime(true), '1000'));
            $client = func_num_args() > 4 ? func_get_arg(4) : $this->getRandomBytes(8);

            // Message 3 response
            $this->sendMessage3($response, $username, $password, $timestamp, $client, $agent);

            return true;
        } catch (Swift_TransportException $e) {
            $agent->executeCommand("RSET\r\n", array(250));

            return false;
        }
    }

    protected function si2bin($si, $bits = 32)
    {
        $bin = null;
        if ($si >= -pow(2, $bits - 1) && ($si <= pow(2, $bits - 1))) {
            // positive or zero
            if ($si >= 0) {
                $bin = base_convert($si, 10, 2);
                // pad to $bits bit
                $bin_length = strlen($bin);
                if ($bin_length < $bits) {
                    $bin = str_repeat('0', $bits - $bin_length).$bin;
                }
            } else {
                // negative
                $si = -$si - pow(2, $bits);
                $bin = base_convert($si, 10, 2);
                $bin_length = strlen($bin);
                if ($bin_length > $bits) {
                    $bin = str_repeat('1', $bits - $bin_length).$bin;
                }
            }
        }

        return $bin;
    }

    /**
     * Send our auth message and returns the response.
     *
     * @param Swift_Transport_SmtpAgent $agent
     *
     * @return string SMTP Response
     */
    protected function sendMessage1(Swift_Transport_SmtpAgent $agent)
    {
        $message = $this->createMessage1();

        return $agent->executeCommand(sprintf("AUTH %s %s\r\n", $this->getAuthKeyword(), base64_encode($message)), array(334));
    }

    /**
     * Fetch all details of our response (message 2).
     *
     * @param string $response
     *
     * @return array our response parsed
     */
    protected function parseMessage2($response)
    {
        $responseHex = bin2hex($response);
        $length = floor(hexdec(substr($responseHex, 28, 4)) / 256) * 2;
        $offset = floor(hexdec(substr($responseHex, 32, 4)) / 256) * 2;
        $challenge = $this->hex2bin(substr($responseHex, 48, 16));
        $context = $this->hex2bin(substr($responseHex, 64, 16));
        $targetInfoH = $this->hex2bin(substr($responseHex, 80, 16));
        $targetName = $this->hex2bin(substr($responseHex, $offset, $length));
        $offset = floor(hexdec(substr($responseHex, 88, 4)) / 256) * 2;
        $targetInfoBlock = substr($responseHex, $offset);
        list($domainName, $serverName, $DNSDomainName, $DNSServerName, $terminatorByte) = $this->readSubBlock($targetInfoBlock);

        return array(
            $challenge,
            $context,
            $targetInfoH,
            $targetName,
            $domainName,
            $serverName,
            $DNSDomainName,
            $DNSServerName,
            $this->hex2bin($targetInfoBlock),
            $terminatorByte,
        );
    }

    /**
     * Read the blob information in from message2.
     *
     * @param $block
     *
     * @return array
     */
    protected function readSubBlock($block)
    {
        // remove terminatorByte cause it's always the same
        $block = substr($block, 0, -8);

        $length = strlen($block);
        $offset = 0;
        $data = array();
        while ($offset < $length) {
            $blockLength = hexdec(substr(substr($block, $offset, 8), -4)) / 256;
            $offset += 8;
            $data[] = $this->hex2bin(substr($block, $offset, $blockLength * 2));
            $offset += $blockLength * 2;
        }

        if (count($data) == 3) {
            $data[] = $data[2];
            $data[2] = '';
        }

        $data[] = $this->createByte('00');

        return $data;
    }

    /**
     * Send our final message with all our data.
     *
     * @param string                    $response  Message 1 response (message 2)
     * @param string                    $username
     * @param string                    $password
     * @param string                    $timestamp
     * @param string                    $client
     * @param Swift_Transport_SmtpAgent $agent
     * @param bool                      $v2        Use version2 of the protocol
     *
     * @return string
     */
    protected function sendMessage3($response, $username, $password, $timestamp, $client, Swift_Transport_SmtpAgent $agent, $v2 = true)
    {
        list($domain, $username) = $this->getDomainAndUsername($username);
        //$challenge, $context, $targetInfoH, $targetName, $domainName, $workstation, $DNSDomainName, $DNSServerName, $blob, $ter
        list($challenge, , , , , $workstation, , , $blob) = $this->parseMessage2($response);

        if (!$v2) {
            // LMv1
            $lmResponse = $this->createLMPassword($password, $challenge);
            // NTLMv1
            $ntlmResponse = $this->createNTLMPassword($password, $challenge);
        } else {
            // LMv2
            $lmResponse = $this->createLMv2Password($password, $username, $domain, $challenge, $client);
            // NTLMv2
            $ntlmResponse = $this->createNTLMv2Hash($password, $username, $domain, $challenge, $blob, $timestamp, $client);
        }

        $message = $this->createMessage3($domain, $username, $workstation, $lmResponse, $ntlmResponse);

        return $agent->executeCommand(sprintf("%s\r\n", base64_encode($message)), array(235));
    }

    /**
     * Create our message 1.
     *
     * @return string
     */
    protected function createMessage1()
    {
        return self::NTLMSIG
        .$this->createByte('01') // Message 1
.$this->createByte('0702'); // Flags
    }

    /**
     * Create our message 3.
     *
     * @param string $domain
     * @param string $username
     * @param string $workstation
     * @param string $lmResponse
     * @param string $ntlmResponse
     *
     * @return string
     */
    protected function createMessage3($domain, $username, $workstation, $lmResponse, $ntlmResponse)
    {
        // Create security buffers
        $domainSec = $this->createSecurityBuffer($domain, 64);
        $domainInfo = $this->readSecurityBuffer(bin2hex($domainSec));
        $userSec = $this->createSecurityBuffer($username, ($domainInfo[0] + $domainInfo[1]) / 2);
        $userInfo = $this->readSecurityBuffer(bin2hex($userSec));
        $workSec = $this->createSecurityBuffer($workstation, ($userInfo[0] + $userInfo[1]) / 2);
        $workInfo = $this->readSecurityBuffer(bin2hex($workSec));
        $lmSec = $this->createSecurityBuffer($lmResponse, ($workInfo[0] + $workInfo[1]) / 2, true);
        $lmInfo = $this->readSecurityBuffer(bin2hex($lmSec));
        $ntlmSec = $this->createSecurityBuffer($ntlmResponse, ($lmInfo[0] + $lmInfo[1]) / 2, true);

        return self::NTLMSIG
        .$this->createByte('03') // TYPE 3 message
.$lmSec // LM response header
.$ntlmSec // NTLM response header
.$domainSec // Domain header
.$userSec // User header
.$workSec // Workstation header
.$this->createByte('000000009a', 8) // session key header (empty)
.$this->createByte('01020000') // FLAGS
.$this->convertTo16bit($domain) // domain name
.$this->convertTo16bit($username) // username
.$this->convertTo16bit($workstation) // workstation
.$lmResponse
        .$ntlmResponse;
    }

    /**
     * @param string $timestamp  Epoch timestamp in microseconds
     * @param string $client     Random bytes
     * @param string $targetInfo
     *
     * @return string
     */
    protected function createBlob($timestamp, $client, $targetInfo)
    {
        return $this->createByte('0101')
        .$this->createByte('00')
        .$timestamp
        .$client
        .$this->createByte('00')
        .$targetInfo
        .$this->createByte('00');
    }

    /**
     * Get domain and username from our username.
     *
     * @example DOMAIN\username
     *
     * @param string $name
     *
     * @return array
     */
    protected function getDomainAndUsername($name)
    {
        if (strpos($name, '\\') !== false) {
            return explode('\\', $name);
        }

        list($user, $domain) = explode('@', $name);

        return array($domain, $user);
    }

    /**
     * Create LMv1 response.
     *
     * @param string $password
     * @param string $challenge
     *
     * @return string
     */
    protected function createLMPassword($password, $challenge)
    {
        // FIRST PART
        $password = $this->createByte(strtoupper($password), 14, false);
        list($key1, $key2) = str_split($password, 7);

        $desKey1 = $this->createDesKey($key1);
        $desKey2 = $this->createDesKey($key2);

        $constantDecrypt = $this->createByte($this->desEncrypt(self::DESCONST, $desKey1).$this->desEncrypt(self::DESCONST, $desKey2), 21, false);

        // SECOND PART
        list($key1, $key2, $key3) = str_split($constantDecrypt, 7);

        $desKey1 = $this->createDesKey($key1);
        $desKey2 = $this->createDesKey($key2);
        $desKey3 = $this->createDesKey($key3);

        return $this->desEncrypt($challenge, $desKey1).$this->desEncrypt($challenge, $desKey2).$this->desEncrypt($challenge, $desKey3);
    }

    /**
     * Create NTLMv1 response.
     *
     * @param string $password
     * @param string $challenge
     *
     * @return string
     */
    protected function createNTLMPassword($password, $challenge)
    {
        // FIRST PART
        $ntlmHash = $this->createByte($this->md4Encrypt($password), 21, false);
        list($key1, $key2, $key3) = str_split($ntlmHash, 7);

        $desKey1 = $this->createDesKey($key1);
        $desKey2 = $this->createDesKey($key2);
        $desKey3 = $this->createDesKey($key3);

        return $this->desEncrypt($challenge, $desKey1).$this->desEncrypt($challenge, $desKey2).$this->desEncrypt($challenge, $desKey3);
    }

    /**
     * Convert a normal timestamp to a tenth of a microtime epoch time.
     *
     * @param string $time
     *
     * @return string
     */
    protected function getCorrectTimestamp($time)
    {
        // Get our timestamp (tricky!)
        bcscale(0);

        $time = number_format($time, 0, '.', ''); // save microtime to string
        $time = bcadd($time, '11644473600000'); // add epoch time
        $time = bcmul($time, 10000); // tenths of a microsecond.

        $binary = $this->si2bin($time, 64); // create 64 bit binary string
        $timestamp = '';
        for ($i = 0; $i < 8; ++$i) {
            $timestamp .= chr(bindec(substr($binary, -(($i + 1) * 8), 8)));
        }

        return $timestamp;
    }

    /**
     * Create LMv2 response.
     *
     * @param string $password
     * @param string $username
     * @param string $domain
     * @param string $challenge NTLM Challenge
     * @param string $client    Random string
     *
     * @return string
     */
    protected function createLMv2Password($password, $username, $domain, $challenge, $client)
    {
        $lmPass = '00'; // by default 00
        // if $password > 15 than we can't use this method
        if (strlen($password) <= 15) {
            $ntlmHash = $this->md4Encrypt($password);
            $ntml2Hash = $this->md5Encrypt($ntlmHash, $this->convertTo16bit(strtoupper($username).$domain));

            $lmPass = bin2hex($this->md5Encrypt($ntml2Hash, $challenge.$client).$client);
        }

        return $this->createByte($lmPass, 24);
    }

    /**
     * Create NTLMv2 response.
     *
     * @param string $password
     * @param string $username
     * @param string $domain
     * @param string $challenge  Hex values
     * @param string $targetInfo Hex values
     * @param string $timestamp
     * @param string $client     Random bytes
     *
     * @return string
     *
     * @see http://davenport.sourceforge.net/ntlm.html#theNtlmResponse
     */
    protected function createNTLMv2Hash($password, $username, $domain, $challenge, $targetInfo, $timestamp, $client)
    {
        $ntlmHash = $this->md4Encrypt($password);
        $ntml2Hash = $this->md5Encrypt($ntlmHash, $this->convertTo16bit(strtoupper($username).$domain));

        // create blob
        $blob = $this->createBlob($timestamp, $client, $targetInfo);

        $ntlmv2Response = $this->md5Encrypt($ntml2Hash, $challenge.$blob);

        return $ntlmv2Response.$blob;
    }

    protected function createDesKey($key)
    {
        $material = array(bin2hex($key[0]));
        $len = strlen($key);
        for ($i = 1; $i < $len; ++$i) {
            list($high, $low) = str_split(bin2hex($key[$i]));
            $v = $this->castToByte(ord($key[$i - 1]) << (7 + 1 - $i) | $this->uRShift(hexdec(dechex(hexdec($high) & 0xf).dechex(hexdec($low) & 0xf)), $i));
            $material[] = str_pad(substr(dechex($v), -2), 2, '0', STR_PAD_LEFT); // cast to byte
        }
        $material[] = str_pad(substr(dechex($this->castToByte(ord($key[6]) << 1)), -2), 2, '0');

        // odd parity
        foreach ($material as $k => $v) {
            $b = $this->castToByte(hexdec($v));
            $needsParity = (($this->uRShift($b, 7) ^ $this->uRShift($b, 6) ^ $this->uRShift($b, 5)
                        ^ $this->uRShift($b, 4) ^ $this->uRShift($b, 3) ^ $this->uRShift($b, 2)
                        ^ $this->uRShift($b, 1)) & 0x01) == 0;

            list($high, $low) = str_split($v);
            if ($needsParity) {
                $material[$k] = dechex(hexdec($high) | 0x0).dechex(hexdec($low) | 0x1);
            } else {
                $material[$k] = dechex(hexdec($high) & 0xf).dechex(hexdec($low) & 0xe);
            }
        }

        return $this->hex2bin(implode('', $material));
    }

    /** HELPER FUNCTIONS */
    /**
     * Create our security buffer depending on length and offset.
     *
     * @param string $value  Value we want to put in
     * @param int    $offset start of value
     * @param bool   $is16   Do we 16bit string or not?
     *
     * @return string
     */
    protected function createSecurityBuffer($value, $offset, $is16 = false)
    {
        $length = strlen(bin2hex($value));
        $length = $is16 ? $length / 2 : $length;
        $length = $this->createByte(str_pad(dechex($length), 2, '0', STR_PAD_LEFT), 2);

        return $length.$length.$this->createByte(dechex($offset), 4);
    }

    /**
     * Read our security buffer to fetch length and offset of our value.
     *
     * @param string $value Securitybuffer in hex
     *
     * @return array array with length and offset
     */
    protected function readSecurityBuffer($value)
    {
        $length = floor(hexdec(substr($value, 0, 4)) / 256) * 2;
        $offset = floor(hexdec(substr($value, 8, 4)) / 256) * 2;

        return array($length, $offset);
    }

    /**
     * Cast to byte java equivalent to (byte).
     *
     * @param int $v
     *
     * @return int
     */
    protected function castToByte($v)
    {
        return (($v + 128) % 256) - 128;
    }

    /**
     * Java unsigned right bitwise
     * $a >>> $b.
     *
     * @param int $a
     * @param int $b
     *
     * @return int
     */
    protected function uRShift($a, $b)
    {
        if ($b == 0) {
            return $a;
        }

        return ($a >> $b) & ~(1 << (8 * PHP_INT_SIZE - 1) >> ($b - 1));
    }

    /**
     * Right padding with 0 to certain length.
     *
     * @param string $input
     * @param int    $bytes Length of bytes
     * @param bool   $isHex Did we provided hex value
     *
     * @return string
     */
    protected function createByte($input, $bytes = 4, $isHex = true)
    {
        if ($isHex) {
            $byte = $this->hex2bin(str_pad($input, $bytes * 2, '00'));
        } else {
            $byte = str_pad($input, $bytes, "\x00");
        }

        return $byte;
    }

    /**
     * Create random bytes.
     *
     * @param $length
     *
     * @return string
     */
    protected function getRandomBytes($length)
    {
        $bytes = openssl_random_pseudo_bytes($length, $strong);

        if (false !== $bytes && true === $strong) {
            return $bytes;
        }

        throw new RuntimeException('OpenSSL did not produce a secure random number.');
    }

    /** ENCRYPTION ALGORITHMS */
    /**
     * DES Encryption.
     *
     * @param string $value An 8-byte string
     * @param string $key
     *
     * @return string
     */
    protected function desEncrypt($value, $key)
    {
        // 1 == OPENSSL_RAW_DATA - but constant is only available as of PHP 5.4.
        return substr(openssl_encrypt($value, 'DES-ECB', $key, 1), 0, 8);
    }

    /**
     * MD5 Encryption.
     *
     * @param string $key Encryption key
     * @param string $msg Message to encrypt
     *
     * @return string
     */
    protected function md5Encrypt($key, $msg)
    {
        $blocksize = 64;
        if (strlen($key) > $blocksize) {
            $key = pack('H*', md5($key));
        }

        $key = str_pad($key, $blocksize, "\0");
        $ipadk = $key ^ str_repeat("\x36", $blocksize);
        $opadk = $key ^ str_repeat("\x5c", $blocksize);

        return pack('H*', md5($opadk.pack('H*', md5($ipadk.$msg))));
    }

    /**
     * MD4 Encryption.
     *
     * @param string $input
     *
     * @return string
     *
     * @see http://php.net/manual/en/ref.hash.php
     */
    protected function md4Encrypt($input)
    {
        $input = $this->convertTo16bit($input);

        return function_exists('hash') ? $this->hex2bin(hash('md4', $input)) : mhash(MHASH_MD4, $input);
    }

    /**
     * Convert UTF-8 to UTF-16.
     *
     * @param string $input
     *
     * @return string
     */
    protected function convertTo16bit($input)
    {
        return iconv('UTF-8', 'UTF-16LE', $input);
    }

    /**
     * Hex2bin replacement for < PHP 5.4.
     *
     * @param string $hex
     *
     * @return string Binary
     */
    protected function hex2bin($hex)
    {
        if (function_exists('hex2bin')) {
            return hex2bin($hex);
        } else {
            return pack('H*', $hex);
        }
    }

    /**
     * @param string $message
     */
    protected function debug($message)
    {
        $message = bin2hex($message);
        $messageId = substr($message, 16, 8);
        echo substr($message, 0, 16)." NTLMSSP Signature<br />\n";
        echo $messageId." Type Indicator<br />\n";

        if ($messageId == '02000000') {
            $map = array(
                'Challenge',
                'Context',
                'Target Information Security Buffer',
                'Target Name Data',
                'NetBIOS Domain Name',
                'NetBIOS Server Name',
                'DNS Domain Name',
                'DNS Server Name',
                'BLOB',
                'Target Information Terminator',
            );

            $data = $this->parseMessage2($this->hex2bin($message));

            foreach ($map as $key => $value) {
                echo bin2hex($data[$key]).' - '.$data[$key].' ||| '.$value."<br />\n";
            }
        } elseif ($messageId == '03000000') {
            $i = 0;
            $data[$i++] = substr($message, 24, 16);
            list($lmLength, $lmOffset) = $this->readSecurityBuffer($data[$i - 1]);

            $data[$i++] = substr($message, 40, 16);
            list($ntmlLength, $ntmlOffset) = $this->readSecurityBuffer($data[$i - 1]);

            $data[$i++] = substr($message, 56, 16);
            list($targetLength, $targetOffset) = $this->readSecurityBuffer($data[$i - 1]);

            $data[$i++] = substr($message, 72, 16);
            list($userLength, $userOffset) = $this->readSecurityBuffer($data[$i - 1]);

            $data[$i++] = substr($message, 88, 16);
            list($workLength, $workOffset) = $this->readSecurityBuffer($data[$i - 1]);

            $data[$i++] = substr($message, 104, 16);
            $data[$i++] = substr($message, 120, 8);
            $data[$i++] = substr($message, $targetOffset, $targetLength);
            $data[$i++] = substr($message, $userOffset, $userLength);
            $data[$i++] = substr($message, $workOffset, $workLength);
            $data[$i++] = substr($message, $lmOffset, $lmLength);
            $data[$i] = substr($message, $ntmlOffset, $ntmlLength);

            $map = array(
                'LM Response Security Buffer',
                'NTLM Response Security Buffer',
                'Target Name Security Buffer',
                'User Name Security Buffer',
                'Workstation Name Security Buffer',
                'Session Key Security Buffer',
                'Flags',
                'Target Name Data',
                'User Name Data',
                'Workstation Name Data',
                'LM Response Data',
                'NTLM Response Data',
            );

            foreach ($map as $key => $value) {
                echo $data[$key].' - '.$this->hex2bin($data[$key]).' ||| '.$value."<br />\n";
            }
        }

        echo '<br /><br />';
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Handles PLAIN authentication.
 *
 * @author Chris Corbyn
 */
class Swift_Transport_Esmtp_Auth_PlainAuthenticator implements Swift_Transport_Esmtp_Authenticator
{
    /**
     * Get the name of the AUTH mechanism this Authenticator handles.
     *
     * @return string
     */
    public function getAuthKeyword()
    {
        return 'PLAIN';
    }

    /**
     * Try to authenticate the user with $username and $password.
     *
     * @param Swift_Transport_SmtpAgent $agent
     * @param string                    $username
     * @param string                    $password
     *
     * @return bool
     */
    public function authenticate(Swift_Transport_SmtpAgent $agent, $username, $password)
    {
        try {
            $message = base64_encode($username.chr(0).$username.chr(0).$password);
            $agent->executeCommand(sprintf("AUTH PLAIN %s\r\n", $message), array(235));

            return true;
        } catch (Swift_TransportException $e) {
            $agent->executeCommand("RSET\r\n", array(250));

            return false;
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Handles XOAUTH2 authentication.
 *
 * Example:
 * <code>
 * $transport = Swift_SmtpTransport::newInstance('smtp.gmail.com', 587, 'tls')
 *   ->setAuthMode('XOAUTH2')
 *   ->setUsername('YOUR_EMAIL_ADDRESS')
 *   ->setPassword('YOUR_ACCESS_TOKEN');
 * </code>
 *
 * @author xu.li<AthenaLightenedMyPath@gmail.com>
 *
 * @see        https://developers.google.com/google-apps/gmail/xoauth2_protocol
 */
class Swift_Transport_Esmtp_Auth_XOAuth2Authenticator implements Swift_Transport_Esmtp_Authenticator
{
    /**
     * Get the name of the AUTH mechanism this Authenticator handles.
     *
     * @return string
     */
    public function getAuthKeyword()
    {
        return 'XOAUTH2';
    }

    /**
     * Try to authenticate the user with $email and $token.
     *
     * @param Swift_Transport_SmtpAgent $agent
     * @param string                    $email
     * @param string                    $token
     *
     * @return bool
     */
    public function authenticate(Swift_Transport_SmtpAgent $agent, $email, $token)
    {
        try {
            $param = $this->constructXOAuth2Params($email, $token);
            $agent->executeCommand('AUTH XOAUTH2 '.$param."\r\n", array(235));

            return true;
        } catch (Swift_TransportException $e) {
            $agent->executeCommand("RSET\r\n", array(250));

            return false;
        }
    }

    /**
     * Construct the auth parameter.
     *
     * @see https://developers.google.com/google-apps/gmail/xoauth2_protocol#the_sasl_xoauth2_mechanism
     */
    protected function constructXOAuth2Params($email, $token)
    {
        return base64_encode("user=$email\1auth=Bearer $token\1\1");
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An Authentication mechanism.
 *
 * @author Chris Corbyn
 */
interface Swift_Transport_Esmtp_Authenticator
{
    /**
     * Get the name of the AUTH mechanism this Authenticator handles.
     *
     * @return string
     */
    public function getAuthKeyword();

    /**
     * Try to authenticate the user with $username and $password.
     *
     * @param Swift_Transport_SmtpAgent $agent
     * @param string                    $username
     * @param string                    $password
     *
     * @return bool
     */
    public function authenticate(Swift_Transport_SmtpAgent $agent, $username, $password);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An ESMTP handler for AUTH support.
 *
 * @author Chris Corbyn
 */
class Swift_Transport_Esmtp_AuthHandler implements Swift_Transport_EsmtpHandler
{
    /**
     * Authenticators available to process the request.
     *
     * @var Swift_Transport_Esmtp_Authenticator[]
     */
    private $_authenticators = array();

    /**
     * The username for authentication.
     *
     * @var string
     */
    private $_username;

    /**
     * The password for authentication.
     *
     * @var string
     */
    private $_password;

    /**
     * The auth mode for authentication.
     *
     * @var string
     */
    private $_auth_mode;

    /**
     * The ESMTP AUTH parameters available.
     *
     * @var string[]
     */
    private $_esmtpParams = array();

    /**
     * Create a new AuthHandler with $authenticators for support.
     *
     * @param Swift_Transport_Esmtp_Authenticator[] $authenticators
     */
    public function __construct(array $authenticators)
    {
        $this->setAuthenticators($authenticators);
    }

    /**
     * Set the Authenticators which can process a login request.
     *
     * @param Swift_Transport_Esmtp_Authenticator[] $authenticators
     */
    public function setAuthenticators(array $authenticators)
    {
        $this->_authenticators = $authenticators;
    }

    /**
     * Get the Authenticators which can process a login request.
     *
     * @return Swift_Transport_Esmtp_Authenticator[]
     */
    public function getAuthenticators()
    {
        return $this->_authenticators;
    }

    /**
     * Set the username to authenticate with.
     *
     * @param string $username
     */
    public function setUsername($username)
    {
        $this->_username = $username;
    }

    /**
     * Get the username to authenticate with.
     *
     * @return string
     */
    public function getUsername()
    {
        return $this->_username;
    }

    /**
     * Set the password to authenticate with.
     *
     * @param string $password
     */
    public function setPassword($password)
    {
        $this->_password = $password;
    }

    /**
     * Get the password to authenticate with.
     *
     * @return string
     */
    public function getPassword()
    {
        return $this->_password;
    }

    /**
     * Set the auth mode to use to authenticate.
     *
     * @param string $mode
     */
    public function setAuthMode($mode)
    {
        $this->_auth_mode = $mode;
    }

    /**
     * Get the auth mode to use to authenticate.
     *
     * @return string
     */
    public function getAuthMode()
    {
        return $this->_auth_mode;
    }

    /**
     * Get the name of the ESMTP extension this handles.
     *
     * @return bool
     */
    public function getHandledKeyword()
    {
        return 'AUTH';
    }

    /**
     * Set the parameters which the EHLO greeting indicated.
     *
     * @param string[] $parameters
     */
    public function setKeywordParams(array $parameters)
    {
        $this->_esmtpParams = $parameters;
    }

    /**
     * Runs immediately after a EHLO has been issued.
     *
     * @param Swift_Transport_SmtpAgent $agent to read/write
     */
    public function afterEhlo(Swift_Transport_SmtpAgent $agent)
    {
        if ($this->_username) {
            $count = 0;
            foreach ($this->_getAuthenticatorsForAgent() as $authenticator) {
                if (in_array(strtolower($authenticator->getAuthKeyword()),
                    array_map('strtolower', $this->_esmtpParams))) {
                    ++$count;
                    if ($authenticator->authenticate($agent, $this->_username, $this->_password)) {
                        return;
                    }
                }
            }
            throw new Swift_TransportException(
                'Failed to authenticate on SMTP server with username "'.
                $this->_username.'" using '.$count.' possible authenticators'
                );
        }
    }

    /**
     * Not used.
     */
    public function getMailParams()
    {
        return array();
    }

    /**
     * Not used.
     */
    public function getRcptParams()
    {
        return array();
    }

    /**
     * Not used.
     */
    public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = array(), &$failedRecipients = null, &$stop = false)
    {
    }

    /**
     * Returns +1, -1 or 0 according to the rules for usort().
     *
     * This method is called to ensure extensions can be execute in an appropriate order.
     *
     * @param string $esmtpKeyword to compare with
     *
     * @return int
     */
    public function getPriorityOver($esmtpKeyword)
    {
        return 0;
    }

    /**
     * Returns an array of method names which are exposed to the Esmtp class.
     *
     * @return string[]
     */
    public function exposeMixinMethods()
    {
        return array('setUsername', 'getUsername', 'setPassword', 'getPassword', 'setAuthMode', 'getAuthMode');
    }

    /**
     * Not used.
     */
    public function resetState()
    {
    }

    /**
     * Returns the authenticator list for the given agent.
     *
     * @param Swift_Transport_SmtpAgent $agent
     *
     * @return array
     */
    protected function _getAuthenticatorsForAgent()
    {
        if (!$mode = strtolower($this->_auth_mode)) {
            return $this->_authenticators;
        }

        foreach ($this->_authenticators as $authenticator) {
            if (strtolower($authenticator->getAuthKeyword()) == $mode) {
                return array($authenticator);
            }
        }

        throw new Swift_TransportException('Auth mode '.$mode.' is invalid');
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * An ESMTP handler.
 *
 * @author Chris Corbyn
 */
interface Swift_Transport_EsmtpHandler
{
    /**
     * Get the name of the ESMTP extension this handles.
     *
     * @return bool
     */
    public function getHandledKeyword();

    /**
     * Set the parameters which the EHLO greeting indicated.
     *
     * @param string[] $parameters
     */
    public function setKeywordParams(array $parameters);

    /**
     * Runs immediately after a EHLO has been issued.
     *
     * @param Swift_Transport_SmtpAgent $agent to read/write
     */
    public function afterEhlo(Swift_Transport_SmtpAgent $agent);

    /**
     * Get params which are appended to MAIL FROM:<>.
     *
     * @return string[]
     */
    public function getMailParams();

    /**
     * Get params which are appended to RCPT TO:<>.
     *
     * @return string[]
     */
    public function getRcptParams();

    /**
     * Runs when a command is due to be sent.
     *
     * @param Swift_Transport_SmtpAgent $agent            to read/write
     * @param string                    $command          to send
     * @param int[]                     $codes            expected in response
     * @param string[]                  $failedRecipients to collect failures
     * @param bool                      $stop             to be set true  by-reference if the command is now sent
     */
    public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = array(), &$failedRecipients = null, &$stop = false);

    /**
     * Returns +1, -1 or 0 according to the rules for usort().
     *
     * This method is called to ensure extensions can be execute in an appropriate order.
     *
     * @param string $esmtpKeyword to compare with
     *
     * @return int
     */
    public function getPriorityOver($esmtpKeyword);

    /**
     * Returns an array of method names which are exposed to the Esmtp class.
     *
     * @return string[]
     */
    public function exposeMixinMethods();

    /**
     * Tells this handler to clear any buffers and reset its state.
     */
    public function resetState();
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Sends Messages over SMTP with ESMTP support.
 *
 * @author Chris Corbyn
 */
class Swift_Transport_EsmtpTransport extends Swift_Transport_AbstractSmtpTransport implements Swift_Transport_SmtpAgent
{
    /**
     * ESMTP extension handlers.
     *
     * @var Swift_Transport_EsmtpHandler[]
     */
    private $_handlers = array();

    /**
     * ESMTP capabilities.
     *
     * @var string[]
     */
    private $_capabilities = array();

    /**
     * Connection buffer parameters.
     *
     * @var array
     */
    private $_params = array(
        'protocol' => 'tcp',
        'host' => 'localhost',
        'port' => 25,
        'timeout' => 30,
        'blocking' => 1,
        'tls' => false,
        'type' => Swift_Transport_IoBuffer::TYPE_SOCKET,
        'stream_context_options' => array(),
        );

    /**
     * Creates a new EsmtpTransport using the given I/O buffer.
     *
     * @param Swift_Transport_IoBuffer       $buf
     * @param Swift_Transport_EsmtpHandler[] $extensionHandlers
     * @param Swift_Events_EventDispatcher   $dispatcher
     */
    public function __construct(Swift_Transport_IoBuffer $buf, array $extensionHandlers, Swift_Events_EventDispatcher $dispatcher)
    {
        parent::__construct($buf, $dispatcher);
        $this->setExtensionHandlers($extensionHandlers);
    }

    /**
     * Set the host to connect to.
     *
     * @param string $host
     *
     * @return Swift_Transport_EsmtpTransport
     */
    public function setHost($host)
    {
        $this->_params['host'] = $host;

        return $this;
    }

    /**
     * Get the host to connect to.
     *
     * @return string
     */
    public function getHost()
    {
        return $this->_params['host'];
    }

    /**
     * Set the port to connect to.
     *
     * @param int $port
     *
     * @return Swift_Transport_EsmtpTransport
     */
    public function setPort($port)
    {
        $this->_params['port'] = (int) $port;

        return $this;
    }

    /**
     * Get the port to connect to.
     *
     * @return int
     */
    public function getPort()
    {
        return $this->_params['port'];
    }

    /**
     * Set the connection timeout.
     *
     * @param int $timeout seconds
     *
     * @return Swift_Transport_EsmtpTransport
     */
    public function setTimeout($timeout)
    {
        $this->_params['timeout'] = (int) $timeout;
        $this->_buffer->setParam('timeout', (int) $timeout);

        return $this;
    }

    /**
     * Get the connection timeout.
     *
     * @return int
     */
    public function getTimeout()
    {
        return $this->_params['timeout'];
    }

    /**
     * Set the encryption type (tls or ssl).
     *
     * @param string $encryption
     *
     * @return Swift_Transport_EsmtpTransport
     */
    public function setEncryption($encryption)
    {
        $encryption = strtolower($encryption);
        if ('tls' == $encryption) {
            $this->_params['protocol'] = 'tcp';
            $this->_params['tls'] = true;
        } else {
            $this->_params['protocol'] = $encryption;
            $this->_params['tls'] = false;
        }

        return $this;
    }

    /**
     * Get the encryption type.
     *
     * @return string
     */
    public function getEncryption()
    {
        return $this->_params['tls'] ? 'tls' : $this->_params['protocol'];
    }

    /**
     * Sets the stream context options.
     *
     * @param array $options
     *
     * @return Swift_Transport_EsmtpTransport
     */
    public function setStreamOptions($options)
    {
        $this->_params['stream_context_options'] = $options;

        return $this;
    }

    /**
     * Returns the stream context options.
     *
     * @return array
     */
    public function getStreamOptions()
    {
        return $this->_params['stream_context_options'];
    }

    /**
     * Sets the source IP.
     *
     * @param string $source
     *
     * @return Swift_Transport_EsmtpTransport
     */
    public function setSourceIp($source)
    {
        $this->_params['sourceIp'] = $source;

        return $this;
    }

    /**
     * Returns the IP used to connect to the destination.
     *
     * @return string
     */
    public function getSourceIp()
    {
        return isset($this->_params['sourceIp']) ? $this->_params['sourceIp'] : null;
    }

    /**
     * Set ESMTP extension handlers.
     *
     * @param Swift_Transport_EsmtpHandler[] $handlers
     *
     * @return Swift_Transport_EsmtpTransport
     */
    public function setExtensionHandlers(array $handlers)
    {
        $assoc = array();
        foreach ($handlers as $handler) {
            $assoc[$handler->getHandledKeyword()] = $handler;
        }

        @uasort($assoc, array($this, '_sortHandlers'));
        $this->_handlers = $assoc;
        $this->_setHandlerParams();

        return $this;
    }

    /**
     * Get ESMTP extension handlers.
     *
     * @return Swift_Transport_EsmtpHandler[]
     */
    public function getExtensionHandlers()
    {
        return array_values($this->_handlers);
    }

    /**
     * Run a command against the buffer, expecting the given response codes.
     *
     * If no response codes are given, the response will not be validated.
     * If codes are given, an exception will be thrown on an invalid response.
     *
     * @param string   $command
     * @param int[]    $codes
     * @param string[] $failures An array of failures by-reference
     *
     * @return string
     */
    public function executeCommand($command, $codes = array(), &$failures = null)
    {
        $failures = (array) $failures;
        $stopSignal = false;
        $response = null;
        foreach ($this->_getActiveHandlers() as $handler) {
            $response = $handler->onCommand(
                $this, $command, $codes, $failures, $stopSignal
                );
            if ($stopSignal) {
                return $response;
            }
        }

        return parent::executeCommand($command, $codes, $failures);
    }

    // -- Mixin invocation code

    /** Mixin handling method for ESMTP handlers */
    public function __call($method, $args)
    {
        foreach ($this->_handlers as $handler) {
            if (in_array(strtolower($method),
                array_map('strtolower', (array) $handler->exposeMixinMethods())
                )) {
                $return = call_user_func_array(array($handler, $method), $args);
                // Allow fluid method calls
                if (is_null($return) && substr($method, 0, 3) == 'set') {
                    return $this;
                } else {
                    return $return;
                }
            }
        }
        trigger_error('Call to undefined method '.$method, E_USER_ERROR);
    }

    /** Get the params to initialize the buffer */
    protected function _getBufferParams()
    {
        return $this->_params;
    }

    /** Overridden to perform EHLO instead */
    protected function _doHeloCommand()
    {
        try {
            $response = $this->executeCommand(
                sprintf("EHLO %s\r\n", $this->_domain), array(250)
                );
        } catch (Swift_TransportException $e) {
            return parent::_doHeloCommand();
        }

        if ($this->_params['tls']) {
            try {
                $this->executeCommand("STARTTLS\r\n", array(220));

                if (!$this->_buffer->startTLS()) {
                    throw new Swift_TransportException('Unable to connect with TLS encryption');
                }

                try {
                    $response = $this->executeCommand(
                        sprintf("EHLO %s\r\n", $this->_domain), array(250)
                        );
                } catch (Swift_TransportException $e) {
                    return parent::_doHeloCommand();
                }
            } catch (Swift_TransportException $e) {
                $this->_throwException($e);
            }
        }

        $this->_capabilities = $this->_getCapabilities($response);
        $this->_setHandlerParams();
        foreach ($this->_getActiveHandlers() as $handler) {
            $handler->afterEhlo($this);
        }
    }

    /** Overridden to add Extension support */
    protected function _doMailFromCommand($address)
    {
        $handlers = $this->_getActiveHandlers();
        $params = array();
        foreach ($handlers as $handler) {
            $params = array_merge($params, (array) $handler->getMailParams());
        }
        $paramStr = !empty($params) ? ' '.implode(' ', $params) : '';
        $this->executeCommand(
            sprintf("MAIL FROM:<%s>%s\r\n", $address, $paramStr), array(250)
            );
    }

    /** Overridden to add Extension support */
    protected function _doRcptToCommand($address)
    {
        $handlers = $this->_getActiveHandlers();
        $params = array();
        foreach ($handlers as $handler) {
            $params = array_merge($params, (array) $handler->getRcptParams());
        }
        $paramStr = !empty($params) ? ' '.implode(' ', $params) : '';
        $this->executeCommand(
            sprintf("RCPT TO:<%s>%s\r\n", $address, $paramStr), array(250, 251, 252)
            );
    }

    /** Determine ESMTP capabilities by function group */
    private function _getCapabilities($ehloResponse)
    {
        $capabilities = array();
        $ehloResponse = trim($ehloResponse);
        $lines = explode("\r\n", $ehloResponse);
        array_shift($lines);
        foreach ($lines as $line) {
            if (preg_match('/^[0-9]{3}[ -]([A-Z0-9-]+)((?:[ =].*)?)$/Di', $line, $matches)) {
                $keyword = strtoupper($matches[1]);
                $paramStr = strtoupper(ltrim($matches[2], ' ='));
                $params = !empty($paramStr) ? explode(' ', $paramStr) : array();
                $capabilities[$keyword] = $params;
            }
        }

        return $capabilities;
    }

    /** Set parameters which are used by each extension handler */
    private function _setHandlerParams()
    {
        foreach ($this->_handlers as $keyword => $handler) {
            if (array_key_exists($keyword, $this->_capabilities)) {
                $handler->setKeywordParams($this->_capabilities[$keyword]);
            }
        }
    }

    /** Get ESMTP handlers which are currently ok to use */
    private function _getActiveHandlers()
    {
        $handlers = array();
        foreach ($this->_handlers as $keyword => $handler) {
            if (array_key_exists($keyword, $this->_capabilities)) {
                $handlers[] = $handler;
            }
        }

        return $handlers;
    }

    /** Custom sort for extension handler ordering */
    private function _sortHandlers($a, $b)
    {
        return $a->getPriorityOver($b->getHandledKeyword());
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Contains a list of redundant Transports so when one fails, the next is used.
 *
 * @author Chris Corbyn
 */
class Swift_Transport_FailoverTransport extends Swift_Transport_LoadBalancedTransport
{
    /**
     * Registered transport currently used.
     *
     * @var Swift_Transport
     */
    private $_currentTransport;

    // needed as __construct is called from elsewhere explicitly
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Send the given Message.
     *
     * Recipient/sender data will be retrieved from the Message API.
     * The return value is the number of recipients who were accepted for delivery.
     *
     * @param Swift_Mime_Message $message
     * @param string[]           $failedRecipients An array of failures by-reference
     *
     * @return int
     */
    public function send(Swift_Mime_Message $message, &$failedRecipients = null)
    {
        $maxTransports = count($this->_transports);
        $sent = 0;
        $this->_lastUsedTransport = null;

        for ($i = 0; $i < $maxTransports
            && $transport = $this->_getNextTransport(); ++$i) {
            try {
                if (!$transport->isStarted()) {
                    $transport->start();
                }

                if ($sent = $transport->send($message, $failedRecipients)) {
                    $this->_lastUsedTransport = $transport;

                    return $sent;
                }
            } catch (Swift_TransportException $e) {
                $this->_killCurrentTransport();
            }
        }

        if (count($this->_transports) == 0) {
            throw new Swift_TransportException(
                'All Transports in FailoverTransport failed, or no Transports available'
                );
        }

        return $sent;
    }

    protected function _getNextTransport()
    {
        if (!isset($this->_currentTransport)) {
            $this->_currentTransport = parent::_getNextTransport();
        }

        return $this->_currentTransport;
    }

    protected function _killCurrentTransport()
    {
        $this->_currentTransport = null;
        parent::_killCurrentTransport();
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Buffers input and output to a resource.
 *
 * @author Chris Corbyn
 */
interface Swift_Transport_IoBuffer extends Swift_InputByteStream, Swift_OutputByteStream
{
    /** A socket buffer over TCP */
    const TYPE_SOCKET = 0x0001;

    /** A process buffer with I/O support */
    const TYPE_PROCESS = 0x0010;

    /**
     * Perform any initialization needed, using the given $params.
     *
     * Parameters will vary depending upon the type of IoBuffer used.
     *
     * @param array $params
     */
    public function initialize(array $params);

    /**
     * Set an individual param on the buffer (e.g. switching to SSL).
     *
     * @param string $param
     * @param mixed  $value
     */
    public function setParam($param, $value);

    /**
     * Perform any shutdown logic needed.
     */
    public function terminate();

    /**
     * Set an array of string replacements which should be made on data written
     * to the buffer.
     *
     * This could replace LF with CRLF for example.
     *
     * @param string[] $replacements
     */
    public function setWriteTranslations(array $replacements);

    /**
     * Get a line of output (including any CRLF).
     *
     * The $sequence number comes from any writes and may or may not be used
     * depending upon the implementation.
     *
     * @param int $sequence of last write to scan from
     *
     * @return string
     */
    public function readLine($sequence);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Redundantly and rotationally uses several Transports when sending.
 *
 * @author Chris Corbyn
 */
class Swift_Transport_LoadBalancedTransport implements Swift_Transport
{
    /**
     * Transports which are deemed useless.
     *
     * @var Swift_Transport[]
     */
    private $_deadTransports = array();

    /**
     * The Transports which are used in rotation.
     *
     * @var Swift_Transport[]
     */
    protected $_transports = array();

    /**
     * The Transport used in the last successful send operation.
     *
     * @var Swift_Transport
     */
    protected $_lastUsedTransport = null;

    // needed as __construct is called from elsewhere explicitly
    public function __construct()
    {
    }

    /**
     * Set $transports to delegate to.
     *
     * @param Swift_Transport[] $transports
     */
    public function setTransports(array $transports)
    {
        $this->_transports = $transports;
        $this->_deadTransports = array();
    }

    /**
     * Get $transports to delegate to.
     *
     * @return Swift_Transport[]
     */
    public function getTransports()
    {
        return array_merge($this->_transports, $this->_deadTransports);
    }

    /**
     * Get the Transport used in the last successful send operation.
     *
     * @return Swift_Transport
     */
    public function getLastUsedTransport()
    {
        return $this->_lastUsedTransport;
    }

    /**
     * Test if this Transport mechanism has started.
     *
     * @return bool
     */
    public function isStarted()
    {
        return count($this->_transports) > 0;
    }

    /**
     * Start this Transport mechanism.
     */
    public function start()
    {
        $this->_transports = array_merge($this->_transports, $this->_deadTransports);
    }

    /**
     * Stop this Transport mechanism.
     */
    public function stop()
    {
        foreach ($this->_transports as $transport) {
            $transport->stop();
        }
    }

    /**
     * Send the given Message.
     *
     * Recipient/sender data will be retrieved from the Message API.
     * The return value is the number of recipients who were accepted for delivery.
     *
     * @param Swift_Mime_Message $message
     * @param string[]           $failedRecipients An array of failures by-reference
     *
     * @return int
     */
    public function send(Swift_Mime_Message $message, &$failedRecipients = null)
    {
        $maxTransports = count($this->_transports);
        $sent = 0;
        $this->_lastUsedTransport = null;

        for ($i = 0; $i < $maxTransports
            && $transport = $this->_getNextTransport(); ++$i) {
            try {
                if (!$transport->isStarted()) {
                    $transport->start();
                }
                if ($sent = $transport->send($message, $failedRecipients)) {
                    $this->_lastUsedTransport = $transport;
                    break;
                }
            } catch (Swift_TransportException $e) {
                $this->_killCurrentTransport();
            }
        }

        if (count($this->_transports) == 0) {
            throw new Swift_TransportException(
                'All Transports in LoadBalancedTransport failed, or no Transports available'
                );
        }

        return $sent;
    }

    /**
     * Register a plugin.
     *
     * @param Swift_Events_EventListener $plugin
     */
    public function registerPlugin(Swift_Events_EventListener $plugin)
    {
        foreach ($this->_transports as $transport) {
            $transport->registerPlugin($plugin);
        }
    }

    /**
     * Rotates the transport list around and returns the first instance.
     *
     * @return Swift_Transport
     */
    protected function _getNextTransport()
    {
        if ($next = array_shift($this->_transports)) {
            $this->_transports[] = $next;
        }

        return $next;
    }

    /**
     * Tag the currently used (top of stack) transport as dead/useless.
     */
    protected function _killCurrentTransport()
    {
        if ($transport = array_pop($this->_transports)) {
            try {
                $transport->stop();
            } catch (Exception $e) {
            }
            $this->_deadTransports[] = $transport;
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * This interface intercepts calls to the mail() function.
 *
 * @author     Chris Corbyn
 */
interface Swift_Transport_MailInvoker
{
    /**
     * Send mail via the mail() function.
     *
     * This method takes the same arguments as PHP mail().
     *
     * @param string $to
     * @param string $subject
     * @param string $body
     * @param string $headers
     * @param string $extraParams
     *
     * @return bool
     */
    public function mail($to, $subject, $body, $headers = null, $extraParams = null);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Sends Messages using the mail() function.
 *
 * It is advised that users do not use this transport if at all possible
 * since a number of plugin features cannot be used in conjunction with this
 * transport due to the internal interface in PHP itself.
 *
 * The level of error reporting with this transport is incredibly weak, again
 * due to limitations of PHP's internal mail() function.  You'll get an
 * all-or-nothing result from sending.
 *
 * @author Chris Corbyn
 *
 * @deprecated since 5.4.5 (to be removed in 6.0)
 */
class Swift_Transport_MailTransport implements Swift_Transport
{
    /** Additional parameters to pass to mail() */
    private $_extraParams = '-f%s';

    /** The event dispatcher from the plugin API */
    private $_eventDispatcher;

    /** An invoker that calls the mail() function */
    private $_invoker;

    /**
     * Create a new MailTransport with the $log.
     *
     * @param Swift_Transport_MailInvoker  $invoker
     * @param Swift_Events_EventDispatcher $eventDispatcher
     */
    public function __construct(Swift_Transport_MailInvoker $invoker, Swift_Events_EventDispatcher $eventDispatcher)
    {
        @trigger_error(sprintf('The %s class is deprecated since version 5.4.5 and will be removed in 6.0. Use the Sendmail or SMTP transport instead.', __CLASS__), E_USER_DEPRECATED);

        $this->_invoker = $invoker;
        $this->_eventDispatcher = $eventDispatcher;
    }

    /**
     * Not used.
     */
    public function isStarted()
    {
        return false;
    }

    /**
     * Not used.
     */
    public function start()
    {
    }

    /**
     * Not used.
     */
    public function stop()
    {
    }

    /**
     * Set the additional parameters used on the mail() function.
     *
     * This string is formatted for sprintf() where %s is the sender address.
     *
     * @param string $params
     *
     * @return Swift_Transport_MailTransport
     */
    public function setExtraParams($params)
    {
        $this->_extraParams = $params;

        return $this;
    }

    /**
     * Get the additional parameters used on the mail() function.
     *
     * This string is formatted for sprintf() where %s is the sender address.
     *
     * @return string
     */
    public function getExtraParams()
    {
        return $this->_extraParams;
    }

    /**
     * Send the given Message.
     *
     * Recipient/sender data will be retrieved from the Message API.
     * The return value is the number of recipients who were accepted for delivery.
     *
     * @param Swift_Mime_Message $message
     * @param string[]           $failedRecipients An array of failures by-reference
     *
     * @return int
     */
    public function send(Swift_Mime_Message $message, &$failedRecipients = null)
    {
        $failedRecipients = (array) $failedRecipients;

        if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) {
            $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
            if ($evt->bubbleCancelled()) {
                return 0;
            }
        }

        $count = (
            count((array) $message->getTo())
            + count((array) $message->getCc())
            + count((array) $message->getBcc())
            );

        $toHeader = $message->getHeaders()->get('To');
        $subjectHeader = $message->getHeaders()->get('Subject');

        if (0 === $count) {
            $this->_throwException(new Swift_TransportException('Cannot send message without a recipient'));
        }
        $to = $toHeader ? $toHeader->getFieldBody() : '';
        $subject = $subjectHeader ? $subjectHeader->getFieldBody() : '';

        $reversePath = $this->_getReversePath($message);

        // Remove headers that would otherwise be duplicated
        $message->getHeaders()->remove('To');
        $message->getHeaders()->remove('Subject');

        $messageStr = $message->toString();

        if ($toHeader) {
            $message->getHeaders()->set($toHeader);
        }
        $message->getHeaders()->set($subjectHeader);

        // Separate headers from body
        if (false !== $endHeaders = strpos($messageStr, "\r\n\r\n")) {
            $headers = substr($messageStr, 0, $endHeaders)."\r\n"; //Keep last EOL
            $body = substr($messageStr, $endHeaders + 4);
        } else {
            $headers = $messageStr."\r\n";
            $body = '';
        }

        unset($messageStr);

        if ("\r\n" != PHP_EOL) {
            // Non-windows (not using SMTP)
            $headers = str_replace("\r\n", PHP_EOL, $headers);
            $subject = str_replace("\r\n", PHP_EOL, $subject);
            $body = str_replace("\r\n", PHP_EOL, $body);
        } else {
            // Windows, using SMTP
            $headers = str_replace("\r\n.", "\r\n..", $headers);
            $subject = str_replace("\r\n.", "\r\n..", $subject);
            $body = str_replace("\r\n.", "\r\n..", $body);
        }

        if ($this->_invoker->mail($to, $subject, $body, $headers, $this->_formatExtraParams($this->_extraParams, $reversePath))) {
            if ($evt) {
                $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
                $evt->setFailedRecipients($failedRecipients);
                $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
            }
        } else {
            $failedRecipients = array_merge(
                $failedRecipients,
                array_keys((array) $message->getTo()),
                array_keys((array) $message->getCc()),
                array_keys((array) $message->getBcc())
                );

            if ($evt) {
                $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED);
                $evt->setFailedRecipients($failedRecipients);
                $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
            }

            $message->generateId();

            $count = 0;
        }

        return $count;
    }

    /**
     * Register a plugin.
     *
     * @param Swift_Events_EventListener $plugin
     */
    public function registerPlugin(Swift_Events_EventListener $plugin)
    {
        $this->_eventDispatcher->bindEventListener($plugin);
    }

    /** Throw a TransportException, first sending it to any listeners */
    protected function _throwException(Swift_TransportException $e)
    {
        if ($evt = $this->_eventDispatcher->createTransportExceptionEvent($this, $e)) {
            $this->_eventDispatcher->dispatchEvent($evt, 'exceptionThrown');
            if (!$evt->bubbleCancelled()) {
                throw $e;
            }
        } else {
            throw $e;
        }
    }

    /** Determine the best-use reverse path for this message */
    private function _getReversePath(Swift_Mime_Message $message)
    {
        $return = $message->getReturnPath();
        $sender = $message->getSender();
        $from = $message->getFrom();
        $path = null;
        if (!empty($return)) {
            $path = $return;
        } elseif (!empty($sender)) {
            $keys = array_keys($sender);
            $path = array_shift($keys);
        } elseif (!empty($from)) {
            $keys = array_keys($from);
            $path = array_shift($keys);
        }

        return $path;
    }

    /**
     * Fix CVE-2016-10074 by disallowing potentially unsafe shell characters.
     *
     * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.
     *
     * @param string $string The string to be validated
     *
     * @return bool
     */
    private function _isShellSafe($string)
    {
        // Future-proof
        if (escapeshellcmd($string) !== $string || !in_array(escapeshellarg($string), array("'$string'", "\"$string\""))) {
            return false;
        }

        $length = strlen($string);
        for ($i = 0; $i < $length; ++$i) {
            $c = $string[$i];
            // All other characters have a special meaning in at least one common shell, including = and +.
            // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
            // Note that this does permit non-Latin alphanumeric characters based on the current locale.
            if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
                return false;
            }
        }

        return true;
    }

    /**
     * Return php mail extra params to use for invoker->mail.
     *
     * @param $extraParams
     * @param $reversePath
     *
     * @return string|null
     */
    private function _formatExtraParams($extraParams, $reversePath)
    {
        if (false !== strpos($extraParams, '-f%s')) {
            if (empty($reversePath) || false === $this->_isShellSafe($reversePath)) {
                $extraParams = str_replace('-f%s', '', $extraParams);
            } else {
                $extraParams = sprintf($extraParams, $reversePath);
            }
        }

        return !empty($extraParams) ? $extraParams : null;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Pretends messages have been sent, but just ignores them.
 *
 * @author Fabien Potencier
 */
class Swift_Transport_NullTransport implements Swift_Transport
{
    /** The event dispatcher from the plugin API */
    private $_eventDispatcher;

    /**
     * Constructor.
     */
    public function __construct(Swift_Events_EventDispatcher $eventDispatcher)
    {
        $this->_eventDispatcher = $eventDispatcher;
    }

    /**
     * Tests if this Transport mechanism has started.
     *
     * @return bool
     */
    public function isStarted()
    {
        return true;
    }

    /**
     * Starts this Transport mechanism.
     */
    public function start()
    {
    }

    /**
     * Stops this Transport mechanism.
     */
    public function stop()
    {
    }

    /**
     * Sends the given message.
     *
     * @param Swift_Mime_Message $message
     * @param string[]           $failedRecipients An array of failures by-reference
     *
     * @return int The number of sent emails
     */
    public function send(Swift_Mime_Message $message, &$failedRecipients = null)
    {
        if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) {
            $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
            if ($evt->bubbleCancelled()) {
                return 0;
            }
        }

        if ($evt) {
            $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
            $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
        }

        $count = (
            count((array) $message->getTo())
            + count((array) $message->getCc())
            + count((array) $message->getBcc())
            );

        return $count;
    }

    /**
     * Register a plugin.
     *
     * @param Swift_Events_EventListener $plugin
     */
    public function registerPlugin(Swift_Events_EventListener $plugin)
    {
        $this->_eventDispatcher->bindEventListener($plugin);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * SendmailTransport for sending mail through a Sendmail/Postfix (etc..) binary.
 *
 * Supported modes are -bs and -t, with any additional flags desired.
 * It is advised to use -bs mode since error reporting with -t mode is not
 * possible.
 *
 * @author Chris Corbyn
 */
class Swift_Transport_SendmailTransport extends Swift_Transport_AbstractSmtpTransport
{
    /**
     * Connection buffer parameters.
     *
     * @var array
     */
    private $_params = array(
        'timeout' => 30,
        'blocking' => 1,
        'command' => '/usr/sbin/sendmail -bs',
        'type' => Swift_Transport_IoBuffer::TYPE_PROCESS,
        );

    /**
     * Create a new SendmailTransport with $buf for I/O.
     *
     * @param Swift_Transport_IoBuffer     $buf
     * @param Swift_Events_EventDispatcher $dispatcher
     */
    public function __construct(Swift_Transport_IoBuffer $buf, Swift_Events_EventDispatcher $dispatcher)
    {
        parent::__construct($buf, $dispatcher);
    }

    /**
     * Start the standalone SMTP session if running in -bs mode.
     */
    public function start()
    {
        if (false !== strpos($this->getCommand(), ' -bs')) {
            parent::start();
        }
    }

    /**
     * Set the command to invoke.
     *
     * If using -t mode you are strongly advised to include -oi or -i in the flags.
     * For example: /usr/sbin/sendmail -oi -t
     * Swift will append a -f<sender> flag if one is not present.
     *
     * The recommended mode is "-bs" since it is interactive and failure notifications
     * are hence possible.
     *
     * @param string $command
     *
     * @return Swift_Transport_SendmailTransport
     */
    public function setCommand($command)
    {
        $this->_params['command'] = $command;

        return $this;
    }

    /**
     * Get the sendmail command which will be invoked.
     *
     * @return string
     */
    public function getCommand()
    {
        return $this->_params['command'];
    }

    /**
     * Send the given Message.
     *
     * Recipient/sender data will be retrieved from the Message API.
     *
     * The return value is the number of recipients who were accepted for delivery.
     * NOTE: If using 'sendmail -t' you will not be aware of any failures until
     * they bounce (i.e. send() will always return 100% success).
     *
     * @param Swift_Mime_Message $message
     * @param string[]           $failedRecipients An array of failures by-reference
     *
     * @return int
     */
    public function send(Swift_Mime_Message $message, &$failedRecipients = null)
    {
        $failedRecipients = (array) $failedRecipients;
        $command = $this->getCommand();
        $buffer = $this->getBuffer();
        $count = 0;

        if (false !== strpos($command, ' -t')) {
            if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) {
                $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
                if ($evt->bubbleCancelled()) {
                    return 0;
                }
            }

            if (false === strpos($command, ' -f')) {
                $command .= ' -f'.escapeshellarg($this->_getReversePath($message));
            }

            $buffer->initialize(array_merge($this->_params, array('command' => $command)));

            if (false === strpos($command, ' -i') && false === strpos($command, ' -oi')) {
                $buffer->setWriteTranslations(array("\r\n" => "\n", "\n." => "\n.."));
            } else {
                $buffer->setWriteTranslations(array("\r\n" => "\n"));
            }

            $count = count((array) $message->getTo())
                + count((array) $message->getCc())
                + count((array) $message->getBcc())
                ;
            $message->toByteStream($buffer);
            $buffer->flushBuffers();
            $buffer->setWriteTranslations(array());
            $buffer->terminate();

            if ($evt) {
                $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
                $evt->setFailedRecipients($failedRecipients);
                $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
            }

            $message->generateId();
        } elseif (false !== strpos($command, ' -bs')) {
            $count = parent::send($message, $failedRecipients);
        } else {
            $this->_throwException(new Swift_TransportException(
                'Unsupported sendmail command flags ['.$command.']. '.
                'Must be one of "-bs" or "-t" but can include additional flags.'
                ));
        }

        return $count;
    }

    /** Get the params to initialize the buffer */
    protected function _getBufferParams()
    {
        return $this->_params;
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * This is the implementation class for {@link Swift_Transport_MailInvoker}.
 *
 * @author     Chris Corbyn
 */
class Swift_Transport_SimpleMailInvoker implements Swift_Transport_MailInvoker
{
    /**
     * Send mail via the mail() function.
     *
     * This method takes the same arguments as PHP mail().
     *
     * @param string $to
     * @param string $subject
     * @param string $body
     * @param string $headers
     * @param string $extraParams
     *
     * @return bool
     */
    public function mail($to, $subject, $body, $headers = null, $extraParams = null)
    {
        if (!ini_get('safe_mode')) {
            return @mail($to, $subject, $body, $headers, $extraParams);
        }

        return @mail($to, $subject, $body, $headers);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Wraps an IoBuffer to send/receive SMTP commands/responses.
 *
 * @author Chris Corbyn
 */
interface Swift_Transport_SmtpAgent
{
    /**
     * Get the IoBuffer where read/writes are occurring.
     *
     * @return Swift_Transport_IoBuffer
     */
    public function getBuffer();

    /**
     * Run a command against the buffer, expecting the given response codes.
     *
     * If no response codes are given, the response will not be validated.
     * If codes are given, an exception will be thrown on an invalid response.
     *
     * @param string   $command
     * @param int[]    $codes
     * @param string[] $failures An array of failures by-reference
     */
    public function executeCommand($command, $codes = array(), &$failures = null);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2009 Fabien Potencier <fabien.potencier@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Stores Messages in a queue.
 *
 * @author Fabien Potencier
 */
class Swift_Transport_SpoolTransport implements Swift_Transport
{
    /** The spool instance */
    private $_spool;

    /** The event dispatcher from the plugin API */
    private $_eventDispatcher;

    /**
     * Constructor.
     */
    public function __construct(Swift_Events_EventDispatcher $eventDispatcher, Swift_Spool $spool = null)
    {
        $this->_eventDispatcher = $eventDispatcher;
        $this->_spool = $spool;
    }

    /**
     * Sets the spool object.
     *
     * @param Swift_Spool $spool
     *
     * @return Swift_Transport_SpoolTransport
     */
    public function setSpool(Swift_Spool $spool)
    {
        $this->_spool = $spool;

        return $this;
    }

    /**
     * Get the spool object.
     *
     * @return Swift_Spool
     */
    public function getSpool()
    {
        return $this->_spool;
    }

    /**
     * Tests if this Transport mechanism has started.
     *
     * @return bool
     */
    public function isStarted()
    {
        return true;
    }

    /**
     * Starts this Transport mechanism.
     */
    public function start()
    {
    }

    /**
     * Stops this Transport mechanism.
     */
    public function stop()
    {
    }

    /**
     * Sends the given message.
     *
     * @param Swift_Mime_Message $message
     * @param string[]           $failedRecipients An array of failures by-reference
     *
     * @return int The number of sent e-mail's
     */
    public function send(Swift_Mime_Message $message, &$failedRecipients = null)
    {
        if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) {
            $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed');
            if ($evt->bubbleCancelled()) {
                return 0;
            }
        }

        $success = $this->_spool->queueMessage($message);

        if ($evt) {
            $evt->setResult($success ? Swift_Events_SendEvent::RESULT_SPOOLED : Swift_Events_SendEvent::RESULT_FAILED);
            $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed');
        }

        return 1;
    }

    /**
     * Register a plugin.
     *
     * @param Swift_Events_EventListener $plugin
     */
    public function registerPlugin(Swift_Events_EventListener $plugin)
    {
        $this->_eventDispatcher->bindEventListener($plugin);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A generic IoBuffer implementation supporting remote sockets and local processes.
 *
 * @author Chris Corbyn
 */
class Swift_Transport_StreamBuffer extends Swift_ByteStream_AbstractFilterableInputStream implements Swift_Transport_IoBuffer
{
    /** A primary socket */
    private $_stream;

    /** The input stream */
    private $_in;

    /** The output stream */
    private $_out;

    /** Buffer initialization parameters */
    private $_params = array();

    /** The ReplacementFilterFactory */
    private $_replacementFactory;

    /** Translations performed on data being streamed into the buffer */
    private $_translations = array();

    /**
     * Create a new StreamBuffer using $replacementFactory for transformations.
     *
     * @param Swift_ReplacementFilterFactory $replacementFactory
     */
    public function __construct(Swift_ReplacementFilterFactory $replacementFactory)
    {
        $this->_replacementFactory = $replacementFactory;
    }

    /**
     * Perform any initialization needed, using the given $params.
     *
     * Parameters will vary depending upon the type of IoBuffer used.
     *
     * @param array $params
     */
    public function initialize(array $params)
    {
        $this->_params = $params;
        switch ($params['type']) {
            case self::TYPE_PROCESS:
                $this->_establishProcessConnection();
                break;
            case self::TYPE_SOCKET:
            default:
                $this->_establishSocketConnection();
                break;
        }
    }

    /**
     * Set an individual param on the buffer (e.g. switching to SSL).
     *
     * @param string $param
     * @param mixed  $value
     */
    public function setParam($param, $value)
    {
        if (isset($this->_stream)) {
            switch ($param) {
                case 'timeout':
                    if ($this->_stream) {
                        stream_set_timeout($this->_stream, $value);
                    }
                    break;

                case 'blocking':
                    if ($this->_stream) {
                        stream_set_blocking($this->_stream, 1);
                    }

            }
        }
        $this->_params[$param] = $value;
    }

    public function startTLS()
    {
        return stream_socket_enable_crypto($this->_stream, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
    }

    /**
     * Perform any shutdown logic needed.
     */
    public function terminate()
    {
        if (isset($this->_stream)) {
            switch ($this->_params['type']) {
                case self::TYPE_PROCESS:
                    fclose($this->_in);
                    fclose($this->_out);
                    proc_close($this->_stream);
                    break;
                case self::TYPE_SOCKET:
                default:
                    fclose($this->_stream);
                    break;
            }
        }
        $this->_stream = null;
        $this->_out = null;
        $this->_in = null;
    }

    /**
     * Set an array of string replacements which should be made on data written
     * to the buffer.
     *
     * This could replace LF with CRLF for example.
     *
     * @param string[] $replacements
     */
    public function setWriteTranslations(array $replacements)
    {
        foreach ($this->_translations as $search => $replace) {
            if (!isset($replacements[$search])) {
                $this->removeFilter($search);
                unset($this->_translations[$search]);
            }
        }

        foreach ($replacements as $search => $replace) {
            if (!isset($this->_translations[$search])) {
                $this->addFilter(
                    $this->_replacementFactory->createFilter($search, $replace), $search
                    );
                $this->_translations[$search] = true;
            }
        }
    }

    /**
     * Get a line of output (including any CRLF).
     *
     * The $sequence number comes from any writes and may or may not be used
     * depending upon the implementation.
     *
     * @param int $sequence of last write to scan from
     *
     * @throws Swift_IoException
     *
     * @return string
     */
    public function readLine($sequence)
    {
        if (isset($this->_out) && !feof($this->_out)) {
            $line = fgets($this->_out);
            if (strlen($line) == 0) {
                $metas = stream_get_meta_data($this->_out);
                if ($metas['timed_out']) {
                    throw new Swift_IoException(
                        'Connection to '.
                            $this->_getReadConnectionDescription().
                        ' Timed Out'
                    );
                }
            }

            return $line;
        }
    }

    /**
     * Reads $length bytes from the stream into a string and moves the pointer
     * through the stream by $length.
     *
     * If less bytes exist than are requested the remaining bytes are given instead.
     * If no bytes are remaining at all, boolean false is returned.
     *
     * @param int $length
     *
     * @throws Swift_IoException
     *
     * @return string|bool
     */
    public function read($length)
    {
        if (isset($this->_out) && !feof($this->_out)) {
            $ret = fread($this->_out, $length);
            if (strlen($ret) == 0) {
                $metas = stream_get_meta_data($this->_out);
                if ($metas['timed_out']) {
                    throw new Swift_IoException(
                        'Connection to '.
                            $this->_getReadConnectionDescription().
                        ' Timed Out'
                    );
                }
            }

            return $ret;
        }
    }

    /** Not implemented */
    public function setReadPointer($byteOffset)
    {
    }

    /** Flush the stream contents */
    protected function _flush()
    {
        if (isset($this->_in)) {
            fflush($this->_in);
        }
    }

    /** Write this bytes to the stream */
    protected function _commit($bytes)
    {
        if (isset($this->_in)) {
            $bytesToWrite = strlen($bytes);
            $totalBytesWritten = 0;

            while ($totalBytesWritten < $bytesToWrite) {
                $bytesWritten = fwrite($this->_in, substr($bytes, $totalBytesWritten));
                if (false === $bytesWritten || 0 === $bytesWritten) {
                    break;
                }

                $totalBytesWritten += $bytesWritten;
            }

            if ($totalBytesWritten > 0) {
                return ++$this->_sequence;
            }
        }
    }

    /**
     * Establishes a connection to a remote server.
     */
    private function _establishSocketConnection()
    {
        $host = $this->_params['host'];
        if (!empty($this->_params['protocol'])) {
            $host = $this->_params['protocol'].'://'.$host;
        }
        $timeout = 15;
        if (!empty($this->_params['timeout'])) {
            $timeout = $this->_params['timeout'];
        }
        $options = array();
        if (!empty($this->_params['sourceIp'])) {
            $options['socket']['bindto'] = $this->_params['sourceIp'].':0';
        }
        if (isset($this->_params['stream_context_options'])) {
            $options = array_merge($options, $this->_params['stream_context_options']);
        }
        $streamContext = stream_context_create($options);
        $this->_stream = @stream_socket_client($host.':'.$this->_params['port'], $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $streamContext);
        if (false === $this->_stream) {
            throw new Swift_TransportException(
                'Connection could not be established with host '.$this->_params['host'].
                ' ['.$errstr.' #'.$errno.']'
                );
        }
        if (!empty($this->_params['blocking'])) {
            stream_set_blocking($this->_stream, 1);
        } else {
            stream_set_blocking($this->_stream, 0);
        }
        stream_set_timeout($this->_stream, $timeout);
        $this->_in = &$this->_stream;
        $this->_out = &$this->_stream;
    }

    /**
     * Opens a process for input/output.
     */
    private function _establishProcessConnection()
    {
        $command = $this->_params['command'];
        $descriptorSpec = array(
            0 => array('pipe', 'r'),
            1 => array('pipe', 'w'),
            2 => array('pipe', 'w'),
            );
        $this->_stream = proc_open($command, $descriptorSpec, $pipes);
        stream_set_blocking($pipes[2], 0);
        if ($err = stream_get_contents($pipes[2])) {
            throw new Swift_TransportException(
                'Process could not be started ['.$err.']'
                );
        }
        $this->_in = &$pipes[0];
        $this->_out = &$pipes[1];
    }

    private function _getReadConnectionDescription()
    {
        switch ($this->_params['type']) {
            case self::TYPE_PROCESS:
                return 'Process '.$this->_params['command'];
                break;

            case self::TYPE_SOCKET:
            default:
                $host = $this->_params['host'];
                if (!empty($this->_params['protocol'])) {
                    $host = $this->_params['protocol'].'://'.$host;
                }
                $host .= ':'.$this->_params['port'];

                return $host;
                break;
        }
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Sends Messages via an abstract Transport subsystem.
 *
 * @author Chris Corbyn
 */
interface Swift_Transport
{
    /**
     * Test if this Transport mechanism has started.
     *
     * @return bool
     */
    public function isStarted();

    /**
     * Start this Transport mechanism.
     */
    public function start();

    /**
     * Stop this Transport mechanism.
     */
    public function stop();

    /**
     * Send the given Message.
     *
     * Recipient/sender data will be retrieved from the Message API.
     * The return value is the number of recipients who were accepted for delivery.
     *
     * @param Swift_Mime_Message $message
     * @param string[]           $failedRecipients An array of failures by-reference
     *
     * @return int
     */
    public function send(Swift_Mime_Message $message, &$failedRecipients = null);

    /**
     * Register a plugin in the Transport.
     *
     * @param Swift_Events_EventListener $plugin
     */
    public function registerPlugin(Swift_Events_EventListener $plugin);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * TransportException thrown when an error occurs in the Transport subsystem.
 *
 * @author Chris Corbyn
 */
class Swift_TransportException extends Swift_IoException
{
    /**
     * Create a new TransportException with $message.
     *
     * @param string    $message
     * @param int       $code
     * @param Exception $previous
     */
    public function __construct($message, $code = 0, Exception $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Utility Class allowing users to simply check expressions again Swift Grammar.
 *
 * @author  Xavier De Cock <xdecock@gmail.com>
 */
class Swift_Validate
{
    /**
     * Grammar Object.
     *
     * @var Swift_Mime_Grammar
     */
    private static $grammar = null;

    /**
     * Checks if an e-mail address matches the current grammars.
     *
     * @param string $email
     *
     * @return bool
     */
    public static function email($email)
    {
        if (self::$grammar === null) {
            self::$grammar = Swift_DependencyContainer::getInstance()
                ->lookup('mime.grammar');
        }

        return (bool) preg_match(
                '/^'.self::$grammar->getDefinition('addr-spec').'$/D',
                $email
            );
    }
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * General utility class in Swift Mailer, not to be instantiated.
 *
 *
 * @author Chris Corbyn
 */
abstract class Swift
{
    /** Swift Mailer Version number generated during dist release process */
    const VERSION = '@SWIFT_VERSION_NUMBER@';

    public static $initialized = false;
    public static $inits = array();

    /**
     * Registers an initializer callable that will be called the first time
     * a SwiftMailer class is autoloaded.
     *
     * This enables you to tweak the default configuration in a lazy way.
     *
     * @param mixed $callable A valid PHP callable that will be called when autoloading the first Swift class
     */
    public static function init($callable)
    {
        self::$inits[] = $callable;
    }

    /**
     * Internal autoloader for spl_autoload_register().
     *
     * @param string $class
     */
    public static function autoload($class)
    {
        // Don't interfere with other autoloaders
        if (0 !== strpos($class, 'Swift_')) {
            return;
        }

        $path = __DIR__.'/'.str_replace('_', '/', $class).'.php';

        if (!file_exists($path)) {
            return;
        }

        require $path;

        if (self::$inits && !self::$initialized) {
            self::$initialized = true;
            foreach (self::$inits as $init) {
                call_user_func($init);
            }
        }
    }

    /**
     * Configure autoloading using Swift Mailer.
     *
     * This is designed to play nicely with other autoloaders.
     *
     * @param mixed $callable A valid PHP callable that will be called when autoloading the first Swift class
     */
    public static function registerAutoload($callable = null)
    {
        if (null !== $callable) {
            self::$inits[] = $callable;
        }
        spl_autoload_register(array('Swift', 'autoload'));
    }
}
<?php

Swift_DependencyContainer::getInstance()
    ->register('cache')
    ->asAliasOf('cache.array')

    ->register('tempdir')
    ->asValue('/tmp')

    ->register('cache.null')
    ->asSharedInstanceOf('Swift_KeyCache_NullKeyCache')

    ->register('cache.array')
    ->asSharedInstanceOf('Swift_KeyCache_ArrayKeyCache')
    ->withDependencies(array('cache.inputstream'))

    ->register('cache.disk')
    ->asSharedInstanceOf('Swift_KeyCache_DiskKeyCache')
    ->withDependencies(array('cache.inputstream', 'tempdir'))

    ->register('cache.inputstream')
    ->asNewInstanceOf('Swift_KeyCache_SimpleKeyCacheInputStream')
;
<?php

Swift_DependencyContainer::getInstance()
    ->register('message.message')
    ->asNewInstanceOf('Swift_Message')

    ->register('message.mimepart')
    ->asNewInstanceOf('Swift_MimePart')
;
<?php

require __DIR__.'/../mime_types.php';

Swift_DependencyContainer::getInstance()
    ->register('properties.charset')
    ->asValue('utf-8')

    ->register('mime.grammar')
    ->asSharedInstanceOf('Swift_Mime_Grammar')

    ->register('mime.message')
    ->asNewInstanceOf('Swift_Mime_SimpleMessage')
    ->withDependencies(array(
        'mime.headerset',
        'mime.qpcontentencoder',
        'cache',
        'mime.grammar',
        'properties.charset',
    ))

    ->register('mime.part')
    ->asNewInstanceOf('Swift_Mime_MimePart')
    ->withDependencies(array(
        'mime.headerset',
        'mime.qpcontentencoder',
        'cache',
        'mime.grammar',
        'properties.charset',
    ))

    ->register('mime.attachment')
    ->asNewInstanceOf('Swift_Mime_Attachment')
    ->withDependencies(array(
        'mime.headerset',
        'mime.base64contentencoder',
        'cache',
        'mime.grammar',
    ))
    ->addConstructorValue($swift_mime_types)

    ->register('mime.embeddedfile')
    ->asNewInstanceOf('Swift_Mime_EmbeddedFile')
    ->withDependencies(array(
        'mime.headerset',
        'mime.base64contentencoder',
        'cache',
        'mime.grammar',
    ))
    ->addConstructorValue($swift_mime_types)

    ->register('mime.headerfactory')
    ->asNewInstanceOf('Swift_Mime_SimpleHeaderFactory')
    ->withDependencies(array(
            'mime.qpheaderencoder',
            'mime.rfc2231encoder',
            'mime.grammar',
            'properties.charset',
        ))

    ->register('mime.headerset')
    ->asNewInstanceOf('Swift_Mime_SimpleHeaderSet')
    ->withDependencies(array('mime.headerfactory', 'properties.charset'))

    ->register('mime.qpheaderencoder')
    ->asNewInstanceOf('Swift_Mime_HeaderEncoder_QpHeaderEncoder')
    ->withDependencies(array('mime.charstream'))

    ->register('mime.base64headerencoder')
    ->asNewInstanceOf('Swift_Mime_HeaderEncoder_Base64HeaderEncoder')
    ->withDependencies(array('mime.charstream'))

    ->register('mime.charstream')
    ->asNewInstanceOf('Swift_CharacterStream_NgCharacterStream')
    ->withDependencies(array('mime.characterreaderfactory', 'properties.charset'))

    ->register('mime.bytecanonicalizer')
    ->asSharedInstanceOf('Swift_StreamFilters_ByteArrayReplacementFilter')
    ->addConstructorValue(array(array(0x0D, 0x0A), array(0x0D), array(0x0A)))
    ->addConstructorValue(array(array(0x0A), array(0x0A), array(0x0D, 0x0A)))

    ->register('mime.characterreaderfactory')
    ->asSharedInstanceOf('Swift_CharacterReaderFactory_SimpleCharacterReaderFactory')

    ->register('mime.safeqpcontentencoder')
    ->asNewInstanceOf('Swift_Mime_ContentEncoder_QpContentEncoder')
    ->withDependencies(array('mime.charstream', 'mime.bytecanonicalizer'))

    ->register('mime.rawcontentencoder')
    ->asNewInstanceOf('Swift_Mime_ContentEncoder_RawContentEncoder')

    ->register('mime.nativeqpcontentencoder')
    ->withDependencies(array('properties.charset'))
    ->asNewInstanceOf('Swift_Mime_ContentEncoder_NativeQpContentEncoder')

    ->register('mime.qpcontentencoderproxy')
    ->asNewInstanceOf('Swift_Mime_ContentEncoder_QpContentEncoderProxy')
    ->withDependencies(array('mime.safeqpcontentencoder', 'mime.nativeqpcontentencoder', 'properties.charset'))

    ->register('mime.7bitcontentencoder')
    ->asNewInstanceOf('Swift_Mime_ContentEncoder_PlainContentEncoder')
    ->addConstructorValue('7bit')
    ->addConstructorValue(true)

    ->register('mime.8bitcontentencoder')
    ->asNewInstanceOf('Swift_Mime_ContentEncoder_PlainContentEncoder')
    ->addConstructorValue('8bit')
    ->addConstructorValue(true)

    ->register('mime.base64contentencoder')
    ->asSharedInstanceOf('Swift_Mime_ContentEncoder_Base64ContentEncoder')

    ->register('mime.rfc2231encoder')
    ->asNewInstanceOf('Swift_Encoder_Rfc2231Encoder')
    ->withDependencies(array('mime.charstream'))

    // As of PHP 5.4.7, the quoted_printable_encode() function behaves correctly.
    // see https://github.com/php/php-src/commit/18bb426587d62f93c54c40bf8535eb8416603629
    ->register('mime.qpcontentencoder')
    ->asAliasOf(version_compare(phpversion(), '5.4.7', '>=') ? 'mime.qpcontentencoderproxy' : 'mime.safeqpcontentencoder')
;

unset($swift_mime_types);
<?php

Swift_DependencyContainer::getInstance()
    ->register('transport.smtp')
    ->asNewInstanceOf('Swift_Transport_EsmtpTransport')
    ->withDependencies(array(
        'transport.buffer',
        array('transport.authhandler'),
        'transport.eventdispatcher',
    ))

    ->register('transport.sendmail')
    ->asNewInstanceOf('Swift_Transport_SendmailTransport')
    ->withDependencies(array(
        'transport.buffer',
        'transport.eventdispatcher',
    ))

    ->register('transport.mail')
    ->asNewInstanceOf('Swift_Transport_MailTransport')
    ->withDependencies(array('transport.mailinvoker', 'transport.eventdispatcher'))

    ->register('transport.loadbalanced')
    ->asNewInstanceOf('Swift_Transport_LoadBalancedTransport')

    ->register('transport.failover')
    ->asNewInstanceOf('Swift_Transport_FailoverTransport')

    ->register('transport.spool')
    ->asNewInstanceOf('Swift_Transport_SpoolTransport')
    ->withDependencies(array('transport.eventdispatcher'))

    ->register('transport.null')
    ->asNewInstanceOf('Swift_Transport_NullTransport')
    ->withDependencies(array('transport.eventdispatcher'))

    ->register('transport.mailinvoker')
    ->asSharedInstanceOf('Swift_Transport_SimpleMailInvoker')

    ->register('transport.buffer')
    ->asNewInstanceOf('Swift_Transport_StreamBuffer')
    ->withDependencies(array('transport.replacementfactory'))

    ->register('transport.authhandler')
    ->asNewInstanceOf('Swift_Transport_Esmtp_AuthHandler')
    ->withDependencies(array(
        array(
            'transport.crammd5auth',
            'transport.loginauth',
            'transport.plainauth',
            'transport.ntlmauth',
            'transport.xoauth2auth',
        ),
    ))

    ->register('transport.crammd5auth')
    ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_CramMd5Authenticator')

    ->register('transport.loginauth')
    ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_LoginAuthenticator')

    ->register('transport.plainauth')
    ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_PlainAuthenticator')

    ->register('transport.xoauth2auth')
    ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_XOAuth2Authenticator')

    ->register('transport.ntlmauth')
    ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_NTLMAuthenticator')

    ->register('transport.eventdispatcher')
    ->asNewInstanceOf('Swift_Events_SimpleEventDispatcher')

    ->register('transport.replacementfactory')
    ->asSharedInstanceOf('Swift_StreamFilters_StringReplacementFilterFactory')
;
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * autogenerated using http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
 * and https://raw.github.com/minad/mimemagic/master/script/freedesktop.org.xml
 */

/*
 * List of MIME type automatically detected in Swift Mailer.
 */

// You may add or take away what you like (lowercase required)

$swift_mime_types = array(
    '3dml' => 'text/vnd.in3d.3dml',
    '3ds' => 'image/x-3ds',
    '3g2' => 'video/3gpp2',
    '3gp' => 'video/3gpp',
    '7z' => 'application/x-7z-compressed',
    'aab' => 'application/x-authorware-bin',
    'aac' => 'audio/x-aac',
    'aam' => 'application/x-authorware-map',
    'aas' => 'application/x-authorware-seg',
    'abw' => 'application/x-abiword',
    'ac' => 'application/pkix-attr-cert',
    'acc' => 'application/vnd.americandynamics.acc',
    'ace' => 'application/x-ace-compressed',
    'acu' => 'application/vnd.acucobol',
    'acutc' => 'application/vnd.acucorp',
    'adp' => 'audio/adpcm',
    'aep' => 'application/vnd.audiograph',
    'afm' => 'application/x-font-type1',
    'afp' => 'application/vnd.ibm.modcap',
    'ahead' => 'application/vnd.ahead.space',
    'ai' => 'application/postscript',
    'aif' => 'audio/x-aiff',
    'aifc' => 'audio/x-aiff',
    'aiff' => 'audio/x-aiff',
    'air' => 'application/vnd.adobe.air-application-installer-package+zip',
    'ait' => 'application/vnd.dvb.ait',
    'ami' => 'application/vnd.amiga.ami',
    'apk' => 'application/vnd.android.package-archive',
    'appcache' => 'text/cache-manifest',
    'apr' => 'application/vnd.lotus-approach',
    'aps' => 'application/postscript',
    'arc' => 'application/x-freearc',
    'asc' => 'application/pgp-signature',
    'asf' => 'video/x-ms-asf',
    'asm' => 'text/x-asm',
    'aso' => 'application/vnd.accpac.simply.aso',
    'asx' => 'video/x-ms-asf',
    'atc' => 'application/vnd.acucorp',
    'atom' => 'application/atom+xml',
    'atomcat' => 'application/atomcat+xml',
    'atomsvc' => 'application/atomsvc+xml',
    'atx' => 'application/vnd.antix.game-component',
    'au' => 'audio/basic',
    'avi' => 'video/x-msvideo',
    'aw' => 'application/applixware',
    'azf' => 'application/vnd.airzip.filesecure.azf',
    'azs' => 'application/vnd.airzip.filesecure.azs',
    'azw' => 'application/vnd.amazon.ebook',
    'bat' => 'application/x-msdownload',
    'bcpio' => 'application/x-bcpio',
    'bdf' => 'application/x-font-bdf',
    'bdm' => 'application/vnd.syncml.dm+wbxml',
    'bed' => 'application/vnd.realvnc.bed',
    'bh2' => 'application/vnd.fujitsu.oasysprs',
    'bin' => 'application/octet-stream',
    'blb' => 'application/x-blorb',
    'blorb' => 'application/x-blorb',
    'bmi' => 'application/vnd.bmi',
    'bmp' => 'image/bmp',
    'book' => 'application/vnd.framemaker',
    'box' => 'application/vnd.previewsystems.box',
    'boz' => 'application/x-bzip2',
    'bpk' => 'application/octet-stream',
    'btif' => 'image/prs.btif',
    'bz' => 'application/x-bzip',
    'bz2' => 'application/x-bzip2',
    'c' => 'text/x-c',
    'c11amc' => 'application/vnd.cluetrust.cartomobile-config',
    'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg',
    'c4d' => 'application/vnd.clonk.c4group',
    'c4f' => 'application/vnd.clonk.c4group',
    'c4g' => 'application/vnd.clonk.c4group',
    'c4p' => 'application/vnd.clonk.c4group',
    'c4u' => 'application/vnd.clonk.c4group',
    'cab' => 'application/vnd.ms-cab-compressed',
    'caf' => 'audio/x-caf',
    'cap' => 'application/vnd.tcpdump.pcap',
    'car' => 'application/vnd.curl.car',
    'cat' => 'application/vnd.ms-pki.seccat',
    'cb7' => 'application/x-cbr',
    'cba' => 'application/x-cbr',
    'cbr' => 'application/x-cbr',
    'cbt' => 'application/x-cbr',
    'cbz' => 'application/x-cbr',
    'cc' => 'text/x-c',
    'cct' => 'application/x-director',
    'ccxml' => 'application/ccxml+xml',
    'cdbcmsg' => 'application/vnd.contact.cmsg',
    'cdf' => 'application/x-netcdf',
    'cdkey' => 'application/vnd.mediastation.cdkey',
    'cdmia' => 'application/cdmi-capability',
    'cdmic' => 'application/cdmi-container',
    'cdmid' => 'application/cdmi-domain',
    'cdmio' => 'application/cdmi-object',
    'cdmiq' => 'application/cdmi-queue',
    'cdx' => 'chemical/x-cdx',
    'cdxml' => 'application/vnd.chemdraw+xml',
    'cdy' => 'application/vnd.cinderella',
    'cer' => 'application/pkix-cert',
    'cfs' => 'application/x-cfs-compressed',
    'cgm' => 'image/cgm',
    'chat' => 'application/x-chat',
    'chm' => 'application/vnd.ms-htmlhelp',
    'chrt' => 'application/vnd.kde.kchart',
    'cif' => 'chemical/x-cif',
    'cii' => 'application/vnd.anser-web-certificate-issue-initiation',
    'cil' => 'application/vnd.ms-artgalry',
    'cla' => 'application/vnd.claymore',
    'class' => 'application/java-vm',
    'clkk' => 'application/vnd.crick.clicker.keyboard',
    'clkp' => 'application/vnd.crick.clicker.palette',
    'clkt' => 'application/vnd.crick.clicker.template',
    'clkw' => 'application/vnd.crick.clicker.wordbank',
    'clkx' => 'application/vnd.crick.clicker',
    'clp' => 'application/x-msclip',
    'cmc' => 'application/vnd.cosmocaller',
    'cmdf' => 'chemical/x-cmdf',
    'cml' => 'chemical/x-cml',
    'cmp' => 'application/vnd.yellowriver-custom-menu',
    'cmx' => 'image/x-cmx',
    'cod' => 'application/vnd.rim.cod',
    'com' => 'application/x-msdownload',
    'conf' => 'text/plain',
    'cpio' => 'application/x-cpio',
    'cpp' => 'text/x-c',
    'cpt' => 'application/mac-compactpro',
    'crd' => 'application/x-mscardfile',
    'crl' => 'application/pkix-crl',
    'crt' => 'application/x-x509-ca-cert',
    'csh' => 'application/x-csh',
    'csml' => 'chemical/x-csml',
    'csp' => 'application/vnd.commonspace',
    'css' => 'text/css',
    'cst' => 'application/x-director',
    'csv' => 'text/csv',
    'cu' => 'application/cu-seeme',
    'curl' => 'text/vnd.curl',
    'cww' => 'application/prs.cww',
    'cxt' => 'application/x-director',
    'cxx' => 'text/x-c',
    'dae' => 'model/vnd.collada+xml',
    'daf' => 'application/vnd.mobius.daf',
    'dart' => 'application/vnd.dart',
    'dataless' => 'application/vnd.fdsn.seed',
    'davmount' => 'application/davmount+xml',
    'dbk' => 'application/docbook+xml',
    'dcr' => 'application/x-director',
    'dcurl' => 'text/vnd.curl.dcurl',
    'dd2' => 'application/vnd.oma.dd2+xml',
    'ddd' => 'application/vnd.fujixerox.ddd',
    'deb' => 'application/x-debian-package',
    'def' => 'text/plain',
    'deploy' => 'application/octet-stream',
    'der' => 'application/x-x509-ca-cert',
    'dfac' => 'application/vnd.dreamfactory',
    'dgc' => 'application/x-dgc-compressed',
    'dic' => 'text/x-c',
    'dir' => 'application/x-director',
    'dis' => 'application/vnd.mobius.dis',
    'dist' => 'application/octet-stream',
    'distz' => 'application/octet-stream',
    'djv' => 'image/vnd.djvu',
    'djvu' => 'image/vnd.djvu',
    'dll' => 'application/x-msdownload',
    'dmg' => 'application/x-apple-diskimage',
    'dmp' => 'application/vnd.tcpdump.pcap',
    'dms' => 'application/octet-stream',
    'dna' => 'application/vnd.dna',
    'doc' => 'application/msword',
    'docm' => 'application/vnd.ms-word.document.macroenabled.12',
    'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'dot' => 'application/msword',
    'dotm' => 'application/vnd.ms-word.template.macroenabled.12',
    'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
    'dp' => 'application/vnd.osgi.dp',
    'dpg' => 'application/vnd.dpgraph',
    'dra' => 'audio/vnd.dra',
    'dsc' => 'text/prs.lines.tag',
    'dssc' => 'application/dssc+der',
    'dtb' => 'application/x-dtbook+xml',
    'dtd' => 'application/xml-dtd',
    'dts' => 'audio/vnd.dts',
    'dtshd' => 'audio/vnd.dts.hd',
    'dump' => 'application/octet-stream',
    'dvb' => 'video/vnd.dvb.file',
    'dvi' => 'application/x-dvi',
    'dwf' => 'model/vnd.dwf',
    'dwg' => 'image/vnd.dwg',
    'dxf' => 'image/vnd.dxf',
    'dxp' => 'application/vnd.spotfire.dxp',
    'dxr' => 'application/x-director',
    'ecelp4800' => 'audio/vnd.nuera.ecelp4800',
    'ecelp7470' => 'audio/vnd.nuera.ecelp7470',
    'ecelp9600' => 'audio/vnd.nuera.ecelp9600',
    'ecma' => 'application/ecmascript',
    'edm' => 'application/vnd.novadigm.edm',
    'edx' => 'application/vnd.novadigm.edx',
    'efif' => 'application/vnd.picsel',
    'ei6' => 'application/vnd.pg.osasli',
    'elc' => 'application/octet-stream',
    'emf' => 'application/x-msmetafile',
    'eml' => 'message/rfc822',
    'emma' => 'application/emma+xml',
    'emz' => 'application/x-msmetafile',
    'eol' => 'audio/vnd.digital-winds',
    'eot' => 'application/vnd.ms-fontobject',
    'eps' => 'application/postscript',
    'epub' => 'application/epub+zip',
    'es3' => 'application/vnd.eszigno3+xml',
    'esa' => 'application/vnd.osgi.subsystem',
    'esf' => 'application/vnd.epson.esf',
    'et3' => 'application/vnd.eszigno3+xml',
    'etx' => 'text/x-setext',
    'eva' => 'application/x-eva',
    'evy' => 'application/x-envoy',
    'exe' => 'application/x-msdownload',
    'exi' => 'application/exi',
    'ext' => 'application/vnd.novadigm.ext',
    'ez' => 'application/andrew-inset',
    'ez2' => 'application/vnd.ezpix-album',
    'ez3' => 'application/vnd.ezpix-package',
    'f' => 'text/x-fortran',
    'f4v' => 'video/x-f4v',
    'f77' => 'text/x-fortran',
    'f90' => 'text/x-fortran',
    'fbs' => 'image/vnd.fastbidsheet',
    'fcdt' => 'application/vnd.adobe.formscentral.fcdt',
    'fcs' => 'application/vnd.isac.fcs',
    'fdf' => 'application/vnd.fdf',
    'fe_launch' => 'application/vnd.denovo.fcselayout-link',
    'fg5' => 'application/vnd.fujitsu.oasysgp',
    'fgd' => 'application/x-director',
    'fh' => 'image/x-freehand',
    'fh4' => 'image/x-freehand',
    'fh5' => 'image/x-freehand',
    'fh7' => 'image/x-freehand',
    'fhc' => 'image/x-freehand',
    'fig' => 'application/x-xfig',
    'flac' => 'audio/x-flac',
    'fli' => 'video/x-fli',
    'flo' => 'application/vnd.micrografx.flo',
    'flv' => 'video/x-flv',
    'flw' => 'application/vnd.kde.kivio',
    'flx' => 'text/vnd.fmi.flexstor',
    'fly' => 'text/vnd.fly',
    'fm' => 'application/vnd.framemaker',
    'fnc' => 'application/vnd.frogans.fnc',
    'for' => 'text/x-fortran',
    'fpx' => 'image/vnd.fpx',
    'frame' => 'application/vnd.framemaker',
    'fsc' => 'application/vnd.fsc.weblaunch',
    'fst' => 'image/vnd.fst',
    'ftc' => 'application/vnd.fluxtime.clip',
    'fti' => 'application/vnd.anser-web-funds-transfer-initiation',
    'fvt' => 'video/vnd.fvt',
    'fxp' => 'application/vnd.adobe.fxp',
    'fxpl' => 'application/vnd.adobe.fxp',
    'fzs' => 'application/vnd.fuzzysheet',
    'g2w' => 'application/vnd.geoplan',
    'g3' => 'image/g3fax',
    'g3w' => 'application/vnd.geospace',
    'gac' => 'application/vnd.groove-account',
    'gam' => 'application/x-tads',
    'gbr' => 'application/rpki-ghostbusters',
    'gca' => 'application/x-gca-compressed',
    'gdl' => 'model/vnd.gdl',
    'geo' => 'application/vnd.dynageo',
    'gex' => 'application/vnd.geometry-explorer',
    'ggb' => 'application/vnd.geogebra.file',
    'ggt' => 'application/vnd.geogebra.tool',
    'ghf' => 'application/vnd.groove-help',
    'gif' => 'image/gif',
    'gim' => 'application/vnd.groove-identity-message',
    'gml' => 'application/gml+xml',
    'gmx' => 'application/vnd.gmx',
    'gnumeric' => 'application/x-gnumeric',
    'gph' => 'application/vnd.flographit',
    'gpx' => 'application/gpx+xml',
    'gqf' => 'application/vnd.grafeq',
    'gqs' => 'application/vnd.grafeq',
    'gram' => 'application/srgs',
    'gramps' => 'application/x-gramps-xml',
    'gre' => 'application/vnd.geometry-explorer',
    'grv' => 'application/vnd.groove-injector',
    'grxml' => 'application/srgs+xml',
    'gsf' => 'application/x-font-ghostscript',
    'gtar' => 'application/x-gtar',
    'gtm' => 'application/vnd.groove-tool-message',
    'gtw' => 'model/vnd.gtw',
    'gv' => 'text/vnd.graphviz',
    'gxf' => 'application/gxf',
    'gxt' => 'application/vnd.geonext',
    'gz' => 'application/x-gzip',
    'h' => 'text/x-c',
    'h261' => 'video/h261',
    'h263' => 'video/h263',
    'h264' => 'video/h264',
    'hal' => 'application/vnd.hal+xml',
    'hbci' => 'application/vnd.hbci',
    'hdf' => 'application/x-hdf',
    'hh' => 'text/x-c',
    'hlp' => 'application/winhlp',
    'hpgl' => 'application/vnd.hp-hpgl',
    'hpid' => 'application/vnd.hp-hpid',
    'hps' => 'application/vnd.hp-hps',
    'hqx' => 'application/mac-binhex40',
    'htke' => 'application/vnd.kenameaapp',
    'htm' => 'text/html',
    'html' => 'text/html',
    'hvd' => 'application/vnd.yamaha.hv-dic',
    'hvp' => 'application/vnd.yamaha.hv-voice',
    'hvs' => 'application/vnd.yamaha.hv-script',
    'i2g' => 'application/vnd.intergeo',
    'icc' => 'application/vnd.iccprofile',
    'ice' => 'x-conference/x-cooltalk',
    'icm' => 'application/vnd.iccprofile',
    'ico' => 'image/x-icon',
    'ics' => 'text/calendar',
    'ief' => 'image/ief',
    'ifb' => 'text/calendar',
    'ifm' => 'application/vnd.shana.informed.formdata',
    'iges' => 'model/iges',
    'igl' => 'application/vnd.igloader',
    'igm' => 'application/vnd.insors.igm',
    'igs' => 'model/iges',
    'igx' => 'application/vnd.micrografx.igx',
    'iif' => 'application/vnd.shana.informed.interchange',
    'imp' => 'application/vnd.accpac.simply.imp',
    'ims' => 'application/vnd.ms-ims',
    'in' => 'text/plain',
    'ink' => 'application/inkml+xml',
    'inkml' => 'application/inkml+xml',
    'install' => 'application/x-install-instructions',
    'iota' => 'application/vnd.astraea-software.iota',
    'ipfix' => 'application/ipfix',
    'ipk' => 'application/vnd.shana.informed.package',
    'irm' => 'application/vnd.ibm.rights-management',
    'irp' => 'application/vnd.irepository.package+xml',
    'iso' => 'application/x-iso9660-image',
    'itp' => 'application/vnd.shana.informed.formtemplate',
    'ivp' => 'application/vnd.immervision-ivp',
    'ivu' => 'application/vnd.immervision-ivu',
    'jad' => 'text/vnd.sun.j2me.app-descriptor',
    'jam' => 'application/vnd.jam',
    'jar' => 'application/java-archive',
    'java' => 'text/x-java-source',
    'jisp' => 'application/vnd.jisp',
    'jlt' => 'application/vnd.hp-jlyt',
    'jnlp' => 'application/x-java-jnlp-file',
    'joda' => 'application/vnd.joost.joda-archive',
    'jpe' => 'image/jpeg',
    'jpeg' => 'image/jpeg',
    'jpg' => 'image/jpeg',
    'jpgm' => 'video/jpm',
    'jpgv' => 'video/jpeg',
    'jpm' => 'video/jpm',
    'js' => 'application/javascript',
    'json' => 'application/json',
    'jsonml' => 'application/jsonml+json',
    'kar' => 'audio/midi',
    'karbon' => 'application/vnd.kde.karbon',
    'kfo' => 'application/vnd.kde.kformula',
    'kia' => 'application/vnd.kidspiration',
    'kml' => 'application/vnd.google-earth.kml+xml',
    'kmz' => 'application/vnd.google-earth.kmz',
    'kne' => 'application/vnd.kinar',
    'knp' => 'application/vnd.kinar',
    'kon' => 'application/vnd.kde.kontour',
    'kpr' => 'application/vnd.kde.kpresenter',
    'kpt' => 'application/vnd.kde.kpresenter',
    'kpxx' => 'application/vnd.ds-keypoint',
    'ksp' => 'application/vnd.kde.kspread',
    'ktr' => 'application/vnd.kahootz',
    'ktx' => 'image/ktx',
    'ktz' => 'application/vnd.kahootz',
    'kwd' => 'application/vnd.kde.kword',
    'kwt' => 'application/vnd.kde.kword',
    'lasxml' => 'application/vnd.las.las+xml',
    'latex' => 'application/x-latex',
    'lbd' => 'application/vnd.llamagraphics.life-balance.desktop',
    'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml',
    'les' => 'application/vnd.hhe.lesson-player',
    'lha' => 'application/x-lzh-compressed',
    'link66' => 'application/vnd.route66.link66+xml',
    'list' => 'text/plain',
    'list3820' => 'application/vnd.ibm.modcap',
    'listafp' => 'application/vnd.ibm.modcap',
    'lnk' => 'application/x-ms-shortcut',
    'log' => 'text/plain',
    'lostxml' => 'application/lost+xml',
    'lrf' => 'application/octet-stream',
    'lrm' => 'application/vnd.ms-lrm',
    'ltf' => 'application/vnd.frogans.ltf',
    'lvp' => 'audio/vnd.lucent.voice',
    'lwp' => 'application/vnd.lotus-wordpro',
    'lzh' => 'application/x-lzh-compressed',
    'm13' => 'application/x-msmediaview',
    'm14' => 'application/x-msmediaview',
    'm1v' => 'video/mpeg',
    'm21' => 'application/mp21',
    'm2a' => 'audio/mpeg',
    'm2v' => 'video/mpeg',
    'm3a' => 'audio/mpeg',
    'm3u' => 'audio/x-mpegurl',
    'm3u8' => 'application/vnd.apple.mpegurl',
    'm4a' => 'audio/mp4',
    'm4u' => 'video/vnd.mpegurl',
    'm4v' => 'video/x-m4v',
    'ma' => 'application/mathematica',
    'mads' => 'application/mads+xml',
    'mag' => 'application/vnd.ecowin.chart',
    'maker' => 'application/vnd.framemaker',
    'man' => 'text/troff',
    'mar' => 'application/octet-stream',
    'mathml' => 'application/mathml+xml',
    'mb' => 'application/mathematica',
    'mbk' => 'application/vnd.mobius.mbk',
    'mbox' => 'application/mbox',
    'mc1' => 'application/vnd.medcalcdata',
    'mcd' => 'application/vnd.mcd',
    'mcurl' => 'text/vnd.curl.mcurl',
    'mdb' => 'application/x-msaccess',
    'mdi' => 'image/vnd.ms-modi',
    'me' => 'text/troff',
    'mesh' => 'model/mesh',
    'meta4' => 'application/metalink4+xml',
    'metalink' => 'application/metalink+xml',
    'mets' => 'application/mets+xml',
    'mfm' => 'application/vnd.mfmp',
    'mft' => 'application/rpki-manifest',
    'mgp' => 'application/vnd.osgeo.mapguide.package',
    'mgz' => 'application/vnd.proteus.magazine',
    'mid' => 'audio/midi',
    'midi' => 'audio/midi',
    'mie' => 'application/x-mie',
    'mif' => 'application/vnd.mif',
    'mime' => 'message/rfc822',
    'mj2' => 'video/mj2',
    'mjp2' => 'video/mj2',
    'mk3d' => 'video/x-matroska',
    'mka' => 'audio/x-matroska',
    'mks' => 'video/x-matroska',
    'mkv' => 'video/x-matroska',
    'mlp' => 'application/vnd.dolby.mlp',
    'mmd' => 'application/vnd.chipnuts.karaoke-mmd',
    'mmf' => 'application/vnd.smaf',
    'mmr' => 'image/vnd.fujixerox.edmics-mmr',
    'mng' => 'video/x-mng',
    'mny' => 'application/x-msmoney',
    'mobi' => 'application/x-mobipocket-ebook',
    'mods' => 'application/mods+xml',
    'mov' => 'video/quicktime',
    'movie' => 'video/x-sgi-movie',
    'mp2' => 'audio/mpeg',
    'mp21' => 'application/mp21',
    'mp2a' => 'audio/mpeg',
    'mp3' => 'audio/mpeg',
    'mp4' => 'video/mp4',
    'mp4a' => 'audio/mp4',
    'mp4s' => 'application/mp4',
    'mp4v' => 'video/mp4',
    'mpc' => 'application/vnd.mophun.certificate',
    'mpe' => 'video/mpeg',
    'mpeg' => 'video/mpeg',
    'mpg' => 'video/mpeg',
    'mpg4' => 'video/mp4',
    'mpga' => 'audio/mpeg',
    'mpkg' => 'application/vnd.apple.installer+xml',
    'mpm' => 'application/vnd.blueice.multipass',
    'mpn' => 'application/vnd.mophun.application',
    'mpp' => 'application/vnd.ms-project',
    'mpt' => 'application/vnd.ms-project',
    'mpy' => 'application/vnd.ibm.minipay',
    'mqy' => 'application/vnd.mobius.mqy',
    'mrc' => 'application/marc',
    'mrcx' => 'application/marcxml+xml',
    'ms' => 'text/troff',
    'mscml' => 'application/mediaservercontrol+xml',
    'mseed' => 'application/vnd.fdsn.mseed',
    'mseq' => 'application/vnd.mseq',
    'msf' => 'application/vnd.epson.msf',
    'msh' => 'model/mesh',
    'msi' => 'application/x-msdownload',
    'msl' => 'application/vnd.mobius.msl',
    'msty' => 'application/vnd.muvee.style',
    'mts' => 'model/vnd.mts',
    'mus' => 'application/vnd.musician',
    'musicxml' => 'application/vnd.recordare.musicxml+xml',
    'mvb' => 'application/x-msmediaview',
    'mwf' => 'application/vnd.mfer',
    'mxf' => 'application/mxf',
    'mxl' => 'application/vnd.recordare.musicxml',
    'mxml' => 'application/xv+xml',
    'mxs' => 'application/vnd.triscape.mxs',
    'mxu' => 'video/vnd.mpegurl',
    'n-gage' => 'application/vnd.nokia.n-gage.symbian.install',
    'n3' => 'text/n3',
    'nb' => 'application/mathematica',
    'nbp' => 'application/vnd.wolfram.player',
    'nc' => 'application/x-netcdf',
    'ncx' => 'application/x-dtbncx+xml',
    'nfo' => 'text/x-nfo',
    'ngdat' => 'application/vnd.nokia.n-gage.data',
    'nitf' => 'application/vnd.nitf',
    'nlu' => 'application/vnd.neurolanguage.nlu',
    'nml' => 'application/vnd.enliven',
    'nnd' => 'application/vnd.noblenet-directory',
    'nns' => 'application/vnd.noblenet-sealer',
    'nnw' => 'application/vnd.noblenet-web',
    'npx' => 'image/vnd.net-fpx',
    'nsc' => 'application/x-conference',
    'nsf' => 'application/vnd.lotus-notes',
    'ntf' => 'application/vnd.nitf',
    'nzb' => 'application/x-nzb',
    'oa2' => 'application/vnd.fujitsu.oasys2',
    'oa3' => 'application/vnd.fujitsu.oasys3',
    'oas' => 'application/vnd.fujitsu.oasys',
    'obd' => 'application/x-msbinder',
    'obj' => 'application/x-tgif',
    'oda' => 'application/oda',
    'odb' => 'application/vnd.oasis.opendocument.database',
    'odc' => 'application/vnd.oasis.opendocument.chart',
    'odf' => 'application/vnd.oasis.opendocument.formula',
    'odft' => 'application/vnd.oasis.opendocument.formula-template',
    'odg' => 'application/vnd.oasis.opendocument.graphics',
    'odi' => 'application/vnd.oasis.opendocument.image',
    'odm' => 'application/vnd.oasis.opendocument.text-master',
    'odp' => 'application/vnd.oasis.opendocument.presentation',
    'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
    'odt' => 'application/vnd.oasis.opendocument.text',
    'oga' => 'audio/ogg',
    'ogg' => 'audio/ogg',
    'ogv' => 'video/ogg',
    'ogx' => 'application/ogg',
    'omdoc' => 'application/omdoc+xml',
    'onepkg' => 'application/onenote',
    'onetmp' => 'application/onenote',
    'onetoc' => 'application/onenote',
    'onetoc2' => 'application/onenote',
    'opf' => 'application/oebps-package+xml',
    'opml' => 'text/x-opml',
    'oprc' => 'application/vnd.palm',
    'org' => 'application/vnd.lotus-organizer',
    'osf' => 'application/vnd.yamaha.openscoreformat',
    'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml',
    'otc' => 'application/vnd.oasis.opendocument.chart-template',
    'otf' => 'application/x-font-otf',
    'otg' => 'application/vnd.oasis.opendocument.graphics-template',
    'oth' => 'application/vnd.oasis.opendocument.text-web',
    'oti' => 'application/vnd.oasis.opendocument.image-template',
    'otp' => 'application/vnd.oasis.opendocument.presentation-template',
    'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
    'ott' => 'application/vnd.oasis.opendocument.text-template',
    'oxps' => 'application/oxps',
    'oxt' => 'application/vnd.openofficeorg.extension',
    'p' => 'text/x-pascal',
    'p10' => 'application/pkcs10',
    'p12' => 'application/x-pkcs12',
    'p7b' => 'application/x-pkcs7-certificates',
    'p7c' => 'application/pkcs7-mime',
    'p7m' => 'application/pkcs7-mime',
    'p7r' => 'application/x-pkcs7-certreqresp',
    'p7s' => 'application/pkcs7-signature',
    'p8' => 'application/pkcs8',
    'pas' => 'text/x-pascal',
    'paw' => 'application/vnd.pawaafile',
    'pbd' => 'application/vnd.powerbuilder6',
    'pbm' => 'image/x-portable-bitmap',
    'pcap' => 'application/vnd.tcpdump.pcap',
    'pcf' => 'application/x-font-pcf',
    'pcl' => 'application/vnd.hp-pcl',
    'pclxl' => 'application/vnd.hp-pclxl',
    'pct' => 'image/x-pict',
    'pcurl' => 'application/vnd.curl.pcurl',
    'pcx' => 'image/x-pcx',
    'pdb' => 'application/vnd.palm',
    'pdf' => 'application/pdf',
    'pfa' => 'application/x-font-type1',
    'pfb' => 'application/x-font-type1',
    'pfm' => 'application/x-font-type1',
    'pfr' => 'application/font-tdpfr',
    'pfx' => 'application/x-pkcs12',
    'pgm' => 'image/x-portable-graymap',
    'pgn' => 'application/x-chess-pgn',
    'pgp' => 'application/pgp-encrypted',
    'php' => 'application/x-php',
    'php3' => 'application/x-php',
    'php4' => 'application/x-php',
    'php5' => 'application/x-php',
    'pic' => 'image/x-pict',
    'pkg' => 'application/octet-stream',
    'pki' => 'application/pkixcmp',
    'pkipath' => 'application/pkix-pkipath',
    'plb' => 'application/vnd.3gpp.pic-bw-large',
    'plc' => 'application/vnd.mobius.plc',
    'plf' => 'application/vnd.pocketlearn',
    'pls' => 'application/pls+xml',
    'pml' => 'application/vnd.ctc-posml',
    'png' => 'image/png',
    'pnm' => 'image/x-portable-anymap',
    'portpkg' => 'application/vnd.macports.portpkg',
    'pot' => 'application/vnd.ms-powerpoint',
    'potm' => 'application/vnd.ms-powerpoint.template.macroenabled.12',
    'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
    'ppam' => 'application/vnd.ms-powerpoint.addin.macroenabled.12',
    'ppd' => 'application/vnd.cups-ppd',
    'ppm' => 'image/x-portable-pixmap',
    'pps' => 'application/vnd.ms-powerpoint',
    'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroenabled.12',
    'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
    'ppt' => 'application/vnd.ms-powerpoint',
    'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12',
    'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
    'pqa' => 'application/vnd.palm',
    'prc' => 'application/x-mobipocket-ebook',
    'pre' => 'application/vnd.lotus-freelance',
    'prf' => 'application/pics-rules',
    'ps' => 'application/postscript',
    'psb' => 'application/vnd.3gpp.pic-bw-small',
    'psd' => 'image/vnd.adobe.photoshop',
    'psf' => 'application/x-font-linux-psf',
    'pskcxml' => 'application/pskc+xml',
    'ptid' => 'application/vnd.pvi.ptid1',
    'pub' => 'application/x-mspublisher',
    'pvb' => 'application/vnd.3gpp.pic-bw-var',
    'pwn' => 'application/vnd.3m.post-it-notes',
    'pya' => 'audio/vnd.ms-playready.media.pya',
    'pyv' => 'video/vnd.ms-playready.media.pyv',
    'qam' => 'application/vnd.epson.quickanime',
    'qbo' => 'application/vnd.intu.qbo',
    'qfx' => 'application/vnd.intu.qfx',
    'qps' => 'application/vnd.publishare-delta-tree',
    'qt' => 'video/quicktime',
    'qwd' => 'application/vnd.quark.quarkxpress',
    'qwt' => 'application/vnd.quark.quarkxpress',
    'qxb' => 'application/vnd.quark.quarkxpress',
    'qxd' => 'application/vnd.quark.quarkxpress',
    'qxl' => 'application/vnd.quark.quarkxpress',
    'qxt' => 'application/vnd.quark.quarkxpress',
    'ra' => 'audio/x-pn-realaudio',
    'ram' => 'audio/x-pn-realaudio',
    'rar' => 'application/x-rar-compressed',
    'ras' => 'image/x-cmu-raster',
    'rcprofile' => 'application/vnd.ipunplugged.rcprofile',
    'rdf' => 'application/rdf+xml',
    'rdz' => 'application/vnd.data-vision.rdz',
    'rep' => 'application/vnd.businessobjects',
    'res' => 'application/x-dtbresource+xml',
    'rgb' => 'image/x-rgb',
    'rif' => 'application/reginfo+xml',
    'rip' => 'audio/vnd.rip',
    'ris' => 'application/x-research-info-systems',
    'rl' => 'application/resource-lists+xml',
    'rlc' => 'image/vnd.fujixerox.edmics-rlc',
    'rld' => 'application/resource-lists-diff+xml',
    'rm' => 'application/vnd.rn-realmedia',
    'rmi' => 'audio/midi',
    'rmp' => 'audio/x-pn-realaudio-plugin',
    'rms' => 'application/vnd.jcp.javame.midlet-rms',
    'rmvb' => 'application/vnd.rn-realmedia-vbr',
    'rnc' => 'application/relax-ng-compact-syntax',
    'roa' => 'application/rpki-roa',
    'roff' => 'text/troff',
    'rp9' => 'application/vnd.cloanto.rp9',
    'rpss' => 'application/vnd.nokia.radio-presets',
    'rpst' => 'application/vnd.nokia.radio-preset',
    'rq' => 'application/sparql-query',
    'rs' => 'application/rls-services+xml',
    'rsd' => 'application/rsd+xml',
    'rss' => 'application/rss+xml',
    'rtf' => 'application/rtf',
    'rtx' => 'text/richtext',
    's' => 'text/x-asm',
    's3m' => 'audio/s3m',
    'saf' => 'application/vnd.yamaha.smaf-audio',
    'sbml' => 'application/sbml+xml',
    'sc' => 'application/vnd.ibm.secure-container',
    'scd' => 'application/x-msschedule',
    'scm' => 'application/vnd.lotus-screencam',
    'scq' => 'application/scvp-cv-request',
    'scs' => 'application/scvp-cv-response',
    'scurl' => 'text/vnd.curl.scurl',
    'sda' => 'application/vnd.stardivision.draw',
    'sdc' => 'application/vnd.stardivision.calc',
    'sdd' => 'application/vnd.stardivision.impress',
    'sdkd' => 'application/vnd.solent.sdkm+xml',
    'sdkm' => 'application/vnd.solent.sdkm+xml',
    'sdp' => 'application/sdp',
    'sdw' => 'application/vnd.stardivision.writer',
    'see' => 'application/vnd.seemail',
    'seed' => 'application/vnd.fdsn.seed',
    'sema' => 'application/vnd.sema',
    'semd' => 'application/vnd.semd',
    'semf' => 'application/vnd.semf',
    'ser' => 'application/java-serialized-object',
    'setpay' => 'application/set-payment-initiation',
    'setreg' => 'application/set-registration-initiation',
    'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data',
    'sfs' => 'application/vnd.spotfire.sfs',
    'sfv' => 'text/x-sfv',
    'sgi' => 'image/sgi',
    'sgl' => 'application/vnd.stardivision.writer-global',
    'sgm' => 'text/sgml',
    'sgml' => 'text/sgml',
    'sh' => 'application/x-sh',
    'shar' => 'application/x-shar',
    'shf' => 'application/shf+xml',
    'sid' => 'image/x-mrsid-image',
    'sig' => 'application/pgp-signature',
    'sil' => 'audio/silk',
    'silo' => 'model/mesh',
    'sis' => 'application/vnd.symbian.install',
    'sisx' => 'application/vnd.symbian.install',
    'sit' => 'application/x-stuffit',
    'sitx' => 'application/x-stuffitx',
    'skd' => 'application/vnd.koan',
    'skm' => 'application/vnd.koan',
    'skp' => 'application/vnd.koan',
    'skt' => 'application/vnd.koan',
    'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12',
    'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
    'slt' => 'application/vnd.epson.salt',
    'sm' => 'application/vnd.stepmania.stepchart',
    'smf' => 'application/vnd.stardivision.math',
    'smi' => 'application/smil+xml',
    'smil' => 'application/smil+xml',
    'smv' => 'video/x-smv',
    'smzip' => 'application/vnd.stepmania.package',
    'snd' => 'audio/basic',
    'snf' => 'application/x-font-snf',
    'so' => 'application/octet-stream',
    'spc' => 'application/x-pkcs7-certificates',
    'spf' => 'application/vnd.yamaha.smaf-phrase',
    'spl' => 'application/x-futuresplash',
    'spot' => 'text/vnd.in3d.spot',
    'spp' => 'application/scvp-vp-response',
    'spq' => 'application/scvp-vp-request',
    'spx' => 'audio/ogg',
    'sql' => 'application/x-sql',
    'src' => 'application/x-wais-source',
    'srt' => 'application/x-subrip',
    'sru' => 'application/sru+xml',
    'srx' => 'application/sparql-results+xml',
    'ssdl' => 'application/ssdl+xml',
    'sse' => 'application/vnd.kodak-descriptor',
    'ssf' => 'application/vnd.epson.ssf',
    'ssml' => 'application/ssml+xml',
    'st' => 'application/vnd.sailingtracker.track',
    'stc' => 'application/vnd.sun.xml.calc.template',
    'std' => 'application/vnd.sun.xml.draw.template',
    'stf' => 'application/vnd.wt.stf',
    'sti' => 'application/vnd.sun.xml.impress.template',
    'stk' => 'application/hyperstudio',
    'stl' => 'application/vnd.ms-pki.stl',
    'str' => 'application/vnd.pg.format',
    'stw' => 'application/vnd.sun.xml.writer.template',
    'sub' => 'text/vnd.dvb.subtitle',
    'sus' => 'application/vnd.sus-calendar',
    'susp' => 'application/vnd.sus-calendar',
    'sv4cpio' => 'application/x-sv4cpio',
    'sv4crc' => 'application/x-sv4crc',
    'svc' => 'application/vnd.dvb.service',
    'svd' => 'application/vnd.svd',
    'svg' => 'image/svg+xml',
    'svgz' => 'image/svg+xml',
    'swa' => 'application/x-director',
    'swf' => 'application/x-shockwave-flash',
    'swi' => 'application/vnd.aristanetworks.swi',
    'sxc' => 'application/vnd.sun.xml.calc',
    'sxd' => 'application/vnd.sun.xml.draw',
    'sxg' => 'application/vnd.sun.xml.writer.global',
    'sxi' => 'application/vnd.sun.xml.impress',
    'sxm' => 'application/vnd.sun.xml.math',
    'sxw' => 'application/vnd.sun.xml.writer',
    't' => 'text/troff',
    't3' => 'application/x-t3vm-image',
    'taglet' => 'application/vnd.mynfc',
    'tao' => 'application/vnd.tao.intent-module-archive',
    'tar' => 'application/x-tar',
    'tcap' => 'application/vnd.3gpp2.tcap',
    'tcl' => 'application/x-tcl',
    'teacher' => 'application/vnd.smart.teacher',
    'tei' => 'application/tei+xml',
    'teicorpus' => 'application/tei+xml',
    'tex' => 'application/x-tex',
    'texi' => 'application/x-texinfo',
    'texinfo' => 'application/x-texinfo',
    'text' => 'text/plain',
    'tfi' => 'application/thraud+xml',
    'tfm' => 'application/x-tex-tfm',
    'tga' => 'image/x-tga',
    'thmx' => 'application/vnd.ms-officetheme',
    'tif' => 'image/tiff',
    'tiff' => 'image/tiff',
    'tmo' => 'application/vnd.tmobile-livetv',
    'torrent' => 'application/x-bittorrent',
    'tpl' => 'application/vnd.groove-tool-template',
    'tpt' => 'application/vnd.trid.tpt',
    'tr' => 'text/troff',
    'tra' => 'application/vnd.trueapp',
    'trm' => 'application/x-msterminal',
    'tsd' => 'application/timestamped-data',
    'tsv' => 'text/tab-separated-values',
    'ttc' => 'application/x-font-ttf',
    'ttf' => 'application/x-font-ttf',
    'ttl' => 'text/turtle',
    'twd' => 'application/vnd.simtech-mindmapper',
    'twds' => 'application/vnd.simtech-mindmapper',
    'txd' => 'application/vnd.genomatix.tuxedo',
    'txf' => 'application/vnd.mobius.txf',
    'txt' => 'text/plain',
    'u32' => 'application/x-authorware-bin',
    'udeb' => 'application/x-debian-package',
    'ufd' => 'application/vnd.ufdl',
    'ufdl' => 'application/vnd.ufdl',
    'ulx' => 'application/x-glulx',
    'umj' => 'application/vnd.umajin',
    'unityweb' => 'application/vnd.unity',
    'uoml' => 'application/vnd.uoml+xml',
    'uri' => 'text/uri-list',
    'uris' => 'text/uri-list',
    'urls' => 'text/uri-list',
    'ustar' => 'application/x-ustar',
    'utz' => 'application/vnd.uiq.theme',
    'uu' => 'text/x-uuencode',
    'uva' => 'audio/vnd.dece.audio',
    'uvd' => 'application/vnd.dece.data',
    'uvf' => 'application/vnd.dece.data',
    'uvg' => 'image/vnd.dece.graphic',
    'uvh' => 'video/vnd.dece.hd',
    'uvi' => 'image/vnd.dece.graphic',
    'uvm' => 'video/vnd.dece.mobile',
    'uvp' => 'video/vnd.dece.pd',
    'uvs' => 'video/vnd.dece.sd',
    'uvt' => 'application/vnd.dece.ttml+xml',
    'uvu' => 'video/vnd.uvvu.mp4',
    'uvv' => 'video/vnd.dece.video',
    'uvva' => 'audio/vnd.dece.audio',
    'uvvd' => 'application/vnd.dece.data',
    'uvvf' => 'application/vnd.dece.data',
    'uvvg' => 'image/vnd.dece.graphic',
    'uvvh' => 'video/vnd.dece.hd',
    'uvvi' => 'image/vnd.dece.graphic',
    'uvvm' => 'video/vnd.dece.mobile',
    'uvvp' => 'video/vnd.dece.pd',
    'uvvs' => 'video/vnd.dece.sd',
    'uvvt' => 'application/vnd.dece.ttml+xml',
    'uvvu' => 'video/vnd.uvvu.mp4',
    'uvvv' => 'video/vnd.dece.video',
    'uvvx' => 'application/vnd.dece.unspecified',
    'uvvz' => 'application/vnd.dece.zip',
    'uvx' => 'application/vnd.dece.unspecified',
    'uvz' => 'application/vnd.dece.zip',
    'vcard' => 'text/vcard',
    'vcd' => 'application/x-cdlink',
    'vcf' => 'text/x-vcard',
    'vcg' => 'application/vnd.groove-vcard',
    'vcs' => 'text/x-vcalendar',
    'vcx' => 'application/vnd.vcx',
    'vis' => 'application/vnd.visionary',
    'viv' => 'video/vnd.vivo',
    'vob' => 'video/x-ms-vob',
    'vor' => 'application/vnd.stardivision.writer',
    'vox' => 'application/x-authorware-bin',
    'vrml' => 'model/vrml',
    'vsd' => 'application/vnd.visio',
    'vsf' => 'application/vnd.vsf',
    'vss' => 'application/vnd.visio',
    'vst' => 'application/vnd.visio',
    'vsw' => 'application/vnd.visio',
    'vtu' => 'model/vnd.vtu',
    'vxml' => 'application/voicexml+xml',
    'w3d' => 'application/x-director',
    'wad' => 'application/x-doom',
    'wav' => 'audio/x-wav',
    'wax' => 'audio/x-ms-wax',
    'wbmp' => 'image/vnd.wap.wbmp',
    'wbs' => 'application/vnd.criticaltools.wbs+xml',
    'wbxml' => 'application/vnd.wap.wbxml',
    'wcm' => 'application/vnd.ms-works',
    'wdb' => 'application/vnd.ms-works',
    'wdp' => 'image/vnd.ms-photo',
    'weba' => 'audio/webm',
    'webm' => 'video/webm',
    'webp' => 'image/webp',
    'wg' => 'application/vnd.pmi.widget',
    'wgt' => 'application/widget',
    'wks' => 'application/vnd.ms-works',
    'wm' => 'video/x-ms-wm',
    'wma' => 'audio/x-ms-wma',
    'wmd' => 'application/x-ms-wmd',
    'wmf' => 'application/x-msmetafile',
    'wml' => 'text/vnd.wap.wml',
    'wmlc' => 'application/vnd.wap.wmlc',
    'wmls' => 'text/vnd.wap.wmlscript',
    'wmlsc' => 'application/vnd.wap.wmlscriptc',
    'wmv' => 'video/x-ms-wmv',
    'wmx' => 'video/x-ms-wmx',
    'wmz' => 'application/x-msmetafile',
    'woff' => 'application/font-woff',
    'wpd' => 'application/vnd.wordperfect',
    'wpl' => 'application/vnd.ms-wpl',
    'wps' => 'application/vnd.ms-works',
    'wqd' => 'application/vnd.wqd',
    'wri' => 'application/x-mswrite',
    'wrl' => 'model/vrml',
    'wsdl' => 'application/wsdl+xml',
    'wspolicy' => 'application/wspolicy+xml',
    'wtb' => 'application/vnd.webturbo',
    'wvx' => 'video/x-ms-wvx',
    'x32' => 'application/x-authorware-bin',
    'x3d' => 'model/x3d+xml',
    'x3db' => 'model/x3d+binary',
    'x3dbz' => 'model/x3d+binary',
    'x3dv' => 'model/x3d+vrml',
    'x3dvz' => 'model/x3d+vrml',
    'x3dz' => 'model/x3d+xml',
    'xaml' => 'application/xaml+xml',
    'xap' => 'application/x-silverlight-app',
    'xar' => 'application/vnd.xara',
    'xbap' => 'application/x-ms-xbap',
    'xbd' => 'application/vnd.fujixerox.docuworks.binder',
    'xbm' => 'image/x-xbitmap',
    'xdf' => 'application/xcap-diff+xml',
    'xdm' => 'application/vnd.syncml.dm+xml',
    'xdp' => 'application/vnd.adobe.xdp+xml',
    'xdssc' => 'application/dssc+xml',
    'xdw' => 'application/vnd.fujixerox.docuworks',
    'xenc' => 'application/xenc+xml',
    'xer' => 'application/patch-ops-error+xml',
    'xfdf' => 'application/vnd.adobe.xfdf',
    'xfdl' => 'application/vnd.xfdl',
    'xht' => 'application/xhtml+xml',
    'xhtml' => 'application/xhtml+xml',
    'xhvml' => 'application/xv+xml',
    'xif' => 'image/vnd.xiff',
    'xla' => 'application/vnd.ms-excel',
    'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12',
    'xlc' => 'application/vnd.ms-excel',
    'xlf' => 'application/x-xliff+xml',
    'xlm' => 'application/vnd.ms-excel',
    'xls' => 'application/vnd.ms-excel',
    'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroenabled.12',
    'xlsm' => 'application/vnd.ms-excel.sheet.macroenabled.12',
    'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'xlt' => 'application/vnd.ms-excel',
    'xltm' => 'application/vnd.ms-excel.template.macroenabled.12',
    'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
    'xlw' => 'application/vnd.ms-excel',
    'xm' => 'audio/xm',
    'xml' => 'application/xml',
    'xo' => 'application/vnd.olpc-sugar',
    'xop' => 'application/xop+xml',
    'xpi' => 'application/x-xpinstall',
    'xpl' => 'application/xproc+xml',
    'xpm' => 'image/x-xpixmap',
    'xpr' => 'application/vnd.is-xpr',
    'xps' => 'application/vnd.ms-xpsdocument',
    'xpw' => 'application/vnd.intercon.formnet',
    'xpx' => 'application/vnd.intercon.formnet',
    'xsl' => 'application/xml',
    'xslt' => 'application/xslt+xml',
    'xsm' => 'application/vnd.syncml+xml',
    'xspf' => 'application/xspf+xml',
    'xul' => 'application/vnd.mozilla.xul+xml',
    'xvm' => 'application/xv+xml',
    'xvml' => 'application/xv+xml',
    'xwd' => 'image/x-xwindowdump',
    'xyz' => 'chemical/x-xyz',
    'xz' => 'application/x-xz',
    'yang' => 'application/yang',
    'yin' => 'application/yin+xml',
    'z1' => 'application/x-zmachine',
    'z2' => 'application/x-zmachine',
    'z3' => 'application/x-zmachine',
    'z4' => 'application/x-zmachine',
    'z5' => 'application/x-zmachine',
    'z6' => 'application/x-zmachine',
    'z7' => 'application/x-zmachine',
    'z8' => 'application/x-zmachine',
    'zaz' => 'application/vnd.zzazz.deck+xml',
    'zip' => 'application/zip',
    'zir' => 'application/vnd.zul',
    'zirz' => 'application/vnd.zul',
    'zmm' => 'application/vnd.handheld-entertainment+xml',
    '123' => 'application/vnd.lotus-1-2-3',
);
<?php

/****************************************************************************/
/*                                                                          */
/* YOU MAY WISH TO MODIFY OR REMOVE THE FOLLOWING LINES WHICH SET DEFAULTS  */
/*                                                                          */
/****************************************************************************/

$preferences = Swift_Preferences::getInstance();

// Sets the default charset so that setCharset() is not needed elsewhere
$preferences->setCharset('utf-8');

// Without these lines the default caching mechanism is "array" but this uses a lot of memory.
// If possible, use a disk cache to enable attaching large attachments etc.
// You can override the default temporary directory by setting the TMPDIR environment variable.
if (@is_writable($tmpDir = sys_get_temp_dir())) {
    $preferences->setTempDir($tmpDir)->setCacheType('disk');
}

// this should only be done when Swiftmailer won't use the native QP content encoder
// see mime_deps.php
if (version_compare(phpversion(), '5.4.7', '<')) {
    $preferences->setQPDotEscape(false);
}
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/*
 * Dependency injection initialization for Swift Mailer.
 */

if (defined('SWIFT_INIT_LOADED')) {
    return;
}

define('SWIFT_INIT_LOADED', true);

// Load in dependency maps
require __DIR__.'/dependency_maps/cache_deps.php';
require __DIR__.'/dependency_maps/mime_deps.php';
require __DIR__.'/dependency_maps/message_deps.php';
require __DIR__.'/dependency_maps/transport_deps.php';

// Load in global library preferences
require __DIR__.'/preferences.php';
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/*
 * Autoloader and dependency injection initialization for Swift Mailer.
 */

if (class_exists('Swift', false)) {
    return;
}

// Load Swift utility class
require __DIR__.'/classes/Swift.php';

if (!function_exists('_swiftmailer_init')) {
    function _swiftmailer_init()
    {
        require __DIR__.'/swift_init.php';
    }
}

// Start the autoloader and lazy-load the init script to set up dependency injection
Swift::registerAutoload('_swiftmailer_init');
<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/*
 * Autoloader and dependency injection initialization for Swift Mailer.
 */

if (class_exists('Swift', false)) {
    return;
}

// Load Swift utility class
require __DIR__.'/Swift.php';

if (!function_exists('_swiftmailer_init')) {
    function _swiftmailer_init()
    {
        require __DIR__.'/swift_init.php';
    }
}

// Start the autoloader and lazy-load the init script to set up dependency injection
Swift::registerAutoload('_swiftmailer_init');
#!/usr/bin/php

<?php
define('APACHE_MIME_TYPES_URL', 'http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types');
define('FREEDESKTOP_XML_URL', 'https://raw2.github.com/minad/mimemagic/master/script/freedesktop.org.xml');

function generateUpToDateMimeArray()
{
    $preamble = "<?php\n\n";
    $preamble .= "/*\n";
    $preamble .= " * This file is part of SwiftMailer.\n";
    $preamble .= " * (c) 2004-2009 Chris Corbyn\n *\n";
    $preamble .= " * For the full copyright and license information, please view the LICENSE\n";
    $preamble .= " * file that was distributed with this source code.\n *\n";
    $preamble .= " * autogenerated using http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types\n";
    $preamble .= " * and https://raw.github.com/minad/mimemagic/master/script/freedesktop.org.xml\n";
    $preamble .= " */\n\n";
    $preamble .= "/*\n";
    $preamble .= " * List of MIME type automatically detected in Swift Mailer.\n";
    $preamble .= " */\n\n";
    $preamble .= "// You may add or take away what you like (lowercase required)\n\n";

    // get current mime types files
    $mime_types = @file_get_contents(APACHE_MIME_TYPES_URL);
    $mime_xml = @file_get_contents(FREEDESKTOP_XML_URL);

    // prepare valid mime types
    $valid_mime_types = array();

    // split mime type and extensions eg. "video/x-matroska        mkv mk3d mks"
    if (preg_match_all('/^#?([a-z0-9\-\+\/\.]+)[\t]+(.*)$/miu', $mime_types, $matches) !== false) {
        // collection of predefined mimetypes (bugfix for wrong resolved or missing mime types)
        $valid_mime_types_preset = array(
            'php' => 'application/x-php',
            'php3' => 'application/x-php',
            'php4' => 'application/x-php',
            'php5' => 'application/x-php',
            'zip' => 'application/zip',
            'gif' => 'image/gif',
            'png' => 'image/png',
            'css' => 'text/css',
            'js' => 'text/javascript',
            'txt' => 'text/plain',
            'aif' => 'audio/x-aiff',
            'aiff' => 'audio/x-aiff',
            'avi' => 'video/avi',
            'bmp' => 'image/bmp',
            'bz2' => 'application/x-bz2',
            'csv' => 'text/csv',
            'dmg' => 'application/x-apple-diskimage',
            'doc' => 'application/msword',
            'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'eml' => 'message/rfc822',
            'aps' => 'application/postscript',
            'exe' => 'application/x-ms-dos-executable',
            'flv' => 'video/x-flv',
            'gz' => 'application/x-gzip',
            'hqx' => 'application/stuffit',
            'htm' => 'text/html',
            'html' => 'text/html',
            'jar' => 'application/x-java-archive',
            'jpeg' => 'image/jpeg',
            'jpg' => 'image/jpeg',
            'm3u' => 'audio/x-mpegurl',
            'm4a' => 'audio/mp4',
            'mdb' => 'application/x-msaccess',
            'mid' => 'audio/midi',
            'midi' => 'audio/midi',
            'mov' => 'video/quicktime',
            'mp3' => 'audio/mpeg',
            'mp4' => 'video/mp4',
            'mpeg' => 'video/mpeg',
            'mpg' => 'video/mpeg',
            'odg' => 'vnd.oasis.opendocument.graphics',
            'odp' => 'vnd.oasis.opendocument.presentation',
            'odt' => 'vnd.oasis.opendocument.text',
            'ods' => 'vnd.oasis.opendocument.spreadsheet',
            'ogg' => 'audio/ogg',
            'pdf' => 'application/pdf',
            'ppt' => 'application/vnd.ms-powerpoint',
            'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
            'ps' => 'application/postscript',
            'rar' => 'application/x-rar-compressed',
            'rtf' => 'application/rtf',
            'tar' => 'application/x-tar',
            'sit' => 'application/x-stuffit',
            'svg' => 'image/svg+xml',
            'tif' => 'image/tiff',
            'tiff' => 'image/tiff',
            'ttf' => 'application/x-font-truetype',
            'vcf' => 'text/x-vcard',
            'wav' => 'audio/wav',
            'wma' => 'audio/x-ms-wma',
            'wmv' => 'audio/x-ms-wmv',
            'xls' => 'application/excel',
            'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'xml' => 'application/xml',
        );

        // wrap array for generating file
        foreach ($valid_mime_types_preset as $extension => $mime_type) {
            // generate array for mimetype to extension resolver (only first match)
            $valid_mime_types[$extension] = "'{$extension}' => '{$mime_type}'";
        }

        // collect extensions
        $valid_extensions = array();

        // all extensions from second match
        foreach ($matches[2] as $i => $extensions) {
            // explode multiple extensions from string
            $extensions = explode(' ', strtolower($extensions));

            // force array for foreach
            if (!is_array($extensions)) {
                $extensions = array($extensions);
            }

            foreach ($extensions as $extension) {
                // get mime type
                $mime_type = $matches[1][$i];

                // check if string length lower than 10
                if (strlen($extension) < 10) {
                    // add extension
                    $valid_extensions[] = $extension;

                    if (!isset($valid_mime_types[$mime_type])) {
                        // generate array for mimetype to extension resolver (only first match)
                        $valid_mime_types[$extension] = "'{$extension}' => '{$mime_type}'";
                    }
                }
            }
        }
    }

    $xml = simplexml_load_string($mime_xml);

    foreach ($xml as $node) {
        // check if there is no pattern
        if (!isset($node->glob['pattern'])) {
            continue;
        }

        // get all matching extensions from match
        foreach ((array) $node->glob['pattern'] as $extension) {
            // skip none glob extensions
            if (strpos($extension, '.') === false) {
                continue;
            }

            // remove get only last part
            $extension = explode('.', strtolower($extension));
            $extension = end($extension);

            // maximum length in database column
            if (strlen($extension) <= 9) {
                $valid_extensions[] = $extension;
            }
        }

        if (isset($node->glob['pattern'][0])) {
            // mime type
            $mime_type = strtolower((string) $node['type']);

            // get first extension
            $extension = strtolower(trim($node->glob['ddpattern'][0], '*.'));

            // skip none glob extensions and check if string length between 1 and 10
            if (strpos($extension, '.') !== false || strlen($extension) < 1 || strlen($extension) > 9) {
                continue;
            }

            // check if string length lower than 10
            if (!isset($valid_mime_types[$mime_type])) {
                // generate array for mimetype to extension resolver (only first match)
                $valid_mime_types[$extension] = "'{$extension}' => '{$mime_type}'";
            }
        }
    }

    // full list of valid extensions only
    $valid_mime_types = array_unique($valid_mime_types);
    ksort($valid_mime_types);

    // combine mime types and extensions array
    $output = "$preamble\$swift_mime_types = array(\n    ".implode($valid_mime_types, ",\n    ")."\n);";

    // write mime_types.php config file
    @file_put_contents('./mime_types.php', $output);
}

generateUpToDateMimeArray();
<?php

require_once 'swift_required.php';
require_once __DIR__.'/Mime/AttachmentAcceptanceTest.php';

class Swift_AttachmentAcceptanceTest extends Swift_Mime_AttachmentAcceptanceTest
{
    protected function _createAttachment()
    {
        return Swift_Attachment::newInstance();
    }
}
<?php

class Swift_ByteStream_FileByteStreamAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    private $_tmpDir;
    private $_testFile;

    public function setUp()
    {
        if (!defined('SWIFT_TMP_DIR')) {
            $this->markTestSkipped(
                'Cannot run test without a writable directory to use ('.
                'define SWIFT_TMP_DIR in tests/config.php if you wish to run this test)'
             );
        }

        $this->_tmpDir = SWIFT_TMP_DIR;
        $this->_testFile = $this->_tmpDir.'/swift-test-file'.__CLASS__;
        file_put_contents($this->_testFile, 'abcdefghijklm');
    }

    public function tearDown()
    {
        unlink($this->_testFile);
    }

    public function testFileDataCanBeRead()
    {
        $file = $this->_createFileStream($this->_testFile);
        $str = '';
        while (false !== $bytes = $file->read(8192)) {
            $str .= $bytes;
        }
        $this->assertEquals('abcdefghijklm', $str);
    }

    public function testFileDataCanBeReadSequentially()
    {
        $file = $this->_createFileStream($this->_testFile);
        $this->assertEquals('abcde', $file->read(5));
        $this->assertEquals('fghijklm', $file->read(8));
        $this->assertFalse($file->read(1));
    }

    public function testFilenameIsReturned()
    {
        $file = $this->_createFileStream($this->_testFile);
        $this->assertEquals($this->_testFile, $file->getPath());
    }

    public function testFileCanBeWrittenTo()
    {
        $file = $this->_createFileStream(
            $this->_testFile, true
            );
        $file->write('foobar');
        $this->assertEquals('foobar', $file->read(8192));
    }

    public function testReadingFromThenWritingToFile()
    {
        $file = $this->_createFileStream(
            $this->_testFile, true
            );
        $file->write('foobar');
        $this->assertEquals('foobar', $file->read(8192));
        $file->write('zipbutton');
        $this->assertEquals('zipbutton', $file->read(8192));
    }

    public function testWritingToFileWithCanonicalization()
    {
        $file = $this->_createFileStream(
            $this->_testFile, true
            );
        $file->addFilter($this->_createFilter(array("\r\n", "\r"), "\n"), 'allToLF');
        $file->write("foo\r\nbar\r");
        $file->write("\nzip\r\ntest\r");
        $file->flushBuffers();
        $this->assertEquals("foo\nbar\nzip\ntest\n", file_get_contents($this->_testFile));
    }

    public function testBindingOtherStreamsMirrorsWriteOperations()
    {
        $file = $this->_createFileStream(
            $this->_testFile, true
            );
        $is1 = $this->_createMockInputStream();
        $is2 = $this->_createMockInputStream();

        $is1->expects($this->at(0))
            ->method('write')
            ->with('x');
        $is1->expects($this->at(1))
            ->method('write')
            ->with('y');
        $is2->expects($this->at(0))
            ->method('write')
            ->with('x');
        $is2->expects($this->at(1))
            ->method('write')
            ->with('y');

        $file->bind($is1);
        $file->bind($is2);

        $file->write('x');
        $file->write('y');
    }

    public function testBindingOtherStreamsMirrorsFlushOperations()
    {
        $file = $this->_createFileStream(
            $this->_testFile, true
            );
        $is1 = $this->_createMockInputStream();
        $is2 = $this->_createMockInputStream();

        $is1->expects($this->once())
            ->method('flushBuffers');
        $is2->expects($this->once())
            ->method('flushBuffers');

        $file->bind($is1);
        $file->bind($is2);

        $file->flushBuffers();
    }

    public function testUnbindingStreamPreventsFurtherWrites()
    {
        $file = $this->_createFileStream(
            $this->_testFile, true
            );
        $is1 = $this->_createMockInputStream();
        $is2 = $this->_createMockInputStream();

        $is1->expects($this->at(0))
            ->method('write')
            ->with('x');
        $is1->expects($this->at(1))
            ->method('write')
            ->with('y');
        $is2->expects($this->once())
            ->method('write')
            ->with('x');

        $file->bind($is1);
        $file->bind($is2);

        $file->write('x');

        $file->unbind($is2);

        $file->write('y');
    }

    // -- Creation methods

    private function _createFilter($search, $replace)
    {
        return new Swift_StreamFilters_StringReplacementFilter($search, $replace);
    }

    private function _createMockInputStream()
    {
        return $this->getMockBuilder('Swift_InputByteStream')->getMock();
    }

    private function _createFileStream($file, $writable = false)
    {
        return new Swift_ByteStream_FileByteStream($file, $writable);
    }
}
<?php

class Swift_CharacterReaderFactory_SimpleCharacterReaderFactoryAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    private $_factory;
    private $_prefix = 'Swift_CharacterReader_';

    public function setUp()
    {
        $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
    }

    public function testCreatingUtf8Reader()
    {
        foreach (array('utf8', 'utf-8', 'UTF-8', 'UTF8') as $utf8) {
            $reader = $this->_factory->getReaderFor($utf8);
            $this->assertInstanceof($this->_prefix.'Utf8Reader', $reader);
        }
    }

    public function testCreatingIso8859XReaders()
    {
        $charsets = array();
        foreach (range(1, 16) as $number) {
            foreach (array('iso', 'iec') as $body) {
                $charsets[] = $body.'-8859-'.$number;
                $charsets[] = $body.'8859-'.$number;
                $charsets[] = strtoupper($body).'-8859-'.$number;
                $charsets[] = strtoupper($body).'8859-'.$number;
            }
        }

        foreach ($charsets as $charset) {
            $reader = $this->_factory->getReaderFor($charset);
            $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader);
            $this->assertEquals(1, $reader->getInitialByteSize());
        }
    }

    public function testCreatingWindows125XReaders()
    {
        $charsets = array();
        foreach (range(0, 8) as $number) {
            $charsets[] = 'windows-125'.$number;
            $charsets[] = 'windows125'.$number;
            $charsets[] = 'WINDOWS-125'.$number;
            $charsets[] = 'WINDOWS125'.$number;
        }

        foreach ($charsets as $charset) {
            $reader = $this->_factory->getReaderFor($charset);
            $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader);
            $this->assertEquals(1, $reader->getInitialByteSize());
        }
    }

    public function testCreatingCodePageReaders()
    {
        $charsets = array();
        foreach (range(0, 8) as $number) {
            $charsets[] = 'cp-125'.$number;
            $charsets[] = 'cp125'.$number;
            $charsets[] = 'CP-125'.$number;
            $charsets[] = 'CP125'.$number;
        }

        foreach (array(437, 737, 850, 855, 857, 858, 860,
            861, 863, 865, 866, 869, ) as $number) {
            $charsets[] = 'cp-'.$number;
            $charsets[] = 'cp'.$number;
            $charsets[] = 'CP-'.$number;
            $charsets[] = 'CP'.$number;
        }

        foreach ($charsets as $charset) {
            $reader = $this->_factory->getReaderFor($charset);
            $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader);
            $this->assertEquals(1, $reader->getInitialByteSize());
        }
    }

    public function testCreatingAnsiReader()
    {
        foreach (array('ansi', 'ANSI') as $ansi) {
            $reader = $this->_factory->getReaderFor($ansi);
            $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader);
            $this->assertEquals(1, $reader->getInitialByteSize());
        }
    }

    public function testCreatingMacintoshReader()
    {
        foreach (array('macintosh', 'MACINTOSH') as $mac) {
            $reader = $this->_factory->getReaderFor($mac);
            $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader);
            $this->assertEquals(1, $reader->getInitialByteSize());
        }
    }

    public function testCreatingKOIReaders()
    {
        $charsets = array();
        foreach (array('7', '8-r', '8-u', '8u', '8r') as $end) {
            $charsets[] = 'koi-'.$end;
            $charsets[] = 'koi'.$end;
            $charsets[] = 'KOI-'.$end;
            $charsets[] = 'KOI'.$end;
        }

        foreach ($charsets as $charset) {
            $reader = $this->_factory->getReaderFor($charset);
            $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader);
            $this->assertEquals(1, $reader->getInitialByteSize());
        }
    }

    public function testCreatingIsciiReaders()
    {
        foreach (array('iscii', 'ISCII', 'viscii', 'VISCII') as $charset) {
            $reader = $this->_factory->getReaderFor($charset);
            $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader);
            $this->assertEquals(1, $reader->getInitialByteSize());
        }
    }

    public function testCreatingMIKReader()
    {
        foreach (array('mik', 'MIK') as $charset) {
            $reader = $this->_factory->getReaderFor($charset);
            $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader);
            $this->assertEquals(1, $reader->getInitialByteSize());
        }
    }

    public function testCreatingCorkReader()
    {
        foreach (array('cork', 'CORK', 't1', 'T1') as $charset) {
            $reader = $this->_factory->getReaderFor($charset);
            $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader);
            $this->assertEquals(1, $reader->getInitialByteSize());
        }
    }

    public function testCreatingUcs2Reader()
    {
        foreach (array('ucs-2', 'UCS-2', 'ucs2', 'UCS2') as $charset) {
            $reader = $this->_factory->getReaderFor($charset);
            $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader);
            $this->assertEquals(2, $reader->getInitialByteSize());
        }
    }

    public function testCreatingUtf16Reader()
    {
        foreach (array('utf-16', 'UTF-16', 'utf16', 'UTF16') as $charset) {
            $reader = $this->_factory->getReaderFor($charset);
            $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader);
            $this->assertEquals(2, $reader->getInitialByteSize());
        }
    }

    public function testCreatingUcs4Reader()
    {
        foreach (array('ucs-4', 'UCS-4', 'ucs4', 'UCS4') as $charset) {
            $reader = $this->_factory->getReaderFor($charset);
            $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader);
            $this->assertEquals(4, $reader->getInitialByteSize());
        }
    }

    public function testCreatingUtf32Reader()
    {
        foreach (array('utf-32', 'UTF-32', 'utf32', 'UTF32') as $charset) {
            $reader = $this->_factory->getReaderFor($charset);
            $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader);
            $this->assertEquals(4, $reader->getInitialByteSize());
        }
    }
}
<?php

require_once 'swift_required.php';

//This is more of a "cross your fingers and hope it works" test!

class Swift_DependencyContainerAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    public function testNoLookupsFail()
    {
        $di = Swift_DependencyContainer::getInstance();
        foreach ($di->listItems() as $itemName) {
            try {
                // to be removed in 6.0
                if ('transport.mail' === $itemName) {
                    continue;
                }
                $di->lookup($itemName);
            } catch (Swift_DependencyException $e) {
                $this->fail($e->getMessage());
            }
        }
    }
}
<?php

require_once 'swift_required.php';
require_once __DIR__.'/Mime/EmbeddedFileAcceptanceTest.php';

class Swift_EmbeddedFileAcceptanceTest extends Swift_Mime_EmbeddedFileAcceptanceTest
{
    protected function _createEmbeddedFile()
    {
        return Swift_EmbeddedFile::newInstance();
    }
}
<?php

class Swift_Encoder_Base64EncoderAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    private $_samplesDir;
    private $_encoder;

    public function setUp()
    {
        $this->_samplesDir = realpath(__DIR__.'/../../../_samples/charsets');
        $this->_encoder = new Swift_Encoder_Base64Encoder();
    }

    public function testEncodingAndDecodingSamples()
    {
        $sampleFp = opendir($this->_samplesDir);
        while (false !== $encodingDir = readdir($sampleFp)) {
            if (substr($encodingDir, 0, 1) == '.') {
                continue;
            }

            $sampleDir = $this->_samplesDir.'/'.$encodingDir;

            if (is_dir($sampleDir)) {
                $fileFp = opendir($sampleDir);
                while (false !== $sampleFile = readdir($fileFp)) {
                    if (substr($sampleFile, 0, 1) == '.') {
                        continue;
                    }

                    $text = file_get_contents($sampleDir.'/'.$sampleFile);
                    $encodedText = $this->_encoder->encodeString($text);

                    $this->assertEquals(
                        base64_decode($encodedText), $text,
                        '%s: Encoded string should decode back to original string for sample '.
                        $sampleDir.'/'.$sampleFile
                        );
                }
                closedir($fileFp);
            }
        }
        closedir($sampleFp);
    }
}
<?php

class Swift_Encoder_QpEncoderAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    private $_samplesDir;
    private $_factory;

    public function setUp()
    {
        $this->_samplesDir = realpath(__DIR__.'/../../../_samples/charsets');
        $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
    }

    public function testEncodingAndDecodingSamples()
    {
        $sampleFp = opendir($this->_samplesDir);
        while (false !== $encodingDir = readdir($sampleFp)) {
            if (substr($encodingDir, 0, 1) == '.') {
                continue;
            }

            $encoding = $encodingDir;
            $charStream = new Swift_CharacterStream_ArrayCharacterStream(
                $this->_factory, $encoding);
            $encoder = new Swift_Encoder_QpEncoder($charStream);

            $sampleDir = $this->_samplesDir.'/'.$encodingDir;

            if (is_dir($sampleDir)) {
                $fileFp = opendir($sampleDir);
                while (false !== $sampleFile = readdir($fileFp)) {
                    if (substr($sampleFile, 0, 1) == '.') {
                        continue;
                    }

                    $text = file_get_contents($sampleDir.'/'.$sampleFile);
                    $encodedText = $encoder->encodeString($text);

                    foreach (explode("\r\n", $encodedText) as $line) {
                        $this->assertLessThanOrEqual(76, strlen($line));
                    }

                    $this->assertEquals(
                        quoted_printable_decode($encodedText), $text,
                        '%s: Encoded string should decode back to original string for sample '.
                        $sampleDir.'/'.$sampleFile
                        );
                }
                closedir($fileFp);
            }
        }
        closedir($sampleFp);
    }
}
<?php

class Swift_Encoder_Rfc2231EncoderAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    private $_samplesDir;
    private $_factory;

    public function setUp()
    {
        $this->_samplesDir = realpath(__DIR__.'/../../../_samples/charsets');
        $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
    }

    public function testEncodingAndDecodingSamples()
    {
        $sampleFp = opendir($this->_samplesDir);
        while (false !== $encodingDir = readdir($sampleFp)) {
            if (substr($encodingDir, 0, 1) == '.') {
                continue;
            }

            $encoding = $encodingDir;
            $charStream = new Swift_CharacterStream_ArrayCharacterStream(
                $this->_factory, $encoding);
            $encoder = new Swift_Encoder_Rfc2231Encoder($charStream);

            $sampleDir = $this->_samplesDir.'/'.$encodingDir;

            if (is_dir($sampleDir)) {
                $fileFp = opendir($sampleDir);
                while (false !== $sampleFile = readdir($fileFp)) {
                    if (substr($sampleFile, 0, 1) == '.') {
                        continue;
                    }

                    $text = file_get_contents($sampleDir.'/'.$sampleFile);
                    $encodedText = $encoder->encodeString($text);

                    $this->assertEquals(
                        urldecode(implode('', explode("\r\n", $encodedText))), $text,
                        '%s: Encoded string should decode back to original string for sample '.
                        $sampleDir.'/'.$sampleFile
                        );
                }
                closedir($fileFp);
            }
        }
        closedir($sampleFp);
    }
}
<?php

require_once 'swift_required.php';

class Swift_EncodingAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    public function testGet7BitEncodingReturns7BitEncoder()
    {
        $encoder = Swift_Encoding::get7BitEncoding();
        $this->assertEquals('7bit', $encoder->getName());
    }

    public function testGet8BitEncodingReturns8BitEncoder()
    {
        $encoder = Swift_Encoding::get8BitEncoding();
        $this->assertEquals('8bit', $encoder->getName());
    }

    public function testGetQpEncodingReturnsQpEncoder()
    {
        $encoder = Swift_Encoding::getQpEncoding();
        $this->assertEquals('quoted-printable', $encoder->getName());
    }

    public function testGetBase64EncodingReturnsBase64Encoder()
    {
        $encoder = Swift_Encoding::getBase64Encoding();
        $this->assertEquals('base64', $encoder->getName());
    }
}
<?php

class Swift_KeyCache_ArrayKeyCacheAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    private $_cache;
    private $_key1 = 'key1';
    private $_key2 = 'key2';

    public function setUp()
    {
        $this->_cache = new Swift_KeyCache_ArrayKeyCache(
            new Swift_KeyCache_SimpleKeyCacheInputStream()
            );
    }

    public function testStringDataCanBeSetAndFetched()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo'));
    }

    public function testStringDataCanBeOverwritten()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->_cache->setString(
            $this->_key1, 'foo', 'whatever', Swift_KeyCache::MODE_WRITE
            );
        $this->assertEquals('whatever', $this->_cache->getString($this->_key1, 'foo'));
    }

    public function testStringDataCanBeAppended()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->_cache->setString(
            $this->_key1, 'foo', 'ing', Swift_KeyCache::MODE_APPEND
            );
        $this->assertEquals('testing', $this->_cache->getString($this->_key1, 'foo'));
    }

    public function testHasKeyReturnValue()
    {
        $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo'));
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo'));
    }

    public function testNsKeyIsWellPartitioned()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->_cache->setString(
            $this->_key2, 'foo', 'ing', Swift_KeyCache::MODE_WRITE
            );
        $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo'));
        $this->assertEquals('ing', $this->_cache->getString($this->_key2, 'foo'));
    }

    public function testItemKeyIsWellPartitioned()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->_cache->setString(
            $this->_key1, 'bar', 'ing', Swift_KeyCache::MODE_WRITE
            );
        $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo'));
        $this->assertEquals('ing', $this->_cache->getString($this->_key1, 'bar'));
    }

    public function testByteStreamCanBeImported()
    {
        $os = new Swift_ByteStream_ArrayByteStream();
        $os->write('abcdef');

        $this->_cache->importFromByteStream(
            $this->_key1, 'foo', $os, Swift_KeyCache::MODE_WRITE
            );
        $this->assertEquals('abcdef', $this->_cache->getString($this->_key1, 'foo'));
    }

    public function testByteStreamCanBeAppended()
    {
        $os1 = new Swift_ByteStream_ArrayByteStream();
        $os1->write('abcdef');

        $os2 = new Swift_ByteStream_ArrayByteStream();
        $os2->write('xyzuvw');

        $this->_cache->importFromByteStream(
            $this->_key1, 'foo', $os1, Swift_KeyCache::MODE_APPEND
            );
        $this->_cache->importFromByteStream(
            $this->_key1, 'foo', $os2, Swift_KeyCache::MODE_APPEND
            );

        $this->assertEquals('abcdefxyzuvw', $this->_cache->getString($this->_key1, 'foo'));
    }

    public function testByteStreamAndStringCanBeAppended()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_APPEND
            );

        $os = new Swift_ByteStream_ArrayByteStream();
        $os->write('abcdef');

        $this->_cache->importFromByteStream(
            $this->_key1, 'foo', $os, Swift_KeyCache::MODE_APPEND
            );
        $this->assertEquals('testabcdef', $this->_cache->getString($this->_key1, 'foo'));
    }

    public function testDataCanBeExportedToByteStream()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );

        $is = new Swift_ByteStream_ArrayByteStream();

        $this->_cache->exportToByteStream($this->_key1, 'foo', $is);

        $string = '';
        while (false !== $bytes = $is->read(8192)) {
            $string .= $bytes;
        }

        $this->assertEquals('test', $string);
    }

    public function testKeyCanBeCleared()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo'));
        $this->_cache->clearKey($this->_key1, 'foo');
        $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo'));
    }

    public function testNsKeyCanBeCleared()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->_cache->setString(
            $this->_key1, 'bar', 'xyz', Swift_KeyCache::MODE_WRITE
            );
        $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo'));
        $this->assertTrue($this->_cache->hasKey($this->_key1, 'bar'));
        $this->_cache->clearAll($this->_key1);
        $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo'));
        $this->assertFalse($this->_cache->hasKey($this->_key1, 'bar'));
    }

    public function testKeyCacheInputStream()
    {
        $is = $this->_cache->getInputByteStream($this->_key1, 'foo');
        $is->write('abc');
        $is->write('xyz');
        $this->assertEquals('abcxyz', $this->_cache->getString($this->_key1, 'foo'));
    }
}
<?php

class Swift_KeyCache_DiskKeyCacheAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    private $_cache;
    private $_key1;
    private $_key2;

    public function setUp()
    {
        if (!defined('SWIFT_TMP_DIR')) {
            $this->markTestSkipped(
                'Cannot run test without a writable directory to use ('.
                'define SWIFT_TMP_DIR in tests/config.php if you wish to run this test)'
             );
        }

        $this->_key1 = uniqid(microtime(true), true);
        $this->_key2 = uniqid(microtime(true), true);
        $this->_cache = new Swift_KeyCache_DiskKeyCache(
            new Swift_KeyCache_SimpleKeyCacheInputStream(),
            SWIFT_TMP_DIR
            );
    }

    public function testStringDataCanBeSetAndFetched()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo'));
    }

    public function testStringDataCanBeOverwritten()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->_cache->setString(
            $this->_key1, 'foo', 'whatever', Swift_KeyCache::MODE_WRITE
            );
        $this->assertEquals('whatever', $this->_cache->getString($this->_key1, 'foo'));
    }

    public function testStringDataCanBeAppended()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->_cache->setString(
            $this->_key1, 'foo', 'ing', Swift_KeyCache::MODE_APPEND
            );
        $this->assertEquals('testing', $this->_cache->getString($this->_key1, 'foo'));
    }

    public function testHasKeyReturnValue()
    {
        $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo'));
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo'));
    }

    public function testNsKeyIsWellPartitioned()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->_cache->setString(
            $this->_key2, 'foo', 'ing', Swift_KeyCache::MODE_WRITE
            );
        $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo'));
        $this->assertEquals('ing', $this->_cache->getString($this->_key2, 'foo'));
    }

    public function testItemKeyIsWellPartitioned()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->_cache->setString(
            $this->_key1, 'bar', 'ing', Swift_KeyCache::MODE_WRITE
            );
        $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo'));
        $this->assertEquals('ing', $this->_cache->getString($this->_key1, 'bar'));
    }

    public function testByteStreamCanBeImported()
    {
        $os = new Swift_ByteStream_ArrayByteStream();
        $os->write('abcdef');

        $this->_cache->importFromByteStream(
            $this->_key1, 'foo', $os, Swift_KeyCache::MODE_WRITE
            );
        $this->assertEquals('abcdef', $this->_cache->getString($this->_key1, 'foo'));
    }

    public function testByteStreamCanBeAppended()
    {
        $os1 = new Swift_ByteStream_ArrayByteStream();
        $os1->write('abcdef');

        $os2 = new Swift_ByteStream_ArrayByteStream();
        $os2->write('xyzuvw');

        $this->_cache->importFromByteStream(
            $this->_key1, 'foo', $os1, Swift_KeyCache::MODE_APPEND
            );
        $this->_cache->importFromByteStream(
            $this->_key1, 'foo', $os2, Swift_KeyCache::MODE_APPEND
            );

        $this->assertEquals('abcdefxyzuvw', $this->_cache->getString($this->_key1, 'foo'));
    }

    public function testByteStreamAndStringCanBeAppended()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_APPEND
            );

        $os = new Swift_ByteStream_ArrayByteStream();
        $os->write('abcdef');

        $this->_cache->importFromByteStream(
            $this->_key1, 'foo', $os, Swift_KeyCache::MODE_APPEND
            );
        $this->assertEquals('testabcdef', $this->_cache->getString($this->_key1, 'foo'));
    }

    public function testDataCanBeExportedToByteStream()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );

        $is = new Swift_ByteStream_ArrayByteStream();

        $this->_cache->exportToByteStream($this->_key1, 'foo', $is);

        $string = '';
        while (false !== $bytes = $is->read(8192)) {
            $string .= $bytes;
        }

        $this->assertEquals('test', $string);
    }

    public function testKeyCanBeCleared()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo'));
        $this->_cache->clearKey($this->_key1, 'foo');
        $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo'));
    }

    public function testNsKeyCanBeCleared()
    {
        $this->_cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->_cache->setString(
            $this->_key1, 'bar', 'xyz', Swift_KeyCache::MODE_WRITE
            );
        $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo'));
        $this->assertTrue($this->_cache->hasKey($this->_key1, 'bar'));
        $this->_cache->clearAll($this->_key1);
        $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo'));
        $this->assertFalse($this->_cache->hasKey($this->_key1, 'bar'));
    }

    public function testKeyCacheInputStream()
    {
        $is = $this->_cache->getInputByteStream($this->_key1, 'foo');
        $is->write('abc');
        $is->write('xyz');
        $this->assertEquals('abcxyz', $this->_cache->getString($this->_key1, 'foo'));
    }
}
<?php

require_once 'swift_required.php';
require_once __DIR__.'/Mime/SimpleMessageAcceptanceTest.php';

class Swift_MessageAcceptanceTest extends Swift_Mime_SimpleMessageAcceptanceTest
{
    public function testAddPartWrapper()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setFrom(array(
            'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));

        $id = $message->getId();
        $date = $message->getDate();
        $boundary = $message->getBoundary();

        $message->addPart('foo', 'text/plain', 'iso-8859-1');
        $message->addPart('test <b>foo</b>', 'text/html', 'iso-8859-1');

        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: multipart/alternative;'."\r\n".
            ' boundary="'.$boundary.'"'."\r\n".
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: text/plain; charset=iso-8859-1'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'foo'.
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: text/html; charset=iso-8859-1'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'test <b>foo</b>'.
            "\r\n\r\n".
            '--'.$boundary.'--'."\r\n",
            $message->toString()
            );
    }

    // -- Private helpers

    protected function _createMessage()
    {
        Swift_DependencyContainer::getInstance()
            ->register('properties.charset')->asValue(null);

        return Swift_Message::newInstance();
    }
}
<?php

class Swift_Mime_AttachmentAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    private $_contentEncoder;
    private $_cache;
    private $_grammar;
    private $_headers;

    public function setUp()
    {
        $this->_cache = new Swift_KeyCache_ArrayKeyCache(
            new Swift_KeyCache_SimpleKeyCacheInputStream()
            );
        $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
        $this->_contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder();

        $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder(
            new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')
            );
        $paramEncoder = new Swift_Encoder_Rfc2231Encoder(
            new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')
            );
        $this->_grammar = new Swift_Mime_Grammar();
        $this->_headers = new Swift_Mime_SimpleHeaderSet(
            new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $this->_grammar)
            );
    }

    public function testDispositionIsSetInHeader()
    {
        $attachment = $this->_createAttachment();
        $attachment->setContentType('application/pdf');
        $attachment->setDisposition('inline');
        $this->assertEquals(
            'Content-Type: application/pdf'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-Disposition: inline'."\r\n",
            $attachment->toString()
            );
    }

    public function testDispositionIsAttachmentByDefault()
    {
        $attachment = $this->_createAttachment();
        $attachment->setContentType('application/pdf');
        $this->assertEquals(
            'Content-Type: application/pdf'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-Disposition: attachment'."\r\n",
            $attachment->toString()
            );
    }

    public function testFilenameIsSetInHeader()
    {
        $attachment = $this->_createAttachment();
        $attachment->setContentType('application/pdf');
        $attachment->setFilename('foo.pdf');
        $this->assertEquals(
            'Content-Type: application/pdf; name=foo.pdf'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-Disposition: attachment; filename=foo.pdf'."\r\n",
            $attachment->toString()
            );
    }

    public function testSizeIsSetInHeader()
    {
        $attachment = $this->_createAttachment();
        $attachment->setContentType('application/pdf');
        $attachment->setSize(12340);
        $this->assertEquals(
            'Content-Type: application/pdf'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-Disposition: attachment; size=12340'."\r\n",
            $attachment->toString()
            );
    }

    public function testMultipleParametersInHeader()
    {
        $attachment = $this->_createAttachment();
        $attachment->setContentType('application/pdf');
        $attachment->setFilename('foo.pdf');
        $attachment->setSize(12340);
        $this->assertEquals(
            'Content-Type: application/pdf; name=foo.pdf'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-Disposition: attachment; filename=foo.pdf; size=12340'."\r\n",
            $attachment->toString()
            );
    }

    public function testEndToEnd()
    {
        $attachment = $this->_createAttachment();
        $attachment->setContentType('application/pdf');
        $attachment->setFilename('foo.pdf');
        $attachment->setSize(12340);
        $attachment->setBody('abcd');
        $this->assertEquals(
            'Content-Type: application/pdf; name=foo.pdf'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-Disposition: attachment; filename=foo.pdf; size=12340'."\r\n".
            "\r\n".
            base64_encode('abcd'),
            $attachment->toString()
            );
    }

    // -- Private helpers

    protected function _createAttachment()
    {
        $entity = new Swift_Mime_Attachment(
            $this->_headers,
            $this->_contentEncoder,
            $this->_cache,
            $this->_grammar
            );

        return $entity;
    }
}
<?php

class Swift_Mime_ContentEncoder_Base64ContentEncoderAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    private $_samplesDir;
    private $_encoder;

    public function setUp()
    {
        $this->_samplesDir = realpath(__DIR__.'/../../../../_samples/charsets');
        $this->_encoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder();
    }

    public function testEncodingAndDecodingSamples()
    {
        $sampleFp = opendir($this->_samplesDir);
        while (false !== $encodingDir = readdir($sampleFp)) {
            if (substr($encodingDir, 0, 1) == '.') {
                continue;
            }

            $sampleDir = $this->_samplesDir.'/'.$encodingDir;

            if (is_dir($sampleDir)) {
                $fileFp = opendir($sampleDir);
                while (false !== $sampleFile = readdir($fileFp)) {
                    if (substr($sampleFile, 0, 1) == '.') {
                        continue;
                    }

                    $text = file_get_contents($sampleDir.'/'.$sampleFile);

                    $os = new Swift_ByteStream_ArrayByteStream();
                    $os->write($text);

                    $is = new Swift_ByteStream_ArrayByteStream();

                    $this->_encoder->encodeByteStream($os, $is);

                    $encoded = '';
                    while (false !== $bytes = $is->read(8192)) {
                        $encoded .= $bytes;
                    }

                    $this->assertEquals(
                        base64_decode($encoded), $text,
                        '%s: Encoded string should decode back to original string for sample '.
                        $sampleDir.'/'.$sampleFile
                        );
                }
                closedir($fileFp);
            }
        }
        closedir($sampleFp);
    }
}
<?php

class Swift_Mime_ContentEncoder_NativeQpContentEncoderAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    protected $_samplesDir;

    /**
     * @var Swift_Mime_ContentEncoder_NativeQpContentEncoder
     */
    protected $_encoder;

    public function setUp()
    {
        $this->_samplesDir = realpath(__DIR__.'/../../../../_samples/charsets');
        $this->_encoder = new Swift_Mime_ContentEncoder_NativeQpContentEncoder();
    }

    public function testEncodingAndDecodingSamples()
    {
        $sampleFp = opendir($this->_samplesDir);
        while (false !== $encodingDir = readdir($sampleFp)) {
            if (substr($encodingDir, 0, 1) == '.') {
                continue;
            }

            $sampleDir = $this->_samplesDir.'/'.$encodingDir;

            if (is_dir($sampleDir)) {
                $fileFp = opendir($sampleDir);
                while (false !== $sampleFile = readdir($fileFp)) {
                    if (substr($sampleFile, 0, 1) == '.') {
                        continue;
                    }

                    $text = file_get_contents($sampleDir.'/'.$sampleFile);

                    $os = new Swift_ByteStream_ArrayByteStream();
                    $os->write($text);

                    $is = new Swift_ByteStream_ArrayByteStream();
                    $this->_encoder->encodeByteStream($os, $is);

                    $encoded = '';
                    while (false !== $bytes = $is->read(8192)) {
                        $encoded .= $bytes;
                    }

                    $this->assertEquals(
                        quoted_printable_decode($encoded),
                        // CR and LF are converted to CRLF
                        preg_replace('~\r(?!\n)|(?<!\r)\n~', "\r\n", $text),
                        '%s: Encoded string should decode back to original string for sample '.$sampleDir.'/'.$sampleFile
                    );
                }
                closedir($fileFp);
            }
        }
        closedir($sampleFp);
    }

    public function testEncodingAndDecodingSamplesFromDiConfiguredInstance()
    {
        $encoder = $this->_createEncoderFromContainer();
        $this->assertSame('=C3=A4=C3=B6=C3=BC=C3=9F', $encoder->encodeString('äöüß'));
    }

    /**
     * @expectedException RuntimeException
     */
    public function testCharsetChangeNotImplemented()
    {
        $this->_encoder->charsetChanged('utf-8');
        $this->_encoder->charsetChanged('charset');
        $this->_encoder->encodeString('foo');
    }

    public function testGetName()
    {
        $this->assertSame('quoted-printable', $this->_encoder->getName());
    }

    private function _createEncoderFromContainer()
    {
        return Swift_DependencyContainer::getInstance()
            ->lookup('mime.nativeqpcontentencoder')
            ;
    }
}
<?php

class Swift_Mime_ContentEncoder_PlainContentEncoderAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    private $_samplesDir;
    private $_encoder;

    public function setUp()
    {
        $this->_samplesDir = realpath(__DIR__.'/../../../../_samples/charsets');
        $this->_encoder = new Swift_Mime_ContentEncoder_PlainContentEncoder('8bit');
    }

    public function testEncodingAndDecodingSamplesString()
    {
        $sampleFp = opendir($this->_samplesDir);
        while (false !== $encodingDir = readdir($sampleFp)) {
            if (substr($encodingDir, 0, 1) == '.') {
                continue;
            }

            $sampleDir = $this->_samplesDir.'/'.$encodingDir;

            if (is_dir($sampleDir)) {
                $fileFp = opendir($sampleDir);
                while (false !== $sampleFile = readdir($fileFp)) {
                    if (substr($sampleFile, 0, 1) == '.') {
                        continue;
                    }

                    $text = file_get_contents($sampleDir.'/'.$sampleFile);
                    $encodedText = $this->_encoder->encodeString($text);

                    $this->assertEquals(
                        $encodedText, $text,
                        '%s: Encoded string should be identical to original string for sample '.
                        $sampleDir.'/'.$sampleFile
                        );
                }
                closedir($fileFp);
            }
        }
        closedir($sampleFp);
    }

    public function testEncodingAndDecodingSamplesByteStream()
    {
        $sampleFp = opendir($this->_samplesDir);
        while (false !== $encodingDir = readdir($sampleFp)) {
            if (substr($encodingDir, 0, 1) == '.') {
                continue;
            }

            $sampleDir = $this->_samplesDir.'/'.$encodingDir;

            if (is_dir($sampleDir)) {
                $fileFp = opendir($sampleDir);
                while (false !== $sampleFile = readdir($fileFp)) {
                    if (substr($sampleFile, 0, 1) == '.') {
                        continue;
                    }

                    $text = file_get_contents($sampleDir.'/'.$sampleFile);

                    $os = new Swift_ByteStream_ArrayByteStream();
                    $os->write($text);

                    $is = new Swift_ByteStream_ArrayByteStream();

                    $this->_encoder->encodeByteStream($os, $is);

                    $encoded = '';
                    while (false !== $bytes = $is->read(8192)) {
                        $encoded .= $bytes;
                    }

                    $this->assertEquals(
                        $encoded, $text,
                        '%s: Encoded string should be identical to original string for sample '.
                        $sampleDir.'/'.$sampleFile
                        );
                }
                closedir($fileFp);
            }
        }
        closedir($sampleFp);
    }
}
<?php

class Swift_Mime_ContentEncoder_QpContentEncoderAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    private $_samplesDir;
    private $_factory;

    public function setUp()
    {
        $this->_samplesDir = realpath(__DIR__.'/../../../../_samples/charsets');
        $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
    }

    public function tearDown()
    {
        Swift_Preferences::getInstance()->setQPDotEscape(false);
    }

    public function testEncodingAndDecodingSamples()
    {
        $sampleFp = opendir($this->_samplesDir);
        while (false !== $encodingDir = readdir($sampleFp)) {
            if (substr($encodingDir, 0, 1) == '.') {
                continue;
            }

            $encoding = $encodingDir;
            $charStream = new Swift_CharacterStream_NgCharacterStream(
                $this->_factory, $encoding);
            $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);

            $sampleDir = $this->_samplesDir.'/'.$encodingDir;

            if (is_dir($sampleDir)) {
                $fileFp = opendir($sampleDir);
                while (false !== $sampleFile = readdir($fileFp)) {
                    if (substr($sampleFile, 0, 1) == '.') {
                        continue;
                    }

                    $text = file_get_contents($sampleDir.'/'.$sampleFile);

                    $os = new Swift_ByteStream_ArrayByteStream();
                    $os->write($text);

                    $is = new Swift_ByteStream_ArrayByteStream();
                    $encoder->encodeByteStream($os, $is);

                    $encoded = '';
                    while (false !== $bytes = $is->read(8192)) {
                        $encoded .= $bytes;
                    }

                    $this->assertEquals(
                        quoted_printable_decode($encoded), $text,
                        '%s: Encoded string should decode back to original string for sample '.
                        $sampleDir.'/'.$sampleFile
                        );
                }
                closedir($fileFp);
            }
        }
        closedir($sampleFp);
    }

    public function testEncodingAndDecodingSamplesFromDiConfiguredInstance()
    {
        $sampleFp = opendir($this->_samplesDir);
        while (false !== $encodingDir = readdir($sampleFp)) {
            if (substr($encodingDir, 0, 1) == '.') {
                continue;
            }

            $encoding = $encodingDir;
            $encoder = $this->_createEncoderFromContainer();

            $sampleDir = $this->_samplesDir.'/'.$encodingDir;

            if (is_dir($sampleDir)) {
                $fileFp = opendir($sampleDir);
                while (false !== $sampleFile = readdir($fileFp)) {
                    if (substr($sampleFile, 0, 1) == '.') {
                        continue;
                    }

                    $text = file_get_contents($sampleDir.'/'.$sampleFile);

                    $os = new Swift_ByteStream_ArrayByteStream();
                    $os->write($text);

                    $is = new Swift_ByteStream_ArrayByteStream();
                    $encoder->encodeByteStream($os, $is);

                    $encoded = '';
                    while (false !== $bytes = $is->read(8192)) {
                        $encoded .= $bytes;
                    }

                    $this->assertEquals(
                        str_replace("\r\n", "\n", quoted_printable_decode($encoded)), str_replace("\r\n", "\n", $text),
                        '%s: Encoded string should decode back to original string for sample '.
                        $sampleDir.'/'.$sampleFile
                        );
                }
                closedir($fileFp);
            }
        }
        closedir($sampleFp);
    }

    public function testEncodingLFTextWithDiConfiguredInstance()
    {
        $encoder = $this->_createEncoderFromContainer();
        $this->assertEquals("a\r\nb\r\nc", $encoder->encodeString("a\nb\nc"));
    }

    public function testEncodingCRTextWithDiConfiguredInstance()
    {
        $encoder = $this->_createEncoderFromContainer();
        $this->assertEquals("a\r\nb\r\nc", $encoder->encodeString("a\rb\rc"));
    }

    public function testEncodingLFCRTextWithDiConfiguredInstance()
    {
        $encoder = $this->_createEncoderFromContainer();
        $this->assertEquals("a\r\n\r\nb\r\n\r\nc", $encoder->encodeString("a\n\rb\n\rc"));
    }

    public function testEncodingCRLFTextWithDiConfiguredInstance()
    {
        $encoder = $this->_createEncoderFromContainer();
        $this->assertEquals("a\r\nb\r\nc", $encoder->encodeString("a\r\nb\r\nc"));
    }

    public function testEncodingDotStuffingWithDiConfiguredInstance()
    {
        // Enable DotEscaping
        Swift_Preferences::getInstance()->setQPDotEscape(true);
        $encoder = $this->_createEncoderFromContainer();
        $this->assertEquals("a=2E\r\n=2E\r\n=2Eb\r\nc", $encoder->encodeString("a.\r\n.\r\n.b\r\nc"));
        // Return to default
        Swift_Preferences::getInstance()->setQPDotEscape(false);
        $encoder = $this->_createEncoderFromContainer();
        $this->assertEquals("a.\r\n.\r\n.b\r\nc", $encoder->encodeString("a.\r\n.\r\n.b\r\nc"));
    }

    public function testDotStuffingEncodingAndDecodingSamplesFromDiConfiguredInstance()
    {
        // Enable DotEscaping
        Swift_Preferences::getInstance()->setQPDotEscape(true);
        $this->testEncodingAndDecodingSamplesFromDiConfiguredInstance();
    }

    private function _createEncoderFromContainer()
    {
        return Swift_DependencyContainer::getInstance()
            ->lookup('mime.qpcontentencoder')
            ;
    }
}
<?php

class Swift_Mime_EmbeddedFileAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    private $_contentEncoder;
    private $_cache;
    private $_grammar;
    private $_headers;

    public function setUp()
    {
        $this->_cache = new Swift_KeyCache_ArrayKeyCache(
            new Swift_KeyCache_SimpleKeyCacheInputStream()
            );
        $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
        $this->_contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder();

        $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder(
            new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')
            );
        $paramEncoder = new Swift_Encoder_Rfc2231Encoder(
            new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')
            );
        $this->_grammar = new Swift_Mime_Grammar();
        $this->_headers = new Swift_Mime_SimpleHeaderSet(
            new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $this->_grammar)
            );
    }

    public function testContentIdIsSetInHeader()
    {
        $file = $this->_createEmbeddedFile();
        $file->setContentType('application/pdf');
        $file->setId('foo@bar');
        $this->assertEquals(
            'Content-Type: application/pdf'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-ID: <foo@bar>'."\r\n".
            'Content-Disposition: inline'."\r\n",
            $file->toString()
            );
    }

    public function testDispositionIsSetInHeader()
    {
        $file = $this->_createEmbeddedFile();
        $id = $file->getId();
        $file->setContentType('application/pdf');
        $file->setDisposition('attachment');
        $this->assertEquals(
            'Content-Type: application/pdf'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-ID: <'.$id.'>'."\r\n".
            'Content-Disposition: attachment'."\r\n",
            $file->toString()
            );
    }

    public function testFilenameIsSetInHeader()
    {
        $file = $this->_createEmbeddedFile();
        $id = $file->getId();
        $file->setContentType('application/pdf');
        $file->setFilename('foo.pdf');
        $this->assertEquals(
            'Content-Type: application/pdf; name=foo.pdf'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-ID: <'.$id.'>'."\r\n".
            'Content-Disposition: inline; filename=foo.pdf'."\r\n",
            $file->toString()
            );
    }

    public function testSizeIsSetInHeader()
    {
        $file = $this->_createEmbeddedFile();
        $id = $file->getId();
        $file->setContentType('application/pdf');
        $file->setSize(12340);
        $this->assertEquals(
            'Content-Type: application/pdf'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-ID: <'.$id.'>'."\r\n".
            'Content-Disposition: inline; size=12340'."\r\n",
            $file->toString()
            );
    }

    public function testMultipleParametersInHeader()
    {
        $file = $this->_createEmbeddedFile();
        $id = $file->getId();
        $file->setContentType('application/pdf');
        $file->setFilename('foo.pdf');
        $file->setSize(12340);

        $this->assertEquals(
            'Content-Type: application/pdf; name=foo.pdf'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-ID: <'.$id.'>'."\r\n".
            'Content-Disposition: inline; filename=foo.pdf; size=12340'."\r\n",
            $file->toString()
            );
    }

    public function testEndToEnd()
    {
        $file = $this->_createEmbeddedFile();
        $id = $file->getId();
        $file->setContentType('application/pdf');
        $file->setFilename('foo.pdf');
        $file->setSize(12340);
        $file->setBody('abcd');
        $this->assertEquals(
            'Content-Type: application/pdf; name=foo.pdf'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-ID: <'.$id.'>'."\r\n".
            'Content-Disposition: inline; filename=foo.pdf; size=12340'."\r\n".
            "\r\n".
            base64_encode('abcd'),
            $file->toString()
            );
    }

    // -- Private helpers

    protected function _createEmbeddedFile()
    {
        $entity = new Swift_Mime_EmbeddedFile(
            $this->_headers,
            $this->_contentEncoder,
            $this->_cache,
            $this->_grammar
            );

        return $entity;
    }
}
<?php

class Swift_Mime_HeaderEncoder_Base64HeaderEncoderAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    private $_encoder;

    public function setUp()
    {
        $this->_encoder = new Swift_Mime_HeaderEncoder_Base64HeaderEncoder();
    }

    public function testEncodingJIS()
    {
        if (function_exists('mb_convert_encoding')) {
            // base64_encode and split cannot handle long JIS text to fold
            $subject = '長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い件名';

            $encodedWrapperLength = strlen('=?iso-2022-jp?'.$this->_encoder->getName().'??=');

            $old = mb_internal_encoding();
            mb_internal_encoding('utf-8');
            $newstring = mb_encode_mimeheader($subject, 'iso-2022-jp', 'B', "\r\n");
            mb_internal_encoding($old);

            $encoded = $this->_encoder->encodeString($subject, 0, 75 - $encodedWrapperLength, 'iso-2022-jp');
            $this->assertEquals(
                $encoded, $newstring,
                'Encoded string should decode back to original string for sample '
            );
        }
    }
}
<?php

class Swift_Mime_MimePartAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    private $_contentEncoder;
    private $_cache;
    private $_grammar;
    private $_headers;

    public function setUp()
    {
        $this->_cache = new Swift_KeyCache_ArrayKeyCache(
            new Swift_KeyCache_SimpleKeyCacheInputStream()
            );
        $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
        $this->_contentEncoder = new Swift_Mime_ContentEncoder_QpContentEncoder(
            new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'),
            new Swift_StreamFilters_ByteArrayReplacementFilter(
                array(array(0x0D, 0x0A), array(0x0D), array(0x0A)),
                array(array(0x0A), array(0x0A), array(0x0D, 0x0A))
                )
            );

        $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder(
            new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')
            );
        $paramEncoder = new Swift_Encoder_Rfc2231Encoder(
            new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')
            );
        $this->_grammar = new Swift_Mime_Grammar();
        $this->_headers = new Swift_Mime_SimpleHeaderSet(
            new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $this->_grammar)
            );
    }

    public function testCharsetIsSetInHeader()
    {
        $part = $this->_createMimePart();
        $part->setContentType('text/plain');
        $part->setCharset('utf-8');
        $part->setBody('foobar');
        $this->assertEquals(
            'Content-Type: text/plain; charset=utf-8'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'foobar',
            $part->toString()
            );
    }

    public function testFormatIsSetInHeaders()
    {
        $part = $this->_createMimePart();
        $part->setContentType('text/plain');
        $part->setFormat('flowed');
        $part->setBody('> foobar');
        $this->assertEquals(
            'Content-Type: text/plain; format=flowed'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            '> foobar',
            $part->toString()
            );
    }

    public function testDelSpIsSetInHeaders()
    {
        $part = $this->_createMimePart();
        $part->setContentType('text/plain');
        $part->setDelSp(true);
        $part->setBody('foobar');
        $this->assertEquals(
            'Content-Type: text/plain; delsp=yes'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'foobar',
            $part->toString()
            );
    }

    public function testAll3ParamsInHeaders()
    {
        $part = $this->_createMimePart();
        $part->setContentType('text/plain');
        $part->setCharset('utf-8');
        $part->setFormat('fixed');
        $part->setDelSp(true);
        $part->setBody('foobar');
        $this->assertEquals(
            'Content-Type: text/plain; charset=utf-8; format=fixed; delsp=yes'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'foobar',
            $part->toString()
            );
    }

    public function testBodyIsCanonicalized()
    {
        $part = $this->_createMimePart();
        $part->setContentType('text/plain');
        $part->setCharset('utf-8');
        $part->setBody("foobar\r\rtest\ning\r");
        $this->assertEquals(
            'Content-Type: text/plain; charset=utf-8'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            "foobar\r\n".
            "\r\n".
            "test\r\n".
            "ing\r\n",
            $part->toString()
            );
    }

    // -- Private helpers

    protected function _createMimePart()
    {
        $entity = new Swift_Mime_MimePart(
            $this->_headers,
            $this->_contentEncoder,
            $this->_cache,
            $this->_grammar
            );

        return $entity;
    }
}
<?php

class Swift_Mime_SimpleMessageAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    public function setUp()
    {
        Swift_Preferences::getInstance()->setCharset(null); //TODO: Test with the charset defined
    }

    public function testBasicHeaders()
    {
        /* -- RFC 2822, 3.6.
     */

        $message = $this->_createMessage();
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'From: '."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString(),
            '%s: Only required headers, and non-empty headers should be displayed'
            );
    }

    public function testSubjectIsDisplayedIfSet()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: '."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testDateCanBeSet()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $id = $message->getId();
        $message->setDate(1234);
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', 1234)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: '."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testMessageIdCanBeSet()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setId('foo@bar');
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <foo@bar>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: '."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testContentTypeCanBeChanged()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setContentType('text/html');
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: '."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/html'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testCharsetCanBeSet()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setContentType('text/html');
        $message->setCharset('iso-8859-1');
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: '."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/html; charset=iso-8859-1'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testFormatCanBeSet()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setFormat('flowed');
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: '."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain; format=flowed'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testEncoderCanBeSet()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setContentType('text/html');
        $message->setEncoder(
            new Swift_Mime_ContentEncoder_PlainContentEncoder('7bit')
            );
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: '."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/html'."\r\n".
            'Content-Transfer-Encoding: 7bit'."\r\n",
            $message->toString()
            );
    }

    public function testFromAddressCanBeSet()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setFrom('chris.corbyn@swiftmailer.org');
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: chris.corbyn@swiftmailer.org'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testFromAddressCanBeSetWithName()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris Corbyn'));
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testMultipleFromAddressesCanBeSet()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setFrom(array(
            'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',
            'mark@swiftmailer.org',
            ));
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>, mark@swiftmailer.org'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testReturnPathAddressCanBeSet()
    {
        $message = $this->_createMessage();
        $message->setReturnPath('chris@w3style.co.uk');
        $message->setSubject('just a test subject');
        $message->setFrom(array(
            'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Return-Path: <chris@w3style.co.uk>'."\r\n".
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testEmptyReturnPathHeaderCanBeUsed()
    {
        $message = $this->_createMessage();
        $message->setReturnPath('');
        $message->setSubject('just a test subject');
        $message->setFrom(array(
            'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Return-Path: <>'."\r\n".
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testSenderCanBeSet()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setSender('chris.corbyn@swiftmailer.org');
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Sender: chris.corbyn@swiftmailer.org'."\r\n".
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: '."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testSenderCanBeSetWithName()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setSender(array('chris.corbyn@swiftmailer.org' => 'Chris'));
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Sender: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: '."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testReplyToCanBeSet()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris'));
        $message->setReplyTo(array('chris@w3style.co.uk' => 'Myself'));
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
            'Reply-To: Myself <chris@w3style.co.uk>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testMultipleReplyAddressCanBeUsed()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris'));
        $message->setReplyTo(array(
            'chris@w3style.co.uk' => 'Myself',
            'my.other@address.com' => 'Me',
            ));
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
            'Reply-To: Myself <chris@w3style.co.uk>, Me <my.other@address.com>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testToAddressCanBeSet()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris'));
        $message->setReplyTo(array(
            'chris@w3style.co.uk' => 'Myself',
            'my.other@address.com' => 'Me',
            ));
        $message->setTo('mark@swiftmailer.org');
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
            'Reply-To: Myself <chris@w3style.co.uk>, Me <my.other@address.com>'."\r\n".
            'To: mark@swiftmailer.org'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testMultipleToAddressesCanBeSet()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris'));
        $message->setReplyTo(array(
            'chris@w3style.co.uk' => 'Myself',
            'my.other@address.com' => 'Me',
            ));
        $message->setTo(array(
            'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn',
            ));
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
            'Reply-To: Myself <chris@w3style.co.uk>, Me <my.other@address.com>'."\r\n".
            'To: mark@swiftmailer.org, Chris Corbyn <chris@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testCcAddressCanBeSet()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris'));
        $message->setReplyTo(array(
            'chris@w3style.co.uk' => 'Myself',
            'my.other@address.com' => 'Me',
            ));
        $message->setTo(array(
            'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn',
            ));
        $message->setCc('john@some-site.com');
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
            'Reply-To: Myself <chris@w3style.co.uk>, Me <my.other@address.com>'."\r\n".
            'To: mark@swiftmailer.org, Chris Corbyn <chris@swiftmailer.org>'."\r\n".
            'Cc: john@some-site.com'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testMultipleCcAddressesCanBeSet()
    {
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris'));
        $message->setReplyTo(array(
            'chris@w3style.co.uk' => 'Myself',
            'my.other@address.com' => 'Me',
            ));
        $message->setTo(array(
            'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn',
            ));
        $message->setCc(array(
            'john@some-site.com' => 'John West',
            'fred@another-site.co.uk' => 'Big Fred',
            ));
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
            'Reply-To: Myself <chris@w3style.co.uk>, Me <my.other@address.com>'."\r\n".
            'To: mark@swiftmailer.org, Chris Corbyn <chris@swiftmailer.org>'."\r\n".
            'Cc: John West <john@some-site.com>, Big Fred <fred@another-site.co.uk>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testBccAddressCanBeSet()
    {
        //Obviously Transports need to setBcc(array()) and send to each Bcc recipient
        // separately in accordance with RFC 2822/2821
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris'));
        $message->setReplyTo(array(
            'chris@w3style.co.uk' => 'Myself',
            'my.other@address.com' => 'Me',
            ));
        $message->setTo(array(
            'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn',
            ));
        $message->setCc(array(
            'john@some-site.com' => 'John West',
            'fred@another-site.co.uk' => 'Big Fred',
            ));
        $message->setBcc('x@alphabet.tld');
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
            'Reply-To: Myself <chris@w3style.co.uk>, Me <my.other@address.com>'."\r\n".
            'To: mark@swiftmailer.org, Chris Corbyn <chris@swiftmailer.org>'."\r\n".
            'Cc: John West <john@some-site.com>, Big Fred <fred@another-site.co.uk>'."\r\n".
            'Bcc: x@alphabet.tld'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testMultipleBccAddressesCanBeSet()
    {
        //Obviously Transports need to setBcc(array()) and send to each Bcc recipient
        // separately in accordance with RFC 2822/2821
        $message = $this->_createMessage();
        $message->setSubject('just a test subject');
        $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris'));
        $message->setReplyTo(array(
            'chris@w3style.co.uk' => 'Myself',
            'my.other@address.com' => 'Me',
            ));
        $message->setTo(array(
            'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn',
            ));
        $message->setCc(array(
            'john@some-site.com' => 'John West',
            'fred@another-site.co.uk' => 'Big Fred',
            ));
        $message->setBcc(array('x@alphabet.tld', 'a@alphabet.tld' => 'A'));
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris <chris.corbyn@swiftmailer.org>'."\r\n".
            'Reply-To: Myself <chris@w3style.co.uk>, Me <my.other@address.com>'."\r\n".
            'To: mark@swiftmailer.org, Chris Corbyn <chris@swiftmailer.org>'."\r\n".
            'Cc: John West <john@some-site.com>, Big Fred <fred@another-site.co.uk>'."\r\n".
            'Bcc: x@alphabet.tld, A <a@alphabet.tld>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString()
            );
    }

    public function testStringBodyIsAppended()
    {
        $message = $this->_createMessage();
        $message->setReturnPath('chris@w3style.co.uk');
        $message->setSubject('just a test subject');
        $message->setFrom(array(
            'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
        $message->setBody(
            'just a test body'."\r\n".
            'with a new line'
            );
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Return-Path: <chris@w3style.co.uk>'."\r\n".
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'just a test body'."\r\n".
            'with a new line',
            $message->toString()
            );
    }

    public function testStringBodyIsEncoded()
    {
        $message = $this->_createMessage();
        $message->setReturnPath('chris@w3style.co.uk');
        $message->setSubject('just a test subject');
        $message->setFrom(array(
            'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
        $message->setBody(
            'Just s'.pack('C*', 0xC2, 0x01, 0x01).'me multi-'."\r\n".
            'line message!'
            );
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Return-Path: <chris@w3style.co.uk>'."\r\n".
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'Just s=C2=01=01me multi-'."\r\n".
            'line message!',
            $message->toString()
            );
    }

    public function testChildrenCanBeAttached()
    {
        $message = $this->_createMessage();
        $message->setReturnPath('chris@w3style.co.uk');
        $message->setSubject('just a test subject');
        $message->setFrom(array(
            'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));

        $id = $message->getId();
        $date = $message->getDate();
        $boundary = $message->getBoundary();

        $part1 = $this->_createMimePart();
        $part1->setContentType('text/plain');
        $part1->setCharset('iso-8859-1');
        $part1->setBody('foo');

        $message->attach($part1);

        $part2 = $this->_createMimePart();
        $part2->setContentType('text/html');
        $part2->setCharset('iso-8859-1');
        $part2->setBody('test <b>foo</b>');

        $message->attach($part2);

        $this->assertEquals(
            'Return-Path: <chris@w3style.co.uk>'."\r\n".
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: multipart/alternative;'."\r\n".
            ' boundary="'.$boundary.'"'."\r\n".
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: text/plain; charset=iso-8859-1'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'foo'.
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: text/html; charset=iso-8859-1'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'test <b>foo</b>'.
            "\r\n\r\n".
            '--'.$boundary.'--'."\r\n",
            $message->toString()
            );
    }

    public function testAttachmentsBeingAttached()
    {
        $message = $this->_createMessage();
        $message->setReturnPath('chris@w3style.co.uk');
        $message->setSubject('just a test subject');
        $message->setFrom(array(
            'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));

        $id = $message->getId();
        $date = preg_quote(date('r', $message->getDate()), '~');
        $boundary = $message->getBoundary();

        $part = $this->_createMimePart();
        $part->setContentType('text/plain');
        $part->setCharset('iso-8859-1');
        $part->setBody('foo');

        $message->attach($part);

        $attachment = $this->_createAttachment();
        $attachment->setContentType('application/pdf');
        $attachment->setFilename('foo.pdf');
        $attachment->setBody('<pdf data>');

        $message->attach($attachment);

        $this->assertRegExp(
            '~^'.
            'Return-Path: <chris@w3style.co.uk>'."\r\n".
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.$date."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: multipart/mixed;'."\r\n".
            ' boundary="'.$boundary.'"'."\r\n".
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: multipart/alternative;'."\r\n".
            ' boundary="(.*?)"'."\r\n".
            "\r\n\r\n".
            '--\\1'."\r\n".
            'Content-Type: text/plain; charset=iso-8859-1'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'foo'.
            "\r\n\r\n".
            '--\\1--'."\r\n".
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: application/pdf; name=foo.pdf'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-Disposition: attachment; filename=foo.pdf'."\r\n".
            "\r\n".
            preg_quote(base64_encode('<pdf data>'), '~').
            "\r\n\r\n".
            '--'.$boundary.'--'."\r\n".
            '$~D',
            $message->toString()
            );
    }

    public function testAttachmentsAndEmbeddedFilesBeingAttached()
    {
        $message = $this->_createMessage();
        $message->setReturnPath('chris@w3style.co.uk');
        $message->setSubject('just a test subject');
        $message->setFrom(array(
            'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));

        $id = $message->getId();
        $date = preg_quote(date('r', $message->getDate()), '~');
        $boundary = $message->getBoundary();

        $part = $this->_createMimePart();
        $part->setContentType('text/plain');
        $part->setCharset('iso-8859-1');
        $part->setBody('foo');

        $message->attach($part);

        $attachment = $this->_createAttachment();
        $attachment->setContentType('application/pdf');
        $attachment->setFilename('foo.pdf');
        $attachment->setBody('<pdf data>');

        $message->attach($attachment);

        $file = $this->_createEmbeddedFile();
        $file->setContentType('image/jpeg');
        $file->setFilename('myimage.jpg');
        $file->setBody('<image data>');

        $message->attach($file);

        $cid = $file->getId();

        $this->assertRegExp(
            '~^'.
            'Return-Path: <chris@w3style.co.uk>'."\r\n".
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.$date."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: multipart/mixed;'."\r\n".
            ' boundary="'.$boundary.'"'."\r\n".
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: multipart/alternative;'."\r\n".
            ' boundary="(.*?)"'."\r\n".
            "\r\n\r\n".
            '--\\1'."\r\n".
            'Content-Type: text/plain; charset=iso-8859-1'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'foo'.

            "\r\n\r\n".
            '--\\1'."\r\n".
            'Content-Type: multipart/related;'."\r\n".
            ' boundary="(.*?)"'."\r\n".
            "\r\n\r\n".
            '--\\2'."\r\n".
            'Content-Type: image/jpeg; name=myimage.jpg'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-ID: <'.$cid.'>'."\r\n".
            'Content-Disposition: inline; filename=myimage.jpg'."\r\n".
            "\r\n".
            preg_quote(base64_encode('<image data>'), '~').
            "\r\n\r\n".
            '--\\2--'."\r\n".
            "\r\n\r\n".
            '--\\1--'."\r\n".
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: application/pdf; name=foo.pdf'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-Disposition: attachment; filename=foo.pdf'."\r\n".
            "\r\n".
            preg_quote(base64_encode('<pdf data>'), '~').
            "\r\n\r\n".
            '--'.$boundary.'--'."\r\n".
            '$~D',
            $message->toString()
            );
    }

    public function testComplexEmbeddingOfContent()
    {
        $message = $this->_createMessage();
        $message->setReturnPath('chris@w3style.co.uk');
        $message->setSubject('just a test subject');
        $message->setFrom(array(
            'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));

        $id = $message->getId();
        $date = preg_quote(date('r', $message->getDate()), '~');
        $boundary = $message->getBoundary();

        $attachment = $this->_createAttachment();
        $attachment->setContentType('application/pdf');
        $attachment->setFilename('foo.pdf');
        $attachment->setBody('<pdf data>');

        $message->attach($attachment);

        $file = $this->_createEmbeddedFile();
        $file->setContentType('image/jpeg');
        $file->setFilename('myimage.jpg');
        $file->setBody('<image data>');

        $part = $this->_createMimePart();
        $part->setContentType('text/html');
        $part->setCharset('iso-8859-1');
        $part->setBody('foo <img src="'.$message->embed($file).'" />');

        $message->attach($part);

        $cid = $file->getId();

        $this->assertRegExp(
            '~^'.
            'Return-Path: <chris@w3style.co.uk>'."\r\n".
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.$date."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: multipart/mixed;'."\r\n".
            ' boundary="'.$boundary.'"'."\r\n".
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: multipart/related;'."\r\n".
            ' boundary="(.*?)"'."\r\n".
            "\r\n\r\n".
            '--\\1'."\r\n".
            'Content-Type: text/html; charset=iso-8859-1'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'foo <img src=3D"cid:'.$cid.'" />'.//=3D is just = in QP
            "\r\n\r\n".
            '--\\1'."\r\n".
            'Content-Type: image/jpeg; name=myimage.jpg'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-ID: <'.$cid.'>'."\r\n".
            'Content-Disposition: inline; filename=myimage.jpg'."\r\n".
            "\r\n".
            preg_quote(base64_encode('<image data>'), '~').
            "\r\n\r\n".
            '--\\1--'."\r\n".
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: application/pdf; name=foo.pdf'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-Disposition: attachment; filename=foo.pdf'."\r\n".
            "\r\n".
            preg_quote(base64_encode('<pdf data>'), '~').
            "\r\n\r\n".
            '--'.$boundary.'--'."\r\n".
            '$~D',
            $message->toString()
            );
    }

    public function testAttachingAndDetachingContent()
    {
        $message = $this->_createMessage();
        $message->setReturnPath('chris@w3style.co.uk');
        $message->setSubject('just a test subject');
        $message->setFrom(array(
            'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));

        $id = $message->getId();
        $date = preg_quote(date('r', $message->getDate()), '~');
        $boundary = $message->getBoundary();

        $part = $this->_createMimePart();
        $part->setContentType('text/plain');
        $part->setCharset('iso-8859-1');
        $part->setBody('foo');

        $message->attach($part);

        $attachment = $this->_createAttachment();
        $attachment->setContentType('application/pdf');
        $attachment->setFilename('foo.pdf');
        $attachment->setBody('<pdf data>');

        $message->attach($attachment);

        $file = $this->_createEmbeddedFile();
        $file->setContentType('image/jpeg');
        $file->setFilename('myimage.jpg');
        $file->setBody('<image data>');

        $message->attach($file);

        $cid = $file->getId();

        $message->detach($attachment);

        $this->assertRegExp(
            '~^'.
            'Return-Path: <chris@w3style.co.uk>'."\r\n".
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.$date."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: multipart/alternative;'."\r\n".
            ' boundary="'.$boundary.'"'."\r\n".
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: text/plain; charset=iso-8859-1'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'foo'.
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: multipart/related;'."\r\n".
            ' boundary="(.*?)"'."\r\n".
            "\r\n\r\n".
            '--\\1'."\r\n".
            'Content-Type: image/jpeg; name=myimage.jpg'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-ID: <'.$cid.'>'."\r\n".
            'Content-Disposition: inline; filename=myimage.jpg'."\r\n".
            "\r\n".
            preg_quote(base64_encode('<image data>'), '~').
            "\r\n\r\n".
            '--\\1--'."\r\n".
            "\r\n\r\n".
            '--'.$boundary.'--'."\r\n".
            '$~D',
            $message->toString(),
            '%s: Attachment should have been detached'
            );
    }

    public function testBoundaryDoesNotAppearAfterAllPartsAreDetached()
    {
        $message = $this->_createMessage();
        $message->setReturnPath('chris@w3style.co.uk');
        $message->setSubject('just a test subject');
        $message->setFrom(array(
            'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));

        $id = $message->getId();
        $date = $message->getDate();
        $boundary = $message->getBoundary();

        $part1 = $this->_createMimePart();
        $part1->setContentType('text/plain');
        $part1->setCharset('iso-8859-1');
        $part1->setBody('foo');

        $message->attach($part1);

        $part2 = $this->_createMimePart();
        $part2->setContentType('text/html');
        $part2->setCharset('iso-8859-1');
        $part2->setBody('test <b>foo</b>');

        $message->attach($part2);

        $message->detach($part1);
        $message->detach($part2);

        $this->assertEquals(
            'Return-Path: <chris@w3style.co.uk>'."\r\n".
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n",
            $message->toString(),
            '%s: Message should be restored to orignal state after parts are detached'
            );
    }

    public function testCharsetFormatOrDelSpAreNotShownWhenBoundaryIsSet()
    {
        $message = $this->_createMessage();
        $message->setReturnPath('chris@w3style.co.uk');
        $message->setSubject('just a test subject');
        $message->setFrom(array(
            'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
        $message->setCharset('utf-8');
        $message->setFormat('flowed');
        $message->setDelSp(true);

        $id = $message->getId();
        $date = $message->getDate();
        $boundary = $message->getBoundary();

        $part1 = $this->_createMimePart();
        $part1->setContentType('text/plain');
        $part1->setCharset('iso-8859-1');
        $part1->setBody('foo');

        $message->attach($part1);

        $part2 = $this->_createMimePart();
        $part2->setContentType('text/html');
        $part2->setCharset('iso-8859-1');
        $part2->setBody('test <b>foo</b>');

        $message->attach($part2);

        $this->assertEquals(
            'Return-Path: <chris@w3style.co.uk>'."\r\n".
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: multipart/alternative;'."\r\n".
            ' boundary="'.$boundary.'"'."\r\n".
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: text/plain; charset=iso-8859-1'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'foo'.
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: text/html; charset=iso-8859-1'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'test <b>foo</b>'.
            "\r\n\r\n".
            '--'.$boundary.'--'."\r\n",
            $message->toString()
            );
    }

    public function testBodyCanBeSetWithAttachments()
    {
        $message = $this->_createMessage();
        $message->setReturnPath('chris@w3style.co.uk');
        $message->setSubject('just a test subject');
        $message->setFrom(array(
            'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
        $message->setContentType('text/html');
        $message->setCharset('iso-8859-1');
        $message->setBody('foo');

        $id = $message->getId();
        $date = date('r', $message->getDate());
        $boundary = $message->getBoundary();

        $attachment = $this->_createAttachment();
        $attachment->setContentType('application/pdf');
        $attachment->setFilename('foo.pdf');
        $attachment->setBody('<pdf data>');

        $message->attach($attachment);

        $this->assertEquals(
            'Return-Path: <chris@w3style.co.uk>'."\r\n".
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.$date."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: multipart/mixed;'."\r\n".
            ' boundary="'.$boundary.'"'."\r\n".
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: text/html; charset=iso-8859-1'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'foo'.
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: application/pdf; name=foo.pdf'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-Disposition: attachment; filename=foo.pdf'."\r\n".
            "\r\n".
            base64_encode('<pdf data>').
            "\r\n\r\n".
            '--'.$boundary.'--'."\r\n",
            $message->toString()
            );
    }

    public function testHtmlPartAlwaysAppearsLast()
    {
        $message = $this->_createMessage();
        $message->setReturnPath('chris@w3style.co.uk');
        $message->setSubject('just a test subject');
        $message->setFrom(array(
            'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));

        $id = $message->getId();
        $date = date('r', $message->getDate());
        $boundary = $message->getBoundary();

        $part1 = $this->_createMimePart();
        $part1->setContentType('text/html');
        $part1->setBody('foo');

        $part2 = $this->_createMimePart();
        $part2->setContentType('text/plain');
        $part2->setBody('bar');

        $message->attach($part1);
        $message->attach($part2);

        $this->assertEquals(
            'Return-Path: <chris@w3style.co.uk>'."\r\n".
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.$date."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: multipart/alternative;'."\r\n".
            ' boundary="'.$boundary.'"'."\r\n".
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'bar'.
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: text/html'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'foo'.
            "\r\n\r\n".
            '--'.$boundary.'--'."\r\n",
            $message->toString()
            );
    }

    public function testBodyBecomesPartIfOtherPartsAttached()
    {
        $message = $this->_createMessage();
        $message->setReturnPath('chris@w3style.co.uk');
        $message->setSubject('just a test subject');
        $message->setFrom(array(
            'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
        $message->setContentType('text/html');
        $message->setBody('foo');

        $id = $message->getId();
        $date = date('r', $message->getDate());
        $boundary = $message->getBoundary();

        $part2 = $this->_createMimePart();
        $part2->setContentType('text/plain');
        $part2->setBody('bar');

        $message->attach($part2);

        $this->assertEquals(
            'Return-Path: <chris@w3style.co.uk>'."\r\n".
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.$date."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: multipart/alternative;'."\r\n".
            ' boundary="'.$boundary.'"'."\r\n".
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'bar'.
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: text/html'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'foo'.
            "\r\n\r\n".
            '--'.$boundary.'--'."\r\n",
            $message->toString()
            );
    }

    public function testBodyIsCanonicalized()
    {
        $message = $this->_createMessage();
        $message->setReturnPath('chris@w3style.co.uk');
        $message->setSubject('just a test subject');
        $message->setFrom(array(
            'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', ));
        $message->setBody(
            'just a test body'."\n".
            'with a new line'
            );
        $id = $message->getId();
        $date = $message->getDate();
        $this->assertEquals(
            'Return-Path: <chris@w3style.co.uk>'."\r\n".
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.date('r', $date)."\r\n".
            'Subject: just a test subject'."\r\n".
            'From: Chris Corbyn <chris.corbyn@swiftmailer.org>'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: text/plain'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'just a test body'."\r\n".
            'with a new line',
            $message->toString()
            );
    }

    // -- Private helpers

    protected function _createMessage()
    {
        return new Swift_Message();
    }

    protected function _createMimePart()
    {
        return new Swift_MimePart();
    }

    protected function _createAttachment()
    {
        return new Swift_Attachment();
    }

    protected function _createEmbeddedFile()
    {
        return new Swift_EmbeddedFile();
    }
}
<?php

require_once 'swift_required.php';
require_once __DIR__.'/Mime/MimePartAcceptanceTest.php';

class Swift_MimePartAcceptanceTest extends Swift_Mime_MimePartAcceptanceTest
{
    protected function _createMimePart()
    {
        Swift_DependencyContainer::getInstance()
            ->register('properties.charset')->asValue(null);

        return Swift_MimePart::newInstance();
    }
}
<?php

abstract class Swift_Transport_StreamBuffer_AbstractStreamBufferAcceptanceTest extends \PHPUnit_Framework_TestCase
{
    protected $_buffer;

    abstract protected function _initializeBuffer();

    public function setUp()
    {
        if (true == getenv('TRAVIS')) {
            $this->markTestSkipped(
                'Will fail on travis-ci if not skipped due to travis blocking '.
                'socket mailing tcp connections.'
             );
        }

        $this->_buffer = new Swift_Transport_StreamBuffer(
            $this->getMockBuilder('Swift_ReplacementFilterFactory')->getMock()
        );
    }

    public function testReadLine()
    {
        $this->_initializeBuffer();

        $line = $this->_buffer->readLine(0);
        $this->assertRegExp('/^[0-9]{3}.*?\r\n$/D', $line);
        $seq = $this->_buffer->write("QUIT\r\n");
        $this->assertTrue((bool) $seq);
        $line = $this->_buffer->readLine($seq);
        $this->assertRegExp('/^[0-9]{3}.*?\r\n$/D', $line);
        $this->_buffer->terminate();
    }

    public function testWrite()
    {
        $this->_initializeBuffer();

        $line = $this->_buffer->readLine(0);
        $this->assertRegExp('/^[0-9]{3}.*?\r\n$/D', $line);

        $seq = $this->_buffer->write("HELO foo\r\n");
        $this->assertTrue((bool) $seq);
        $line = $this->_buffer->readLine($seq);
        $this->assertRegExp('/^[0-9]{3}.*?\r\n$/D', $line);

        $seq = $this->_buffer->write("QUIT\r\n");
        $this->assertTrue((bool) $seq);
        $line = $this->_buffer->readLine($seq);
        $this->assertRegExp('/^[0-9]{3}.*?\r\n$/D', $line);
        $this->_buffer->terminate();
    }

    public function testBindingOtherStreamsMirrorsWriteOperations()
    {
        $this->_initializeBuffer();

        $is1 = $this->_createMockInputStream();
        $is2 = $this->_createMockInputStream();

        $is1->expects($this->at(0))
            ->method('write')
            ->with('x');
        $is1->expects($this->at(1))
            ->method('write')
            ->with('y');
        $is2->expects($this->at(0))
            ->method('write')
            ->with('x');
        $is2->expects($this->at(1))
            ->method('write')
            ->with('y');

        $this->_buffer->bind($is1);
        $this->_buffer->bind($is2);

        $this->_buffer->write('x');
        $this->_buffer->write('y');
    }

    public function testBindingOtherStreamsMirrorsFlushOperations()
    {
        $this->_initializeBuffer();

        $is1 = $this->_createMockInputStream();
        $is2 = $this->_createMockInputStream();

        $is1->expects($this->once())
            ->method('flushBuffers');
        $is2->expects($this->once())
            ->method('flushBuffers');

        $this->_buffer->bind($is1);
        $this->_buffer->bind($is2);

        $this->_buffer->flushBuffers();
    }

    public function testUnbindingStreamPreventsFurtherWrites()
    {
        $this->_initializeBuffer();

        $is1 = $this->_createMockInputStream();
        $is2 = $this->_createMockInputStream();

        $is1->expects($this->at(0))
            ->method('write')
            ->with('x');
        $is1->expects($this->at(1))
            ->method('write')
            ->with('y');
        $is2->expects($this->once())
            ->method('write')
            ->with('x');

        $this->_buffer->bind($is1);
        $this->_buffer->bind($is2);

        $this->_buffer->write('x');

        $this->_buffer->unbind($is2);

        $this->_buffer->write('y');
    }

    // -- Creation Methods

    private function _createMockInputStream()
    {
        return $this->getMockBuilder('Swift_InputByteStream')->getMock();
    }
}
<?php

require_once __DIR__.'/AbstractStreamBufferAcceptanceTest.php';

class Swift_Transport_StreamBuffer_BasicSocketAcceptanceTest extends Swift_Transport_StreamBuffer_AbstractStreamBufferAcceptanceTest
{
    public function setUp()
    {
        if (!defined('SWIFT_SMTP_HOST')) {
            $this->markTestSkipped(
                'Cannot run test without an SMTP host to connect to (define '.
                'SWIFT_SMTP_HOST in tests/acceptance.conf.php if you wish to run this test)'
             );
        }
        parent::setUp();
    }

    protected function _initializeBuffer()
    {
        $parts = explode(':', SWIFT_SMTP_HOST);
        $host = $parts[0];
        $port = isset($parts[1]) ? $parts[1] : 25;

        $this->_buffer->initialize(array(
            'type' => Swift_Transport_IoBuffer::TYPE_SOCKET,
            'host' => $host,
            'port' => $port,
            'protocol' => 'tcp',
            'blocking' => 1,
            'timeout' => 15,
        ));
    }
}
<?php

require_once __DIR__.'/AbstractStreamBufferAcceptanceTest.php';

class Swift_Transport_StreamBuffer_ProcessAcceptanceTest extends Swift_Transport_StreamBuffer_AbstractStreamBufferAcceptanceTest
{
    public function setUp()
    {
        if (!defined('SWIFT_SENDMAIL_PATH')) {
            $this->markTestSkipped(
                'Cannot run test without a path to sendmail (define '.
                'SWIFT_SENDMAIL_PATH in tests/acceptance.conf.php if you wish to run this test)'
             );
        }

        parent::setUp();
    }

    protected function _initializeBuffer()
    {
        $this->_buffer->initialize(array(
            'type' => Swift_Transport_IoBuffer::TYPE_PROCESS,
            'command' => SWIFT_SENDMAIL_PATH.' -bs',
        ));
    }
}
<?php

class Swift_Transport_StreamBuffer_SocketTimeoutTest extends \PHPUnit_Framework_TestCase
{
    protected $_buffer;

    protected $_randomHighPort;

    protected $_server;

    public function setUp()
    {
        if (!defined('SWIFT_SMTP_HOST')) {
            $this->markTestSkipped(
                'Cannot run test without an SMTP host to connect to (define '.
                'SWIFT_SMTP_HOST in tests/acceptance.conf.php if you wish to run this test)'
             );
        }

        $serverStarted = false;
        for ($i = 0; $i < 5; ++$i) {
            $this->_randomHighPort = rand(50000, 65000);
            $this->_server = stream_socket_server('tcp://127.0.0.1:'.$this->_randomHighPort);
            if ($this->_server) {
                $serverStarted = true;
            }
        }

        $this->_buffer = new Swift_Transport_StreamBuffer(
            $this->getMockBuilder('Swift_ReplacementFilterFactory')->getMock()
        );
    }

    protected function _initializeBuffer()
    {
        $host = '127.0.0.1';
        $port = $this->_randomHighPort;

        $this->_buffer->initialize(array(
            'type' => Swift_Transport_IoBuffer::TYPE_SOCKET,
            'host' => $host,
            'port' => $port,
            'protocol' => 'tcp',
            'blocking' => 1,
            'timeout' => 1,
        ));
    }

    public function testTimeoutException()
    {
        $this->_initializeBuffer();
        $e = null;
        try {
            $line = $this->_buffer->readLine(0);
        } catch (Exception $e) {
        }
        $this->assertInstanceof('Swift_IoException', $e, 'IO Exception Not Thrown On Connection Timeout');
        $this->assertRegExp('/Connection to .* Timed Out/', $e->getMessage());
    }

    public function tearDown()
    {
        if ($this->_server) {
            stream_socket_shutdown($this->_server, STREAM_SHUT_RDWR);
        }
    }
}
<?php

require_once __DIR__.'/AbstractStreamBufferAcceptanceTest.php';

class Swift_Transport_StreamBuffer_SslSocketAcceptanceTest extends Swift_Transport_StreamBuffer_AbstractStreamBufferAcceptanceTest
{
    public function setUp()
    {
        $streams = stream_get_transports();
        if (!in_array('ssl', $streams)) {
            $this->markTestSkipped(
                'SSL is not configured for your system.  It is not possible to run this test'
             );
        }
        if (!defined('SWIFT_SSL_HOST')) {
            $this->markTestSkipped(
                'Cannot run test without an SSL enabled SMTP host to connect to (define '.
                'SWIFT_SSL_HOST in tests/acceptance.conf.php if you wish to run this test)'
             );
        }

        parent::setUp();
    }

    protected function _initializeBuffer()
    {
        $parts = explode(':', SWIFT_SSL_HOST);
        $host = $parts[0];
        $port = isset($parts[1]) ? $parts[1] : 25;

        $this->_buffer->initialize(array(
            'type' => Swift_Transport_IoBuffer::TYPE_SOCKET,
            'host' => $host,
            'port' => $port,
            'protocol' => 'ssl',
            'blocking' => 1,
            'timeout' => 15,
        ));
    }
}
<?php

require_once __DIR__.'/AbstractStreamBufferAcceptanceTest.php';

class Swift_Transport_StreamBuffer_TlsSocketAcceptanceTest extends Swift_Transport_StreamBuffer_AbstractStreamBufferAcceptanceTest
{
    public function setUp()
    {
        $streams = stream_get_transports();
        if (!in_array('tls', $streams)) {
            $this->markTestSkipped(
                'TLS is not configured for your system.  It is not possible to run this test'
             );
        }
        if (!defined('SWIFT_TLS_HOST')) {
            $this->markTestSkipped(
                'Cannot run test without a TLS enabled SMTP host to connect to (define '.
                'SWIFT_TLS_HOST in tests/acceptance.conf.php if you wish to run this test)'
             );
        }
        parent::setUp();
    }

    protected function _initializeBuffer()
    {
        $parts = explode(':', SWIFT_TLS_HOST);
        $host = $parts[0];
        $port = isset($parts[1]) ? $parts[1] : 25;

        $this->_buffer->initialize(array(
            'type' => Swift_Transport_IoBuffer::TYPE_SOCKET,
            'host' => $host,
            'port' => $port,
            'protocol' => 'tls',
            'blocking' => 1,
            'timeout' => 15,
            ));
    }
}
<?php

require_once dirname(__DIR__).'/vendor/autoload.php';

// Disable garbage collector to prevent segfaults
gc_disable();

set_include_path(get_include_path().PATH_SEPARATOR.dirname(__DIR__).'/lib');

Mockery::getConfiguration()->allowMockingNonExistentMethods(true);

if (is_file(__DIR__.'/acceptance.conf.php')) {
    require_once __DIR__.'/acceptance.conf.php';
}
if (is_file(__DIR__.'/smoke.conf.php')) {
    require_once __DIR__.'/smoke.conf.php';
}
require_once __DIR__.'/StreamCollector.php';
require_once __DIR__.'/IdenticalBinaryConstraint.php';
require_once __DIR__.'/SwiftMailerTestCase.php';
require_once __DIR__.'/SwiftMailerSmokeTestCase.php';
<?php

class Swift_Bug111Test extends \PHPUnit_Framework_TestCase
{
    public function testUnstructuredHeaderSlashesShouldNotBeEscaped()
    {
        $complicated_header = array(
            'to' => array(
                'email1@example.com',
                'email2@example.com',
                'email3@example.com',
                'email4@example.com',
                'email5@example.com',
            ),
            'sub' => array(
                '-name-' => array(
                    'email1',
                    '"email2"',
                    'email3\\',
                    'email4',
                    'email5',
                ),
                '-url-' => array(
                    'http://google.com',
                    'http://yahoo.com',
                    'http://hotmail.com',
                    'http://aol.com',
                    'http://facebook.com',
                ),
            ),
        );
        $json = json_encode($complicated_header);

        $message = new Swift_Message();
        $headers = $message->getHeaders();
        $headers->addTextHeader('X-SMTPAPI', $json);
        $header = $headers->get('X-SMTPAPI');

        $this->assertEquals('Swift_Mime_Headers_UnstructuredHeader', get_class($header));
        $this->assertEquals($json, $header->getFieldBody());
    }
}
<?php

class Swift_Bug118Test extends \PHPUnit_Framework_TestCase
{
    private $_message;

    public function setUp()
    {
        $this->_message = new Swift_Message();
    }

    public function testCallingGenerateIdChangesTheMessageId()
    {
        $currentId = $this->_message->getId();
        $this->_message->generateId();
        $newId = $this->_message->getId();

        $this->assertNotEquals($currentId, $newId);
    }
}
<?php

class Swift_Bug206Test extends \PHPUnit_Framework_TestCase
{
    private $_factory;

    public function setUp()
    {
        $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
        $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder(
            new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')
        );
        $paramEncoder = new Swift_Encoder_Rfc2231Encoder(
            new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')
        );
        $grammar = new Swift_Mime_Grammar();
        $this->_factory = new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $grammar);
    }

    public function testMailboxHeaderEncoding()
    {
        $this->_testHeaderIsFullyEncoded('email@example.org', 'Family Name, Name', ' "Family Name, Name" <email@example.org>');
        $this->_testHeaderIsFullyEncoded('email@example.org', 'Family Namé, Name', ' Family =?utf-8?Q?Nam=C3=A9=2C?= Name');
        $this->_testHeaderIsFullyEncoded('email@example.org', 'Family Namé , Name', ' Family =?utf-8?Q?Nam=C3=A9_=2C?= Name');
        $this->_testHeaderIsFullyEncoded('email@example.org', 'Family Namé ;Name', ' Family =?utf-8?Q?Nam=C3=A9_=3BName?= ');
    }

    private function _testHeaderIsFullyEncoded($email, $name, $expected)
    {
        $mailboxHeader = $this->_factory->createMailboxHeader('To', array(
            $email => $name,
        ));

        $headerBody = substr($mailboxHeader->toString(), 3, strlen($expected));

        $this->assertEquals($expected, $headerBody);
    }
}
<?php

class Swift_Bug274Test extends \PHPUnit_Framework_TestCase
{
    public function testEmptyFileNameAsAttachment()
    {
        $message = new Swift_Message();
        $this->setExpectedException('Swift_IoException', 'The path cannot be empty');
        $message->attach(Swift_Attachment::fromPath(''));
    }

    public function testNonEmptyFileNameAsAttachment()
    {
        $message = new Swift_Message();
        try {
            $message->attach(Swift_Attachment::fromPath(__FILE__));
        } catch (Exception $e) {
            $this->fail('Path should not be empty');
        }
    }
}
<?php

class Swift_Bug34Test extends \PHPUnit_Framework_TestCase
{
    public function setUp()
    {
        Swift_Preferences::getInstance()->setCharset('utf-8');
    }

    public function testEmbeddedFilesWithMultipartDataCreateMultipartRelatedContentAsAnAlternative()
    {
        $message = Swift_Message::newInstance();
        $message->setCharset('utf-8');
        $message->setSubject('test subject');
        $message->addPart('plain part', 'text/plain');

        $image = Swift_Image::newInstance('<image data>', 'image.gif', 'image/gif');
        $cid = $message->embed($image);

        $message->setBody('<img src="'.$cid.'" />', 'text/html');

        $message->setTo(array('user@domain.tld' => 'User'));

        $message->setFrom(array('other@domain.tld' => 'Other'));
        $message->setSender(array('other@domain.tld' => 'Other'));

        $id = $message->getId();
        $date = preg_quote(date('r', $message->getDate()), '~');
        $boundary = $message->getBoundary();
        $cidVal = $image->getId();

        $this->assertRegExp(
        '~^'.
        'Sender: Other <other@domain.tld>'."\r\n".
        'Message-ID: <'.$id.'>'."\r\n".
        'Date: '.$date."\r\n".
        'Subject: test subject'."\r\n".
        'From: Other <other@domain.tld>'."\r\n".
        'To: User <user@domain.tld>'."\r\n".
        'MIME-Version: 1.0'."\r\n".
        'Content-Type: multipart/alternative;'."\r\n".
        ' boundary="'.$boundary.'"'."\r\n".
        "\r\n\r\n".
        '--'.$boundary."\r\n".
        'Content-Type: text/plain; charset=utf-8'."\r\n".
        'Content-Transfer-Encoding: quoted-printable'."\r\n".
        "\r\n".
        'plain part'.
        "\r\n\r\n".
        '--'.$boundary."\r\n".
        'Content-Type: multipart/related;'."\r\n".
        ' boundary="(.*?)"'."\r\n".
        "\r\n\r\n".
        '--\\1'."\r\n".
        'Content-Type: text/html; charset=utf-8'."\r\n".
        'Content-Transfer-Encoding: quoted-printable'."\r\n".
        "\r\n".
        '<img.*?/>'.
        "\r\n\r\n".
        '--\\1'."\r\n".
        'Content-Type: image/gif; name=image.gif'."\r\n".
        'Content-Transfer-Encoding: base64'."\r\n".
        'Content-ID: <'.$cidVal.'>'."\r\n".
        'Content-Disposition: inline; filename=image.gif'."\r\n".
        "\r\n".
        preg_quote(base64_encode('<image data>'), '~').
        "\r\n\r\n".
        '--\\1--'."\r\n".
        "\r\n\r\n".
        '--'.$boundary.'--'."\r\n".
        '$~D',
        $message->toString()
        );
    }
}
<?php

class Swift_Bug35Test extends \PHPUnit_Framework_TestCase
{
    public function setUp()
    {
        Swift_Preferences::getInstance()->setCharset('utf-8');
    }

    public function testHTMLPartAppearsLastEvenWhenAttachmentsAdded()
    {
        $message = Swift_Message::newInstance();
        $message->setCharset('utf-8');
        $message->setSubject('test subject');
        $message->addPart('plain part', 'text/plain');

        $attachment = Swift_Attachment::newInstance('<data>', 'image.gif', 'image/gif');
        $message->attach($attachment);

        $message->setBody('HTML part', 'text/html');

        $message->setTo(array('user@domain.tld' => 'User'));

        $message->setFrom(array('other@domain.tld' => 'Other'));
        $message->setSender(array('other@domain.tld' => 'Other'));

        $id = $message->getId();
        $date = preg_quote(date('r', $message->getDate()), '~');
        $boundary = $message->getBoundary();

        $this->assertRegExp(
        '~^'.
        'Sender: Other <other@domain.tld>'."\r\n".
        'Message-ID: <'.$id.'>'."\r\n".
        'Date: '.$date."\r\n".
        'Subject: test subject'."\r\n".
        'From: Other <other@domain.tld>'."\r\n".
        'To: User <user@domain.tld>'."\r\n".
        'MIME-Version: 1.0'."\r\n".
        'Content-Type: multipart/mixed;'."\r\n".
        ' boundary="'.$boundary.'"'."\r\n".
        "\r\n\r\n".
        '--'.$boundary."\r\n".
        'Content-Type: multipart/alternative;'."\r\n".
        ' boundary="(.*?)"'."\r\n".
        "\r\n\r\n".
        '--\\1'."\r\n".
        'Content-Type: text/plain; charset=utf-8'."\r\n".
        'Content-Transfer-Encoding: quoted-printable'."\r\n".
        "\r\n".
        'plain part'.
        "\r\n\r\n".
        '--\\1'."\r\n".
        'Content-Type: text/html; charset=utf-8'."\r\n".
        'Content-Transfer-Encoding: quoted-printable'."\r\n".
        "\r\n".
        'HTML part'.
        "\r\n\r\n".
        '--\\1--'."\r\n".
        "\r\n\r\n".
        '--'.$boundary."\r\n".
        'Content-Type: image/gif; name=image.gif'."\r\n".
        'Content-Transfer-Encoding: base64'."\r\n".
        'Content-Disposition: attachment; filename=image.gif'."\r\n".
        "\r\n".
        preg_quote(base64_encode('<data>'), '~').
        "\r\n\r\n".
        '--'.$boundary.'--'."\r\n".
        '$~D',
        $message->toString()
        );
    }
}
<?php

class Swift_Bug38Test extends \PHPUnit_Framework_TestCase
{
    private $_attFile;
    private $_attFileName;
    private $_attFileType;

    public function setUp()
    {
        $this->_attFileName = 'data.txt';
        $this->_attFileType = 'text/plain';
        $this->_attFile = __DIR__.'/../../_samples/files/data.txt';
        Swift_Preferences::getInstance()->setCharset('utf-8');
    }

    public function testWritingMessageToByteStreamProducesCorrectStructure()
    {
        $message = new Swift_Message();
        $message->setSubject('test subject');
        $message->setTo('user@domain.tld');
        $message->setCc('other@domain.tld');
        $message->setFrom('user@domain.tld');

        $image = new Swift_Image('<data>', 'image.gif', 'image/gif');

        $cid = $message->embed($image);
        $message->setBody('HTML part', 'text/html');

        $id = $message->getId();
        $date = preg_quote(date('r', $message->getDate()), '~');
        $boundary = $message->getBoundary();
        $imgId = $image->getId();

        $stream = new Swift_ByteStream_ArrayByteStream();

        $message->toByteStream($stream);

        $this->assertPatternInStream(
            '~^'.
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.$date."\r\n".
            'Subject: test subject'."\r\n".
            'From: user@domain.tld'."\r\n".
            'To: user@domain.tld'."\r\n".
            'Cc: other@domain.tld'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: multipart/related;'."\r\n".
            ' boundary="'.$boundary.'"'."\r\n".
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: text/html; charset=utf-8'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'HTML part'.
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: image/gif; name=image.gif'."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-ID: <'.preg_quote($imgId, '~').'>'."\r\n".
            'Content-Disposition: inline; filename=image.gif'."\r\n".
            "\r\n".
            preg_quote(base64_encode('<data>'), '~').
            "\r\n\r\n".
            '--'.$boundary.'--'."\r\n".
            '$~D',
            $stream
        );
    }

    public function testWritingMessageToByteStreamTwiceProducesCorrectStructure()
    {
        $message = new Swift_Message();
        $message->setSubject('test subject');
        $message->setTo('user@domain.tld');
        $message->setCc('other@domain.tld');
        $message->setFrom('user@domain.tld');

        $image = new Swift_Image('<data>', 'image.gif', 'image/gif');

        $cid = $message->embed($image);
        $message->setBody('HTML part', 'text/html');

        $id = $message->getId();
        $date = preg_quote(date('r', $message->getDate()), '~');
        $boundary = $message->getBoundary();
        $imgId = $image->getId();

        $pattern = '~^'.
        'Message-ID: <'.$id.'>'."\r\n".
        'Date: '.$date."\r\n".
        'Subject: test subject'."\r\n".
        'From: user@domain.tld'."\r\n".
        'To: user@domain.tld'."\r\n".
        'Cc: other@domain.tld'."\r\n".
        'MIME-Version: 1.0'."\r\n".
        'Content-Type: multipart/related;'."\r\n".
        ' boundary="'.$boundary.'"'."\r\n".
        "\r\n\r\n".
        '--'.$boundary."\r\n".
        'Content-Type: text/html; charset=utf-8'."\r\n".
        'Content-Transfer-Encoding: quoted-printable'."\r\n".
        "\r\n".
        'HTML part'.
        "\r\n\r\n".
        '--'.$boundary."\r\n".
        'Content-Type: image/gif; name=image.gif'."\r\n".
        'Content-Transfer-Encoding: base64'."\r\n".
        'Content-ID: <'.preg_quote($imgId, '~').'>'."\r\n".
        'Content-Disposition: inline; filename=image.gif'."\r\n".
        "\r\n".
        preg_quote(base64_encode('<data>'), '~').
        "\r\n\r\n".
        '--'.$boundary.'--'."\r\n".
        '$~D'
        ;

        $streamA = new Swift_ByteStream_ArrayByteStream();
        $streamB = new Swift_ByteStream_ArrayByteStream();

        $message->toByteStream($streamA);
        $message->toByteStream($streamB);

        $this->assertPatternInStream($pattern, $streamA);
        $this->assertPatternInStream($pattern, $streamB);
    }

    public function testWritingMessageToByteStreamTwiceUsingAFileAttachment()
    {
        $message = new Swift_Message();
        $message->setSubject('test subject');
        $message->setTo('user@domain.tld');
        $message->setCc('other@domain.tld');
        $message->setFrom('user@domain.tld');

        $attachment = Swift_Attachment::fromPath($this->_attFile);

        $message->attach($attachment);

        $message->setBody('HTML part', 'text/html');

        $id = $message->getId();
        $date = preg_quote(date('r', $message->getDate()), '~');
        $boundary = $message->getBoundary();

        $streamA = new Swift_ByteStream_ArrayByteStream();
        $streamB = new Swift_ByteStream_ArrayByteStream();

        $pattern = '~^'.
            'Message-ID: <'.$id.'>'."\r\n".
            'Date: '.$date."\r\n".
            'Subject: test subject'."\r\n".
            'From: user@domain.tld'."\r\n".
            'To: user@domain.tld'."\r\n".
            'Cc: other@domain.tld'."\r\n".
            'MIME-Version: 1.0'."\r\n".
            'Content-Type: multipart/mixed;'."\r\n".
            ' boundary="'.$boundary.'"'."\r\n".
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: text/html; charset=utf-8'."\r\n".
            'Content-Transfer-Encoding: quoted-printable'."\r\n".
            "\r\n".
            'HTML part'.
            "\r\n\r\n".
            '--'.$boundary."\r\n".
            'Content-Type: '.$this->_attFileType.'; name='.$this->_attFileName."\r\n".
            'Content-Transfer-Encoding: base64'."\r\n".
            'Content-Disposition: attachment; filename='.$this->_attFileName."\r\n".
            "\r\n".
            preg_quote(base64_encode(file_get_contents($this->_attFile)), '~').
            "\r\n\r\n".
            '--'.$boundary.'--'."\r\n".
            '$~D'
            ;

        $message->toByteStream($streamA);
        $message->toByteStream($streamB);

        $this->assertPatternInStream($pattern, $streamA);
        $this->assertPatternInStream($pattern, $streamB);
    }

    // -- Helpers

    public function assertPatternInStream($pattern, $stream, $message = '%s')
    {
        $string = '';
        while (false !== $bytes = $stream->read(8192)) {
            $string .= $bytes;
        }
        $this->assertRegExp($pattern, $string, $message);
    }
}
<?php

use Mockery as m;

class Swift_Bug518Test extends \PHPUnit_Framework_TestCase
{
    public function testIfEmailChangesAfterQueued()
    {
        $failedRecipients = 'value';
        $message = new Swift_Message();
        $message->setTo('foo@bar.com');

        $that = $this;
        $messageValidation = function ($m) use ($that) {
            //the getTo should return the same value as we put in
            $that->assertEquals('foo@bar.com', key($m->getTo()), 'The message has changed after it was put to the memory queue');

            return true;
        };

        $transport = m::mock('Swift_Transport');
        $transport->shouldReceive('isStarted')->andReturn(true);
        $transport->shouldReceive('send')
            ->with(m::on($messageValidation), $failedRecipients)
            ->andReturn(1);

        $memorySpool = new Swift_MemorySpool();
        $memorySpool->queueMessage($message);

        /*
         * The message is queued in memory.
         * Lets change the message
         */
        $message->setTo('other@value.com');

        $memorySpool->flushQueue($transport, $failedRecipients);
    }
}
<?php

class Swift_Bug51Test extends \SwiftMailerTestCase
{
    private $_attachmentFile;
    private $_outputFile;

    public function setUp()
    {
        if (!defined('SWIFT_TMP_DIR') || !is_writable(SWIFT_TMP_DIR)) {
            $this->markTestSkipped(
                'Cannot run test without a writable directory to use ('.
                'define SWIFT_TMP_DIR in tests/config.php if you wish to run this test)'
             );
        }

        $this->_attachmentFile = SWIFT_TMP_DIR.'/attach.rand.bin';
        file_put_contents($this->_attachmentFile, '');

        $this->_outputFile = SWIFT_TMP_DIR.'/attach.out.bin';
        file_put_contents($this->_outputFile, '');
    }

    public function tearDown()
    {
        unlink($this->_attachmentFile);
        unlink($this->_outputFile);
    }

    public function testAttachmentsDoNotGetTruncatedUsingToByteStream()
    {
        //Run 100 times with 10KB attachments
        for ($i = 0; $i < 10; ++$i) {
            $message = $this->_createMessageWithRandomAttachment(
                10000, $this->_attachmentFile
            );

            file_put_contents($this->_outputFile, '');
            $message->toByteStream(
                new Swift_ByteStream_FileByteStream($this->_outputFile, true)
            );

            $emailSource = file_get_contents($this->_outputFile);

            $this->assertAttachmentFromSourceMatches(
                file_get_contents($this->_attachmentFile),
                $emailSource
            );
        }
    }

    public function testAttachmentsDoNotGetTruncatedUsingToString()
    {
        //Run 100 times with 10KB attachments
        for ($i = 0; $i < 10; ++$i) {
            $message = $this->_createMessageWithRandomAttachment(
                10000, $this->_attachmentFile
            );

            $emailSource = $message->toString();

            $this->assertAttachmentFromSourceMatches(
                file_get_contents($this->_attachmentFile),
                $emailSource
            );
        }
    }

    // -- Custom Assertions

    public function assertAttachmentFromSourceMatches($attachmentData, $source)
    {
        $encHeader = 'Content-Transfer-Encoding: base64';
        $base64declaration = strpos($source, $encHeader);

        $attachmentDataStart = strpos($source, "\r\n\r\n", $base64declaration);
        $attachmentDataEnd = strpos($source, "\r\n--", $attachmentDataStart);

        if (false === $attachmentDataEnd) {
            $attachmentBase64 = trim(substr($source, $attachmentDataStart));
        } else {
            $attachmentBase64 = trim(substr(
                $source, $attachmentDataStart,
                $attachmentDataEnd - $attachmentDataStart
            ));
        }

        $this->assertIdenticalBinary($attachmentData, base64_decode($attachmentBase64));
    }

    // -- Creation Methods

    private function _fillFileWithRandomBytes($byteCount, $file)
    {
        // I was going to use dd with if=/dev/random but this way seems more
        // cross platform even if a hella expensive!!

        file_put_contents($file, '');
        $fp = fopen($file, 'wb');
        for ($i = 0; $i < $byteCount; ++$i) {
            $byteVal = rand(0, 255);
            fwrite($fp, pack('i', $byteVal));
        }
        fclose($fp);
    }

    private function _createMessageWithRandomAttachment($size, $attachmentPath)
    {
        $this->_fillFileWithRandomBytes($size, $attachmentPath);

        $message = Swift_Message::newInstance()
            ->setSubject('test')
            ->setBody('test')
            ->setFrom('a@b.c')
            ->setTo('d@e.f')
            ->attach(Swift_Attachment::fromPath($attachmentPath))
            ;

        return $message;
    }
}
<?php

use Mockery as m;

class Swift_Bug534Test extends \PHPUnit_Framework_TestCase
{
    public function testEmbeddedImagesAreEmbedded()
    {
        $message = Swift_Message::newInstance()
            ->setFrom('from@example.com')
            ->setTo('to@example.com')
            ->setSubject('test')
        ;
        $cid = $message->embed(Swift_Image::fromPath(__DIR__.'/../../_samples/files/swiftmailer.png'));
        $message->setBody('<img src="'.$cid.'" />', 'text/html');

        $that = $this;
        $messageValidation = function (Swift_Mime_Message $message) use ($that) {
            preg_match('/cid:(.*)"/', $message->toString(), $matches);
            $cid = $matches[1];
            preg_match('/Content-ID: <(.*)>/', $message->toString(), $matches);
            $contentId = $matches[1];
            $that->assertEquals($cid, $contentId, 'cid in body and mime part Content-ID differ');

            return true;
        };

        $failedRecipients = array();

        $transport = m::mock('Swift_Transport');
        $transport->shouldReceive('isStarted')->andReturn(true);
        $transport->shouldReceive('send')->with(m::on($messageValidation), $failedRecipients)->andReturn(1);

        $memorySpool = new Swift_MemorySpool();
        $memorySpool->queueMessage($message);
        $memorySpool->flushQueue($transport, $failedRecipients);
    }
}
<?php

class Swift_Bug650Test extends \PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider encodingDataProvider
     *
     * @param string $name
     * @param string $expectedEncodedName
     */
    public function testMailboxHeaderEncoding($name, $expectedEncodedName)
    {
        $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
        $charStream = new Swift_CharacterStream_NgCharacterStream($factory, 'utf-8');
        $encoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder($charStream);
        $header = new Swift_Mime_Headers_MailboxHeader('To', $encoder, new Swift_Mime_Grammar());
        $header->setCharset('utf-8');

        $header->setNameAddresses(array(
            'test@example.com' => $name,
        ));

        $this->assertSame('To: '.$expectedEncodedName." <test@example.com>\r\n", $header->toString());
    }

    public function encodingDataProvider()
    {
        return array(
            array('this is " a test ö', 'this is =?utf-8?Q?=22?= a test =?utf-8?Q?=C3=B6?='),
            array(': this is a test ö', '=?utf-8?Q?=3A?= this is a test =?utf-8?Q?=C3=B6?='),
            array('( test ö', '=?utf-8?Q?=28?= test =?utf-8?Q?=C3=B6?='),
            array('[ test ö', '=?utf-8?Q?=5B?= test =?utf-8?Q?=C3=B6?='),
            array('@ test ö)', '=?utf-8?Q?=40?= test =?utf-8?Q?=C3=B6=29?='),
        );
    }
}
<?php

class Swift_Bug71Test extends \PHPUnit_Framework_TestCase
{
    private $_message;

    public function setUp()
    {
        $this->_message = new Swift_Message('test');
    }

    public function testCallingToStringAfterSettingNewBodyReflectsChanges()
    {
        $this->_message->setBody('BODY1');
        $this->assertRegExp('/BODY1/', $this->_message->toString());

        $this->_message->setBody('BODY2');
        $this->assertRegExp('/BODY2/', $this->_message->toString());
    }
}
<?php

class Swift_Bug76Test extends \PHPUnit_Framework_TestCase
{
    private $_inputFile;
    private $_outputFile;
    private $_encoder;

    public function setUp()
    {
        if (!defined('SWIFT_TMP_DIR') || !is_writable(SWIFT_TMP_DIR)) {
            $this->markTestSkipped(
                'Cannot run test without a writable directory to use ('.
                'define SWIFT_TMP_DIR in tests/config.php if you wish to run this test)'
             );
        }

        $this->_inputFile = SWIFT_TMP_DIR.'/in.bin';
        file_put_contents($this->_inputFile, '');

        $this->_outputFile = SWIFT_TMP_DIR.'/out.bin';
        file_put_contents($this->_outputFile, '');

        $this->_encoder = $this->_createEncoder();
    }

    public function tearDown()
    {
        unlink($this->_inputFile);
        unlink($this->_outputFile);
    }

    public function testBase64EncodedLineLengthNeverExceeds76CharactersEvenIfArgsDo()
    {
        $this->_fillFileWithRandomBytes(1000, $this->_inputFile);

        $os = $this->_createStream($this->_inputFile);
        $is = $this->_createStream($this->_outputFile);

        $this->_encoder->encodeByteStream($os, $is, 0, 80); //Exceeds 76

        $this->assertMaxLineLength(76, $this->_outputFile,
            '%s: Line length should not exceed 76 characters'
        );
    }

    // -- Custom Assertions

    public function assertMaxLineLength($length, $filePath, $message = '%s')
    {
        $lines = file($filePath);
        foreach ($lines as $line) {
            $this->assertTrue((strlen(trim($line)) <= 76), $message);
        }
    }

    // -- Creation Methods

    private function _fillFileWithRandomBytes($byteCount, $file)
    {
        // I was going to use dd with if=/dev/random but this way seems more
        // cross platform even if a hella expensive!!

        file_put_contents($file, '');
        $fp = fopen($file, 'wb');
        for ($i = 0; $i < $byteCount; ++$i) {
            $byteVal = rand(0, 255);
            fwrite($fp, pack('i', $byteVal));
        }
        fclose($fp);
    }

    private function _createEncoder()
    {
        return new Swift_Mime_ContentEncoder_Base64ContentEncoder();
    }

    private function _createStream($file)
    {
        return new Swift_ByteStream_FileByteStream($file, true);
    }
}
<?php


class Swift_FileByteStreamConsecutiveReadCalls extends \PHPUnit_Framework_TestCase
{
    /**
     * @test
     * @expectedException \Swift_IoException
     */
    public function shouldThrowExceptionOnConsecutiveRead()
    {
        $fbs = new \Swift_ByteStream_FileByteStream('does not exist');
        try {
            $fbs->read(100);
        } catch (\Swift_IoException $exc) {
            $fbs->read(100);
        }
    }
}
<?php

class MimeEntityFixture implements Swift_Mime_MimeEntity
{
    private $level;
    private $string;
    private $contentType;

    public function __construct($level = null, $string = '', $contentType = null)
    {
        $this->level = $level;
        $this->string = $string;
        $this->contentType = $contentType;
    }

    public function getNestingLevel()
    {
        return $this->level;
    }

    public function toString()
    {
        return $this->string;
    }

    public function getContentType()
    {
        return $this->contentType;
    }

    // These methods are here to account for the implemented interfaces
    public function getId()
    {
    }
    public function getHeaders()
    {
    }
    public function getBody()
    {
    }
    public function setBody($body, $contentType = null)
    {
    }
    public function toByteStream(Swift_InputByteStream $is)
    {
    }
    public function charsetChanged($charset)
    {
    }
    public function encoderChanged(Swift_Mime_ContentEncoder $encoder)
    {
    }
    public function getChildren()
    {
    }
    public function setChildren(array $children)
    {
    }
}
<?php

/**
 * A binary safe string comparison.
 *
 * @author Chris Corbyn
 */
class IdenticalBinaryConstraint extends \PHPUnit_Framework_Constraint
{
    protected $value;

    public function __construct($value)
    {
        $this->value = $value;
    }

    /**
     * Evaluates the constraint for parameter $other. Returns TRUE if the
     * constraint is met, FALSE otherwise.
     *
     * @param mixed $other Value or object to evaluate.
     *
     * @return bool
     */
    public function matches($other)
    {
        $aHex = $this->asHexString($this->value);
        $bHex = $this->asHexString($other);

        return $aHex === $bHex;
    }

    /**
     * Returns a string representation of the constraint.
     *
     * @return string
     */
    public function toString()
    {
        return 'indentical binary';
    }

    /**
     * Get the given string of bytes as a stirng of Hexadecimal sequences.
     *
     * @param string $binary
     *
     * @return string
     */
    private function asHexString($binary)
    {
        $hex = '';

        $bytes = unpack('H*', $binary);

        foreach ($bytes as &$byte) {
            $byte = strtoupper($byte);
        }

        return implode('', $bytes);
    }
}
<?php

/**
 * @group smoke
 */
class Swift_Smoke_AttachmentSmokeTest extends SwiftMailerSmokeTestCase
{
    private $_attFile;

    public function setUp()
    {
        parent::setup(); // For skip
        $this->_attFile = __DIR__.'/../../../_samples/files/textfile.zip';
    }

    public function testAttachmentSending()
    {
        $mailer = $this->_getMailer();
        $message = Swift_Message::newInstance()
            ->setSubject('[Swift Mailer] AttachmentSmokeTest')
            ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Swift Mailer'))
            ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS)
            ->setBody('This message should contain an attached ZIP file (named "textfile.zip").'.PHP_EOL.
                'When unzipped, the archive should produce a text file which reads:'.PHP_EOL.
                '"This is part of a Swift Mailer v4 smoke test."'
                )
            ->attach(Swift_Attachment::fromPath($this->_attFile))
            ;
        $this->assertEquals(1, $mailer->send($message),
            '%s: The smoke test should send a single message'
            );
    }
}
<?php

/**
 * @group smoke
 */
class Swift_Smoke_BasicSmokeTest extends SwiftMailerSmokeTestCase
{
    public function testBasicSending()
    {
        $mailer = $this->_getMailer();
        $message = Swift_Message::newInstance()
            ->setSubject('[Swift Mailer] BasicSmokeTest')
            ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Swift Mailer'))
            ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS)
            ->setBody('One, two, three, four, five...'.PHP_EOL.
                'six, seven, eight...'
                )
            ;
        $this->assertEquals(1, $mailer->send($message),
            '%s: The smoke test should send a single message'
            );
    }
}
<?php

/**
 * @group smoke
 */
class Swift_Smoke_HtmlWithAttachmentSmokeTest extends SwiftMailerSmokeTestCase
{
    private $_attFile;

    public function setUp()
    {
        $this->_attFile = __DIR__.'/../../../_samples/files/textfile.zip';
    }

    public function testAttachmentSending()
    {
        $mailer = $this->_getMailer();
        $message = Swift_Message::newInstance('[Swift Mailer] HtmlWithAttachmentSmokeTest')
            ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Swift Mailer'))
            ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS)
            ->attach(Swift_Attachment::fromPath($this->_attFile))
            ->setBody('<p>This HTML-formatted message should contain an attached ZIP file (named "textfile.zip").'.PHP_EOL.
                'When unzipped, the archive should produce a text file which reads:</p>'.PHP_EOL.
                '<p><q>This is part of a Swift Mailer v4 smoke test.</q></p>', 'text/html'
            )
            ;
        $this->assertEquals(1, $mailer->send($message),
            '%s: The smoke test should send a single message'
            );
    }
}
<?php

/**
 * @group smoke
 */
class Swift_Smoke_InternationalSmokeTest extends SwiftMailerSmokeTestCase
{
    private $_attFile;

    public function setUp()
    {
        parent::setup(); // For skip
        $this->_attFile = __DIR__.'/../../../_samples/files/textfile.zip';
    }

    public function testAttachmentSending()
    {
        $mailer = $this->_getMailer();
        $message = Swift_Message::newInstance()
            ->setCharset('utf-8')
            ->setSubject('[Swift Mailer] InternationalSmokeTest (διεθνής)')
            ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Χριστοφορου (Swift Mailer)'))
            ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS)
            ->setBody('This message should contain an attached ZIP file (named "κείμενο, εδάφιο, θέμα.zip").'.PHP_EOL.
                'When unzipped, the archive should produce a text file which reads:'.PHP_EOL.
                '"This is part of a Swift Mailer v4 smoke test."'.PHP_EOL.
                PHP_EOL.
                'Following is some arbitrary Greek text:'.PHP_EOL.
                'Δεν βρέθηκαν λέξεις.'
                )
            ->attach(Swift_Attachment::fromPath($this->_attFile)
                ->setContentType('application/zip')
                ->setFilename('κείμενο, εδάφιο, θέμα.zip')
                )
            ;
        $this->assertEquals(1, $mailer->send($message),
            '%s: The smoke test should send a single message'
            );
    }
}
<?php

class Swift_StreamCollector
{
    public $content = '';

    public function __invoke($arg)
    {
        $this->content .= $arg;
    }
}
<?php

/**
 * Base test for smoke tests.
 *
 * @author Rouven Weßling
 */
class SwiftMailerSmokeTestCase extends SwiftMailerTestCase
{
    public function setUp()
    {
        if (!defined('SWIFT_SMOKE_TRANSPORT_TYPE')) {
            $this->markTestSkipped(
                'Smoke tests are skipped if tests/smoke.conf.php is not edited'
             );
        }
    }

    protected function _getMailer()
    {
        switch (SWIFT_SMOKE_TRANSPORT_TYPE) {
            case 'smtp':
                $transport = Swift_DependencyContainer::getInstance()->lookup('transport.smtp')
                    ->setHost(SWIFT_SMOKE_SMTP_HOST)
                    ->setPort(SWIFT_SMOKE_SMTP_PORT)
                    ->setUsername(SWIFT_SMOKE_SMTP_USER)
                    ->setPassword(SWIFT_SMOKE_SMTP_PASS)
                    ->setEncryption(SWIFT_SMOKE_SMTP_ENCRYPTION)
                    ;
                break;
            case 'sendmail':
                $transport = Swift_DependencyContainer::getInstance()->lookup('transport.sendmail')
                    ->setCommand(SWIFT_SMOKE_SENDMAIL_COMMAND)
                    ;
                break;
            case 'mail':
            case 'nativemail':
                $transport = Swift_DependencyContainer::getInstance()->lookup('transport.mail');
                break;
            default:
                throw new Exception('Undefined transport ['.SWIFT_SMOKE_TRANSPORT_TYPE.']');
        }

        return new Swift_Mailer($transport);
    }
}
<?php

/**
 * A base test case with some custom expectations.
 *
 * @author Rouven Weßling
 */
class SwiftMailerTestCase extends \PHPUnit_Framework_TestCase
{
    public static function regExp($pattern)
    {
        if (!is_string($pattern)) {
            throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
        }

        return new PHPUnit_Framework_Constraint_PCREMatch($pattern);
    }

    public function assertIdenticalBinary($expected, $actual, $message = '')
    {
        $constraint = new IdenticalBinaryConstraint($expected);
        self::assertThat($actual, $constraint, $message);
    }

    protected function tearDown()
    {
        \Mockery::close();
    }

    protected function getMockery($class)
    {
        return \Mockery::mock($class);
    }
}
<?php

class Swift_ByteStream_ArrayByteStreamTest extends \PHPUnit_Framework_TestCase
{
    public function testReadingSingleBytesFromBaseInput()
    {
        $input = array('a', 'b', 'c');
        $bs = $this->_createArrayStream($input);
        $output = array();
        while (false !== $bytes = $bs->read(1)) {
            $output[] = $bytes;
        }
        $this->assertEquals($input, $output,
            '%s: Bytes read from stream should be the same as bytes in constructor'
            );
    }

    public function testReadingMultipleBytesFromBaseInput()
    {
        $input = array('a', 'b', 'c', 'd');
        $bs = $this->_createArrayStream($input);
        $output = array();
        while (false !== $bytes = $bs->read(2)) {
            $output[] = $bytes;
        }
        $this->assertEquals(array('ab', 'cd'), $output,
            '%s: Bytes read from stream should be in pairs'
            );
    }

    public function testReadingOddOffsetOnLastByte()
    {
        $input = array('a', 'b', 'c', 'd', 'e');
        $bs = $this->_createArrayStream($input);
        $output = array();
        while (false !== $bytes = $bs->read(2)) {
            $output[] = $bytes;
        }
        $this->assertEquals(array('ab', 'cd', 'e'), $output,
            '%s: Bytes read from stream should be in pairs except final read'
            );
    }

    public function testSettingPointerPartway()
    {
        $input = array('a', 'b', 'c');
        $bs = $this->_createArrayStream($input);
        $bs->setReadPointer(1);
        $this->assertEquals('b', $bs->read(1),
            '%s: Byte should be second byte since pointer as at offset 1'
            );
    }

    public function testResettingPointerAfterExhaustion()
    {
        $input = array('a', 'b', 'c');
        $bs = $this->_createArrayStream($input);
        while (false !== $bs->read(1));

        $bs->setReadPointer(0);
        $this->assertEquals('a', $bs->read(1),
            '%s: Byte should be first byte since pointer as at offset 0'
            );
    }

    public function testPointerNeverSetsBelowZero()
    {
        $input = array('a', 'b', 'c');
        $bs = $this->_createArrayStream($input);

        $bs->setReadPointer(-1);
        $this->assertEquals('a', $bs->read(1),
            '%s: Byte should be first byte since pointer should be at offset 0'
            );
    }

    public function testPointerNeverSetsAboveStackSize()
    {
        $input = array('a', 'b', 'c');
        $bs = $this->_createArrayStream($input);

        $bs->setReadPointer(3);
        $this->assertFalse($bs->read(1),
            '%s: Stream should be at end and thus return false'
            );
    }

    public function testBytesCanBeWrittenToStream()
    {
        $input = array('a', 'b', 'c');
        $bs = $this->_createArrayStream($input);

        $bs->write('de');

        $output = array();
        while (false !== $bytes = $bs->read(1)) {
            $output[] = $bytes;
        }
        $this->assertEquals(array('a', 'b', 'c', 'd', 'e'), $output,
            '%s: Bytes read from stream should be from initial stack + written'
            );
    }

    public function testContentsCanBeFlushed()
    {
        $input = array('a', 'b', 'c');
        $bs = $this->_createArrayStream($input);

        $bs->flushBuffers();

        $this->assertFalse($bs->read(1),
            '%s: Contents have been flushed so read() should return false'
            );
    }

    public function testConstructorCanTakeStringArgument()
    {
        $bs = $this->_createArrayStream('abc');
        $output = array();
        while (false !== $bytes = $bs->read(1)) {
            $output[] = $bytes;
        }
        $this->assertEquals(array('a', 'b', 'c'), $output,
            '%s: Bytes read from stream should be the same as bytes in constructor'
            );
    }

    public function testBindingOtherStreamsMirrorsWriteOperations()
    {
        $bs = $this->_createArrayStream('');
        $is1 = $this->getMockBuilder('Swift_InputByteStream')->getMock();
        $is2 = $this->getMockBuilder('Swift_InputByteStream')->getMock();

        $is1->expects($this->at(0))
            ->method('write')
            ->with('x');
        $is1->expects($this->at(1))
            ->method('write')
            ->with('y');
        $is2->expects($this->at(0))
            ->method('write')
            ->with('x');
        $is2->expects($this->at(1))
            ->method('write')
            ->with('y');

        $bs->bind($is1);
        $bs->bind($is2);

        $bs->write('x');
        $bs->write('y');
    }

    public function testBindingOtherStreamsMirrorsFlushOperations()
    {
        $bs = $this->_createArrayStream('');
        $is1 = $this->getMockBuilder('Swift_InputByteStream')->getMock();
        $is2 = $this->getMockBuilder('Swift_InputByteStream')->getMock();

        $is1->expects($this->once())
            ->method('flushBuffers');
        $is2->expects($this->once())
            ->method('flushBuffers');

        $bs->bind($is1);
        $bs->bind($is2);

        $bs->flushBuffers();
    }

    public function testUnbindingStreamPreventsFurtherWrites()
    {
        $bs = $this->_createArrayStream('');
        $is1 = $this->getMockBuilder('Swift_InputByteStream')->getMock();
        $is2 = $this->getMockBuilder('Swift_InputByteStream')->getMock();

        $is1->expects($this->at(0))
            ->method('write')
            ->with('x');
        $is1->expects($this->at(1))
            ->method('write')
            ->with('y');
        $is2->expects($this->once())
            ->method('write')
            ->with('x');

        $bs->bind($is1);
        $bs->bind($is2);

        $bs->write('x');

        $bs->unbind($is2);

        $bs->write('y');
    }

    // -- Creation Methods

    private function _createArrayStream($input)
    {
        return new Swift_ByteStream_ArrayByteStream($input);
    }
}
<?php

class Swift_CharacterReader_GenericFixedWidthReaderTest extends \PHPUnit_Framework_TestCase
{
    public function testInitialByteSizeMatchesWidth()
    {
        $reader = new Swift_CharacterReader_GenericFixedWidthReader(1);
        $this->assertSame(1, $reader->getInitialByteSize());

        $reader = new Swift_CharacterReader_GenericFixedWidthReader(4);
        $this->assertSame(4, $reader->getInitialByteSize());
    }

    public function testValidationValueIsBasedOnOctetCount()
    {
        $reader = new Swift_CharacterReader_GenericFixedWidthReader(4);

        $this->assertSame(
            1, $reader->validateByteSequence(array(0x01, 0x02, 0x03), 3)
            ); //3 octets

        $this->assertSame(
            2, $reader->validateByteSequence(array(0x01, 0x0A), 2)
            ); //2 octets

        $this->assertSame(
            3, $reader->validateByteSequence(array(0xFE), 1)
            ); //1 octet

        $this->assertSame(
            0, $reader->validateByteSequence(array(0xFE, 0x03, 0x67, 0x9A), 4)
            ); //All 4 octets
    }

    public function testValidationFailsIfTooManyOctets()
    {
        $reader = new Swift_CharacterReader_GenericFixedWidthReader(6);

        $this->assertSame(-1, $reader->validateByteSequence(
            array(0xFE, 0x03, 0x67, 0x9A, 0x10, 0x09, 0x85), 7
            )); //7 octets
    }
}
<?php

class Swift_CharacterReader_UsAsciiReaderTest extends \PHPUnit_Framework_TestCase
{
    /*

    for ($c = '', $size = 1; false !== $bytes = $os->read($size); ) {
        $c .= $bytes;
        $size = $v->validateCharacter($c);
        if (-1 == $size) {
            throw new Exception( ... invalid char .. );
        } elseif (0 == $size) {
            return $c; //next character in $os
        }
    }

    */

    private $_reader;

    public function setUp()
    {
        $this->_reader = new Swift_CharacterReader_UsAsciiReader();
    }

    public function testAllValidAsciiCharactersReturnZero()
    {
        for ($ordinal = 0x00; $ordinal <= 0x7F; ++$ordinal) {
            $this->assertSame(
                0, $this->_reader->validateByteSequence(array($ordinal), 1)
                );
        }
    }

    public function testMultipleBytesAreInvalid()
    {
        for ($ordinal = 0x00; $ordinal <= 0x7F; $ordinal += 2) {
            $this->assertSame(
                -1, $this->_reader->validateByteSequence(array($ordinal, $ordinal + 1), 2)
                );
        }
    }

    public function testBytesAboveAsciiRangeAreInvalid()
    {
        for ($ordinal = 0x80; $ordinal <= 0xFF; ++$ordinal) {
            $this->assertSame(
                -1, $this->_reader->validateByteSequence(array($ordinal), 1)
                );
        }
    }
}
<?php

class Swift_CharacterReader_Utf8ReaderTest extends \PHPUnit_Framework_TestCase
{
    private $_reader;

    public function setUp()
    {
        $this->_reader = new Swift_CharacterReader_Utf8Reader();
    }

    public function testLeading7BitOctetCausesReturnZero()
    {
        for ($ordinal = 0x00; $ordinal <= 0x7F; ++$ordinal) {
            $this->assertSame(
                0, $this->_reader->validateByteSequence(array($ordinal), 1)
                );
        }
    }

    public function testLeadingByteOf2OctetCharCausesReturn1()
    {
        for ($octet = 0xC0; $octet <= 0xDF; ++$octet) {
            $this->assertSame(
                1, $this->_reader->validateByteSequence(array($octet), 1)
                );
        }
    }

    public function testLeadingByteOf3OctetCharCausesReturn2()
    {
        for ($octet = 0xE0; $octet <= 0xEF; ++$octet) {
            $this->assertSame(
                2, $this->_reader->validateByteSequence(array($octet), 1)
                );
        }
    }

    public function testLeadingByteOf4OctetCharCausesReturn3()
    {
        for ($octet = 0xF0; $octet <= 0xF7; ++$octet) {
            $this->assertSame(
                3, $this->_reader->validateByteSequence(array($octet), 1)
                );
        }
    }

    public function testLeadingByteOf5OctetCharCausesReturn4()
    {
        for ($octet = 0xF8; $octet <= 0xFB; ++$octet) {
            $this->assertSame(
                4, $this->_reader->validateByteSequence(array($octet), 1)
                );
        }
    }

    public function testLeadingByteOf6OctetCharCausesReturn5()
    {
        for ($octet = 0xFC; $octet <= 0xFD; ++$octet) {
            $this->assertSame(
                5, $this->_reader->validateByteSequence(array($octet), 1)
                );
        }
    }
}
<?php

class Swift_CharacterStream_ArrayCharacterStreamTest extends \SwiftMailerTestCase
{
    public function testValidatorAlgorithmOnImportString()
    {
        $reader = $this->_getReader();
        $factory = $this->_getFactory($reader);

        $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');

        $reader->shouldReceive('getInitialByteSize')
               ->zeroOrMoreTimes()
               ->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);

        $stream->importString(pack('C*',
            0xD0, 0x94,
            0xD0, 0xB6,
            0xD0, 0xBE,
            0xD1, 0x8D,
            0xD0, 0xBB,
            0xD0, 0xB0
            )
        );
    }

    public function testCharactersWrittenUseValidator()
    {
        $reader = $this->_getReader();
        $factory = $this->_getFactory($reader);

        $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');

        $reader->shouldReceive('getInitialByteSize')
               ->zeroOrMoreTimes()
               ->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);

        $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));

        $stream->write(pack('C*',
            0xD0, 0xBB,
            0xD1, 0x8E,
            0xD0, 0xB1,
            0xD1, 0x8B,
            0xD1, 0x85
            )
        );
    }

    public function testReadCharactersAreInTact()
    {
        $reader = $this->_getReader();
        $factory = $this->_getFactory($reader);

        $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');

        $reader->shouldReceive('getInitialByteSize')
               ->zeroOrMoreTimes()
               ->andReturn(1);
        //String
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        //Stream
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);

        $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));

        $stream->write(pack('C*',
            0xD0, 0xBB,
            0xD1, 0x8E,
            0xD0, 0xB1,
            0xD1, 0x8B,
            0xD1, 0x85
            )
        );

        $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1));
        $this->assertIdenticalBinary(
            pack('C*', 0xD0, 0xB6, 0xD0, 0xBE), $stream->read(2)
            );
        $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBB), $stream->read(1));
        $this->assertIdenticalBinary(
            pack('C*', 0xD1, 0x8E, 0xD0, 0xB1, 0xD1, 0x8B), $stream->read(3)
            );
        $this->assertIdenticalBinary(pack('C*', 0xD1, 0x85), $stream->read(1));

        $this->assertFalse($stream->read(1));
    }

    public function testCharactersCanBeReadAsByteArrays()
    {
        $reader = $this->_getReader();
        $factory = $this->_getFactory($reader);

        $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');

        $reader->shouldReceive('getInitialByteSize')
               ->zeroOrMoreTimes()
               ->andReturn(1);
        //String
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        //Stream
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1);

        $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));

        $stream->write(pack('C*',
            0xD0, 0xBB,
            0xD1, 0x8E,
            0xD0, 0xB1,
            0xD1, 0x8B,
            0xD1, 0x85
            )
        );

        $this->assertEquals(array(0xD0, 0x94), $stream->readBytes(1));
        $this->assertEquals(array(0xD0, 0xB6, 0xD0, 0xBE), $stream->readBytes(2));
        $this->assertEquals(array(0xD0, 0xBB), $stream->readBytes(1));
        $this->assertEquals(
            array(0xD1, 0x8E, 0xD0, 0xB1, 0xD1, 0x8B), $stream->readBytes(3)
            );
        $this->assertEquals(array(0xD1, 0x85), $stream->readBytes(1));

        $this->assertFalse($stream->readBytes(1));
    }

    public function testRequestingLargeCharCountPastEndOfStream()
    {
        $reader = $this->_getReader();
        $factory = $this->_getFactory($reader);

        $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');

        $reader->shouldReceive('getInitialByteSize')
               ->zeroOrMoreTimes()
               ->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);

        $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));

        $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE),
            $stream->read(100)
            );

        $this->assertFalse($stream->read(1));
    }

    public function testRequestingByteArrayCountPastEndOfStream()
    {
        $reader = $this->_getReader();
        $factory = $this->_getFactory($reader);

        $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');

        $reader->shouldReceive('getInitialByteSize')
               ->zeroOrMoreTimes()
               ->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);

        $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));

        $this->assertEquals(array(0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE),
            $stream->readBytes(100)
            );

        $this->assertFalse($stream->readBytes(1));
    }

    public function testPointerOffsetCanBeSet()
    {
        $reader = $this->_getReader();
        $factory = $this->_getFactory($reader);

        $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');

        $reader->shouldReceive('getInitialByteSize')
               ->zeroOrMoreTimes()
               ->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);

        $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));

        $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1));

        $stream->setPointer(0);

        $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1));

        $stream->setPointer(2);

        $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBE), $stream->read(1));
    }

    public function testContentsCanBeFlushed()
    {
        $reader = $this->_getReader();
        $factory = $this->_getFactory($reader);

        $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');

        $reader->shouldReceive('getInitialByteSize')
               ->zeroOrMoreTimes()
               ->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);

        $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE));

        $stream->flushContents();

        $this->assertFalse($stream->read(1));
    }

    public function testByteStreamCanBeImportingUsesValidator()
    {
        $reader = $this->_getReader();
        $factory = $this->_getFactory($reader);
        $os = $this->_getByteStream();

        $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');

        $os->shouldReceive('setReadPointer')
           ->between(0, 1)
           ->with(0);
        $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0));
        $os->shouldReceive('read')->once()->andReturn(pack('C*', 0x94));
        $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0));
        $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xB6));
        $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0));
        $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xBE));
        $os->shouldReceive('read')
           ->zeroOrMoreTimes()
           ->andReturn(false);

        $reader->shouldReceive('getInitialByteSize')
               ->zeroOrMoreTimes()
               ->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);

        $stream->importByteStream($os);
    }

    public function testImportingStreamProducesCorrectCharArray()
    {
        $reader = $this->_getReader();
        $factory = $this->_getFactory($reader);
        $os = $this->_getByteStream();

        $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8');

        $os->shouldReceive('setReadPointer')
           ->between(0, 1)
           ->with(0);
        $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0));
        $os->shouldReceive('read')->once()->andReturn(pack('C*', 0x94));
        $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0));
        $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xB6));
        $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0));
        $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xBE));
        $os->shouldReceive('read')
           ->zeroOrMoreTimes()
           ->andReturn(false);

        $reader->shouldReceive('getInitialByteSize')
               ->zeroOrMoreTimes()
               ->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1);

        $stream->importByteStream($os);

        $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1));
        $this->assertIdenticalBinary(pack('C*', 0xD0, 0xB6), $stream->read(1));
        $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBE), $stream->read(1));

        $this->assertFalse($stream->read(1));
    }

    public function testAlgorithmWithFixedWidthCharsets()
    {
        $reader = $this->_getReader();
        $factory = $this->_getFactory($reader);

        $reader->shouldReceive('getInitialByteSize')
               ->zeroOrMoreTimes()
               ->andReturn(2);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1, 0x8D), 2);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0, 0xBB), 2);
        $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0, 0xB0), 2);

        $stream = new Swift_CharacterStream_ArrayCharacterStream(
            $factory, 'utf-8'
        );
        $stream->importString(pack('C*', 0xD1, 0x8D, 0xD0, 0xBB, 0xD0, 0xB0));

        $this->assertIdenticalBinary(pack('C*', 0xD1, 0x8D), $stream->read(1));
        $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBB), $stream->read(1));
        $this->assertIdenticalBinary(pack('C*', 0xD0, 0xB0), $stream->read(1));

        $this->assertFalse($stream->read(1));
    }

    // -- Creation methods

    private function _getReader()
    {
        return $this->getMockery('Swift_CharacterReader');
    }

    private function _getFactory($reader)
    {
        $factory = $this->getMockery('Swift_CharacterReaderFactory');
        $factory->shouldReceive('getReaderFor')
                ->zeroOrMoreTimes()
                ->with('utf-8')
                ->andReturn($reader);

        return $factory;
    }

    private function _getByteStream()
    {
        return $this->getMockery('Swift_OutputByteStream');
    }
}
<?php

class One
{
    public $arg1;
    public $arg2;
    public function __construct($arg1 = null, $arg2 = null)
    {
        $this->arg1 = $arg1;
        $this->arg2 = $arg2;
    }
}

class Swift_DependencyContainerTest extends \PHPUnit_Framework_TestCase
{
    private $_container;

    public function setUp()
    {
        $this->_container = new Swift_DependencyContainer();
    }

    public function testRegisterAndLookupValue()
    {
        $this->_container->register('foo')->asValue('bar');
        $this->assertEquals('bar', $this->_container->lookup('foo'));
    }

    public function testHasReturnsTrueForRegisteredValue()
    {
        $this->_container->register('foo')->asValue('bar');
        $this->assertTrue($this->_container->has('foo'));
    }

    public function testHasReturnsFalseForUnregisteredValue()
    {
        $this->assertFalse($this->_container->has('foo'));
    }

    public function testRegisterAndLookupNewInstance()
    {
        $this->_container->register('one')->asNewInstanceOf('One');
        $this->assertInstanceof('One', $this->_container->lookup('one'));
    }

    public function testHasReturnsTrueForRegisteredInstance()
    {
        $this->_container->register('one')->asNewInstanceOf('One');
        $this->assertTrue($this->_container->has('one'));
    }

    public function testNewInstanceIsAlwaysNew()
    {
        $this->_container->register('one')->asNewInstanceOf('One');
        $a = $this->_container->lookup('one');
        $b = $this->_container->lookup('one');
        $this->assertEquals($a, $b);
    }

    public function testRegisterAndLookupSharedInstance()
    {
        $this->_container->register('one')->asSharedInstanceOf('One');
        $this->assertInstanceof('One', $this->_container->lookup('one'));
    }

    public function testHasReturnsTrueForSharedInstance()
    {
        $this->_container->register('one')->asSharedInstanceOf('One');
        $this->assertTrue($this->_container->has('one'));
    }

    public function testMultipleSharedInstancesAreSameInstance()
    {
        $this->_container->register('one')->asSharedInstanceOf('One');
        $a = $this->_container->lookup('one');
        $b = $this->_container->lookup('one');
        $this->assertEquals($a, $b);
    }

    public function testNewInstanceWithDependencies()
    {
        $this->_container->register('foo')->asValue('FOO');
        $this->_container->register('one')->asNewInstanceOf('One')
            ->withDependencies(array('foo'));
        $obj = $this->_container->lookup('one');
        $this->assertSame('FOO', $obj->arg1);
    }

    public function testNewInstanceWithMultipleDependencies()
    {
        $this->_container->register('foo')->asValue('FOO');
        $this->_container->register('bar')->asValue(42);
        $this->_container->register('one')->asNewInstanceOf('One')
            ->withDependencies(array('foo', 'bar'));
        $obj = $this->_container->lookup('one');
        $this->assertSame('FOO', $obj->arg1);
        $this->assertSame(42, $obj->arg2);
    }

    public function testNewInstanceWithInjectedObjects()
    {
        $this->_container->register('foo')->asValue('FOO');
        $this->_container->register('one')->asNewInstanceOf('One');
        $this->_container->register('two')->asNewInstanceOf('One')
            ->withDependencies(array('one', 'foo'));
        $obj = $this->_container->lookup('two');
        $this->assertEquals($this->_container->lookup('one'), $obj->arg1);
        $this->assertSame('FOO', $obj->arg2);
    }

    public function testNewInstanceWithAddConstructorValue()
    {
        $this->_container->register('one')->asNewInstanceOf('One')
            ->addConstructorValue('x')
            ->addConstructorValue(99);
        $obj = $this->_container->lookup('one');
        $this->assertSame('x', $obj->arg1);
        $this->assertSame(99, $obj->arg2);
    }

    public function testNewInstanceWithAddConstructorLookup()
    {
        $this->_container->register('foo')->asValue('FOO');
        $this->_container->register('bar')->asValue(42);
        $this->_container->register('one')->asNewInstanceOf('One')
            ->addConstructorLookup('foo')
            ->addConstructorLookup('bar');

        $obj = $this->_container->lookup('one');
        $this->assertSame('FOO', $obj->arg1);
        $this->assertSame(42, $obj->arg2);
    }

    public function testResolvedDependenciesCanBeLookedUp()
    {
        $this->_container->register('foo')->asValue('FOO');
        $this->_container->register('one')->asNewInstanceOf('One');
        $this->_container->register('two')->asNewInstanceOf('One')
            ->withDependencies(array('one', 'foo'));
        $deps = $this->_container->createDependenciesFor('two');
        $this->assertEquals(
            array($this->_container->lookup('one'), 'FOO'), $deps
            );
    }

    public function testArrayOfDependenciesCanBeSpecified()
    {
        $this->_container->register('foo')->asValue('FOO');
        $this->_container->register('one')->asNewInstanceOf('One');
        $this->_container->register('two')->asNewInstanceOf('One')
            ->withDependencies(array(array('one', 'foo'), 'foo'));

        $obj = $this->_container->lookup('two');
        $this->assertEquals(array($this->_container->lookup('one'), 'FOO'), $obj->arg1);
        $this->assertSame('FOO', $obj->arg2);
    }

    public function testAliasCanBeSet()
    {
        $this->_container->register('foo')->asValue('FOO');
        $this->_container->register('bar')->asAliasOf('foo');

        $this->assertSame('FOO', $this->_container->lookup('bar'));
    }

    public function testAliasOfAliasCanBeSet()
    {
        $this->_container->register('foo')->asValue('FOO');
        $this->_container->register('bar')->asAliasOf('foo');
        $this->_container->register('zip')->asAliasOf('bar');
        $this->_container->register('button')->asAliasOf('zip');

        $this->assertSame('FOO', $this->_container->lookup('button'));
    }
}
<?php

class Swift_Encoder_Base64EncoderTest extends \PHPUnit_Framework_TestCase
{
    private $_encoder;

    public function setUp()
    {
        $this->_encoder = new Swift_Encoder_Base64Encoder();
    }

    /*
    There's really no point in testing the entire base64 encoding to the
    level QP encoding has been tested.  base64_encode() has been in PHP for
    years.
    */

    public function testInputOutputRatioIs3to4Bytes()
    {
        /*
        RFC 2045, 6.8

         The encoding process represents 24-bit groups of input bits as output
         strings of 4 encoded characters.  Proceeding from left to right, a
         24-bit input group is formed by concatenating 3 8bit input groups.
         These 24 bits are then treated as 4 concatenated 6-bit groups, each
         of which is translated into a single digit in the base64 alphabet.
         */

        $this->assertEquals(
            'MTIz', $this->_encoder->encodeString('123'),
            '%s: 3 bytes of input should yield 4 bytes of output'
            );
        $this->assertEquals(
            'MTIzNDU2', $this->_encoder->encodeString('123456'),
            '%s: 6 bytes in input should yield 8 bytes of output'
            );
        $this->assertEquals(
            'MTIzNDU2Nzg5', $this->_encoder->encodeString('123456789'),
            '%s: 9 bytes in input should yield 12 bytes of output'
            );
    }

    public function testPadLength()
    {
        /*
        RFC 2045, 6.8

       Special processing is performed if fewer than 24 bits are available
       at the end of the data being encoded.  A full encoding quantum is
       always completed at the end of a body.  When fewer than 24 input bits
       are available in an input group, zero bits are added (on the right)
       to form an integral number of 6-bit groups.  Padding at the end of
       the data is performed using the "=" character.  Since all base64
       input is an integral number of octets, only the following cases can
       arise: (1) the final quantum of encoding input is an integral
       multiple of 24 bits; here, the final unit of encoded output will be
       an integral multiple of 4 characters with no "=" padding, (2) the
       final quantum of encoding input is exactly 8 bits; here, the final
       unit of encoded output will be two characters followed by two "="
       padding characters, or (3) the final quantum of encoding input is
       exactly 16 bits; here, the final unit of encoded output will be three
       characters followed by one "=" padding character.
       */

        for ($i = 0; $i < 30; ++$i) {
            $input = pack('C', rand(0, 255));
            $this->assertRegExp(
                '~^[a-zA-Z0-9/\+]{2}==$~', $this->_encoder->encodeString($input),
                '%s: A single byte should have 2 bytes of padding'
                );
        }

        for ($i = 0; $i < 30; ++$i) {
            $input = pack('C*', rand(0, 255), rand(0, 255));
            $this->assertRegExp(
                '~^[a-zA-Z0-9/\+]{3}=$~', $this->_encoder->encodeString($input),
                '%s: Two bytes should have 1 byte of padding'
                );
        }

        for ($i = 0; $i < 30; ++$i) {
            $input = pack('C*', rand(0, 255), rand(0, 255), rand(0, 255));
            $this->assertRegExp(
                '~^[a-zA-Z0-9/\+]{4}$~', $this->_encoder->encodeString($input),
                '%s: Three bytes should have no padding'
                );
        }
    }

    public function testMaximumLineLengthIs76Characters()
    {
        /*
         The encoded output stream must be represented in lines of no more
         than 76 characters each.  All line breaks or other characters not
         found in Table 1 must be ignored by decoding software.
         */

        $input =
        'abcdefghijklmnopqrstuvwxyz'.
        'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
        '1234567890'.
        'abcdefghijklmnopqrstuvwxyz'.
        'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
        '1234567890'.
        'ABCDEFGHIJKLMNOPQRSTUVWXYZ';

        $output =
        'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk'.//38
        'NERUZHSElKS0xNTk9QUVJTVFVWV1hZWjEyMzQ1'."\r\n".//76 *
        'Njc4OTBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3'.//38
        'h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFla'."\r\n".//76 *
        'MTIzNDU2Nzg5MEFCQ0RFRkdISUpLTE1OT1BRUl'.//38
        'NUVVZXWFla';                                       //48

        $this->assertEquals(
            $output, $this->_encoder->encodeString($input),
            '%s: Lines should be no more than 76 characters'
            );
    }

    public function testMaximumLineLengthCanBeSpecified()
    {
        $input =
        'abcdefghijklmnopqrstuvwxyz'.
        'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
        '1234567890'.
        'abcdefghijklmnopqrstuvwxyz'.
        'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
        '1234567890'.
        'ABCDEFGHIJKLMNOPQRSTUVWXYZ';

        $output =
        'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk'.//38
        'NERUZHSElKS0'."\r\n".//50 *
        'xNTk9QUVJTVFVWV1hZWjEyMzQ1Njc4OTBhYmNk'.//38
        'ZWZnaGlqa2xt'."\r\n".//50 *
        'bm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1'.//38
        'BRUlNUVVZXWF'."\r\n".//50 *
        'laMTIzNDU2Nzg5MEFCQ0RFRkdISUpLTE1OT1BR'.//38
        'UlNUVVZXWFla';                                     //50 *

        $this->assertEquals(
            $output, $this->_encoder->encodeString($input, 0, 50),
            '%s: Lines should be no more than 100 characters'
            );
    }

    public function testFirstLineLengthCanBeDifferent()
    {
        $input =
        'abcdefghijklmnopqrstuvwxyz'.
        'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
        '1234567890'.
        'abcdefghijklmnopqrstuvwxyz'.
        'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
        '1234567890'.
        'ABCDEFGHIJKLMNOPQRSTUVWXYZ';

        $output =
        'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk'.//38
        'NERUZHSElKS0xNTk9QU'."\r\n".//57 *
        'VJTVFVWV1hZWjEyMzQ1Njc4OTBhYmNkZWZnaGl'.//38
        'qa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLT'."\r\n".//76 *
        'E1OT1BRUlNUVVZXWFlaMTIzNDU2Nzg5MEFCQ0R'.//38
        'FRkdISUpLTE1OT1BRUlNUVVZXWFla';                    //67

        $this->assertEquals(
            $output, $this->_encoder->encodeString($input, 19),
            '%s: First line offset is 19 so first line should be 57 chars long'
            );
    }
}
<?php

class Swift_Encoder_QpEncoderTest extends \SwiftMailerTestCase
{
    /* -- RFC 2045, 6.7 --
    (1)   (General 8bit representation) Any octet, except a CR or
                    LF that is part of a CRLF line break of the canonical
                    (standard) form of the data being encoded, may be
                    represented by an "=" followed by a two digit
                    hexadecimal representation of the octet's value.  The
                    digits of the hexadecimal alphabet, for this purpose,
                    are "0123456789ABCDEF".  Uppercase letters must be
                    used; lowercase letters are not allowed.  Thus, for
                    example, the decimal value 12 (US-ASCII form feed) can
                    be represented by "=0C", and the decimal value 61 (US-
                    ASCII EQUAL SIGN) can be represented by "=3D".  This
                    rule must be followed except when the following rules
                    allow an alternative encoding.
                    */

    public function testPermittedCharactersAreNotEncoded()
    {
        /* -- RFC 2045, 6.7 --
        (2)   (Literal representation) Octets with decimal values of
                    33 through 60 inclusive, and 62 through 126, inclusive,
                    MAY be represented as the US-ASCII characters which
                    correspond to those octets (EXCLAMATION POINT through
                    LESS THAN, and GREATER THAN through TILDE,
                    respectively).
                    */

        foreach (array_merge(range(33, 60), range(62, 126)) as $ordinal) {
            $char = chr($ordinal);

            $charStream = $this->_createCharStream();
            $charStream->shouldReceive('flushContents')
                       ->once();
            $charStream->shouldReceive('importString')
                       ->once()
                       ->with($char);
            $charStream->shouldReceive('readBytes')
                       ->once()
                       ->andReturn(array($ordinal));
            $charStream->shouldReceive('readBytes')
                       ->atLeast()->times(1)
                       ->andReturn(false);

            $encoder = new Swift_Encoder_QpEncoder($charStream);

            $this->assertIdenticalBinary($char, $encoder->encodeString($char));
        }
    }

    public function testWhiteSpaceAtLineEndingIsEncoded()
    {
        /* -- RFC 2045, 6.7 --
        (3)   (White Space) Octets with values of 9 and 32 MAY be
                    represented as US-ASCII TAB (HT) and SPACE characters,
                    respectively, but MUST NOT be so represented at the end
                    of an encoded line.  Any TAB (HT) or SPACE characters
                    on an encoded line MUST thus be followed on that line
                    by a printable character.  In particular, an "=" at the
                    end of an encoded line, indicating a soft line break
                    (see rule #5) may follow one or more TAB (HT) or SPACE
                    characters.  It follows that an octet with decimal
                    value 9 or 32 appearing at the end of an encoded line
                    must be represented according to Rule #1.  This rule is
                    necessary because some MTAs (Message Transport Agents,
                    programs which transport messages from one user to
                    another, or perform a portion of such transfers) are
                    known to pad lines of text with SPACEs, and others are
                    known to remove "white space" characters from the end
                    of a line.  Therefore, when decoding a Quoted-Printable
                    body, any trailing white space on a line must be
                    deleted, as it will necessarily have been added by
                    intermediate transport agents.
                    */

        $HT = chr(0x09); //9
        $SPACE = chr(0x20); //32

        //HT
        $string = 'a'.$HT.$HT."\r\n".'b';

        $charStream = $this->_createCharStream();
        $charStream->shouldReceive('flushContents')
                    ->once();
        $charStream->shouldReceive('importString')
                    ->once()
                    ->with($string);

        $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('a')));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x09));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x09));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0D));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0A));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('b')));
        $charStream->shouldReceive('readBytes')->once()->andReturn(false);

        $encoder = new Swift_Encoder_QpEncoder($charStream);
        $this->assertEquals(
            'a'.$HT.'=09'."\r\n".'b',
            $encoder->encodeString($string)
            );

        //SPACE
        $string = 'a'.$SPACE.$SPACE."\r\n".'b';

        $charStream = $this->_createCharStream();
        $charStream->shouldReceive('flushContents')
                    ->once();
        $charStream->shouldReceive('importString')
                    ->once()
                    ->with($string);

        $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('a')));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x20));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x20));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0D));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0A));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('b')));
        $charStream->shouldReceive('readBytes')->once()->andReturn(false);

        $encoder = new Swift_Encoder_QpEncoder($charStream);
        $this->assertEquals(
            'a'.$SPACE.'=20'."\r\n".'b',
            $encoder->encodeString($string)
            );
    }

    public function testCRLFIsLeftAlone()
    {
        /*
        (4)   (Line Breaks) A line break in a text body, represented
                    as a CRLF sequence in the text canonical form, must be
                    represented by a (RFC 822) line break, which is also a
                    CRLF sequence, in the Quoted-Printable encoding.  Since
                    the canonical representation of media types other than
                    text do not generally include the representation of
                    line breaks as CRLF sequences, no hard line breaks
                    (i.e. line breaks that are intended to be meaningful
                    and to be displayed to the user) can occur in the
                    quoted-printable encoding of such types.  Sequences
                    like "=0D", "=0A", "=0A=0D" and "=0D=0A" will routinely
                    appear in non-text data represented in quoted-
                    printable, of course.

                    Note that many implementations may elect to encode the
                    local representation of various content types directly
                    rather than converting to canonical form first,
                    encoding, and then converting back to local
                    representation.  In particular, this may apply to plain
                    text material on systems that use newline conventions
                    other than a CRLF terminator sequence.  Such an
                    implementation optimization is permissible, but only
                    when the combined canonicalization-encoding step is
                    equivalent to performing the three steps separately.
                    */

        $string = 'a'."\r\n".'b'."\r\n".'c'."\r\n";

        $charStream = $this->_createCharStream();
        $charStream->shouldReceive('flushContents')
                    ->once();
        $charStream->shouldReceive('importString')
                    ->once()
                    ->with($string);

        $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('a')));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0D));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0A));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('b')));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0D));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0A));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('c')));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0D));
        $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0A));
        $charStream->shouldReceive('readBytes')->once()->andReturn(false);

        $encoder = new Swift_Encoder_QpEncoder($charStream);
        $this->assertEquals($string, $encoder->encodeString($string));
    }

    public function testLinesLongerThan76CharactersAreSoftBroken()
    {
        /*
        (5)   (Soft Line Breaks) The Quoted-Printable encoding
                    REQUIRES that encoded lines be no more than 76
                    characters long.  If longer lines are to be encoded
                    with the Quoted-Printable encoding, "soft" line breaks
                    must be used.  An equal sign as the last character on a
                    encoded line indicates such a non-significant ("soft")
                    line break in the encoded text.
                    */

        $input = str_repeat('a', 140);

        $charStream = $this->_createCharStream();
        $charStream->shouldReceive('flushContents')
                    ->once();
        $charStream->shouldReceive('importString')
                    ->once()
                    ->with($input);

        $output = '';
        for ($i = 0; $i < 140; ++$i) {
            $charStream->shouldReceive('readBytes')
                       ->once()
                       ->andReturn(array(ord('a')));

            if (75 == $i) {
                $output .= "=\r\n";
            }
            $output .= 'a';
        }

        $charStream->shouldReceive('readBytes')
                    ->once()
                    ->andReturn(false);

        $encoder = new Swift_Encoder_QpEncoder($charStream);
        $this->assertEquals($output, $encoder->encodeString($input));
    }

    public function testMaxLineLengthCanBeSpecified()
    {
        $input = str_repeat('a', 100);

        $charStream = $this->_createCharStream();
        $charStream->shouldReceive('flushContents')
                    ->once();
        $charStream->shouldReceive('importString')
                    ->once()
                    ->with($input);

        $output = '';
        for ($i = 0; $i < 100; ++$i) {
            $charStream->shouldReceive('readBytes')
                       ->once()
                       ->andReturn(array(ord('a')));

            if (53 == $i) {
                $output .= "=\r\n";
            }
            $output .= 'a';
        }
        $charStream->shouldReceive('readBytes')
                    ->once()
                    ->andReturn(false);

        $encoder = new Swift_Encoder_QpEncoder($charStream);
        $this->assertEquals($output, $encoder->encodeString($input, 0, 54));
    }

    public function testBytesBelowPermittedRangeAreEncoded()
    {
        /*
        According to Rule (1 & 2)
        */

        foreach (range(0, 32) as $ordinal) {
            $char = chr($ordinal);

            $charStream = $this->_createCharStream();
            $charStream->shouldReceive('flushContents')
                       ->once();
            $charStream->shouldReceive('importString')
                       ->once()
                       ->with($char);
            $charStream->shouldReceive('readBytes')
                       ->once()
                       ->andReturn(array($ordinal));
            $charStream->shouldReceive('readBytes')
                       ->atLeast()->times(1)
                       ->andReturn(false);

            $encoder = new Swift_Encoder_QpEncoder($charStream);

            $this->assertEquals(
                sprintf('=%02X', $ordinal), $encoder->encodeString($char)
                );
        }
    }

    public function testDecimalByte61IsEncoded()
    {
        /*
        According to Rule (1 & 2)
        */

        $char = '=';

        $charStream = $this->_createCharStream();
        $charStream->shouldReceive('flushContents')
                    ->once();
        $charStream->shouldReceive('importString')
                    ->once()
                    ->with($char);
        $charStream->shouldReceive('readBytes')
                    ->once()
                    ->andReturn(array(61));
        $charStream->shouldReceive('readBytes')
                    ->atLeast()->times(1)
                    ->andReturn(false);

        $encoder = new Swift_Encoder_QpEncoder($charStream);

        $this->assertEquals('=3D', $encoder->encodeString('='));
    }

    public function testBytesAbovePermittedRangeAreEncoded()
    {
        /*
        According to Rule (1 & 2)
        */

        foreach (range(127, 255) as $ordinal) {
            $char = chr($ordinal);

            $charStream = $this->_createCharStream();
            $charStream->shouldReceive('flushContents')
                       ->once();
            $charStream->shouldReceive('importString')
                       ->once()
                       ->with($char);
            $charStream->shouldReceive('readBytes')
                       ->once()
                       ->andReturn(array($ordinal));
            $charStream->shouldReceive('readBytes')
                       ->atLeast()->times(1)
                       ->andReturn(false);

            $encoder = new Swift_Encoder_QpEncoder($charStream);

            $this->assertEquals(
                sprintf('=%02X', $ordinal), $encoder->encodeString($char)
                );
        }
    }

    public function testFirstLineLengthCanBeDifferent()
    {
        $input = str_repeat('a', 140);

        $charStream = $this->_createCharStream();
        $charStream->shouldReceive('flushContents')
                    ->once();
        $charStream->shouldReceive('importString')
                    ->once()
                    ->with($input);

        $output = '';
        for ($i = 0; $i < 140; ++$i) {
            $charStream->shouldReceive('readBytes')
                       ->once()
                       ->andReturn(array(ord('a')));

            if (53 == $i || 53 + 75 == $i) {
                $output .= "=\r\n";
            }
            $output .= 'a';
        }

        $charStream->shouldReceive('readBytes')
                    ->once()
                    ->andReturn(false);

        $encoder = new Swift_Encoder_QpEncoder($charStream);
        $this->assertEquals(
            $output, $encoder->encodeString($input, 22),
            '%s: First line should start at offset 22 so can only have max length 54'
            );
    }

    public function testTextIsPreWrapped()
    {
        $encoder = $this->createEncoder();

        $input = str_repeat('a', 70)."\r\n".
                 str_repeat('a', 70)."\r\n".
                 str_repeat('a', 70);

        $this->assertEquals(
            $input, $encoder->encodeString($input)
            );
    }

    // -- Creation methods

    private function _createCharStream()
    {
        return $this->getMockery('Swift_CharacterStream')->shouldIgnoreMissing();
    }

    private function createEncoder()
    {
        $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
        $charStream = new Swift_CharacterStream_NgCharacterStream($factory, 'utf-8');

        return new Swift_Encoder_QpEncoder($charStream);
    }
}
<?php

class Swift_Encoder_Rfc2231EncoderTest extends \SwiftMailerTestCase
{
    private $_rfc2045Token = '/^[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+$/D';

    /* --
    This algorithm is described in RFC 2231, but is barely touched upon except
    for mentioning bytes can be represented as their octet values (e.g. %20 for
    the SPACE character).

    The tests here focus on how to use that representation to always generate text
    which matches RFC 2045's definition of "token".
    */

    public function testEncodingAsciiCharactersProducesValidToken()
    {
        $charStream = $this->getMockery('Swift_CharacterStream');

        $string = '';
        foreach (range(0x00, 0x7F) as $octet) {
            $char = pack('C', $octet);
            $string .= $char;
            $charStream->shouldReceive('read')
                       ->once()
                       ->andReturn($char);
        }

        $charStream->shouldReceive('flushContents')
                    ->once();
        $charStream->shouldReceive('importString')
                    ->once()
                    ->with($string);
        $charStream->shouldReceive('read')
                    ->atLeast()->times(1)
                    ->andReturn(false);

        $encoder = new Swift_Encoder_Rfc2231Encoder($charStream);
        $encoded = $encoder->encodeString($string);

        foreach (explode("\r\n", $encoded) as $line) {
            $this->assertRegExp($this->_rfc2045Token, $line,
                '%s: Encoder should always return a valid RFC 2045 token.');
        }
    }

    public function testEncodingNonAsciiCharactersProducesValidToken()
    {
        $charStream = $this->getMockery('Swift_CharacterStream');

        $string = '';
        foreach (range(0x80, 0xFF) as $octet) {
            $char = pack('C', $octet);
            $string .= $char;
            $charStream->shouldReceive('read')
                       ->once()
                       ->andReturn($char);
        }
        $charStream->shouldReceive('flushContents')
                    ->once();
        $charStream->shouldReceive('importString')
                    ->once()
                    ->with($string);
        $charStream->shouldReceive('read')
                    ->atLeast()->times(1)
                    ->andReturn(false);
        $encoder = new Swift_Encoder_Rfc2231Encoder($charStream);

        $encoded = $encoder->encodeString($string);

        foreach (explode("\r\n", $encoded) as $line) {
            $this->assertRegExp($this->_rfc2045Token, $line,
                '%s: Encoder should always return a valid RFC 2045 token.');
        }
    }

    public function testMaximumLineLengthCanBeSet()
    {
        $charStream = $this->getMockery('Swift_CharacterStream');

        $string = '';
        for ($x = 0; $x < 200; ++$x) {
            $char = 'a';
            $string .= $char;
            $charStream->shouldReceive('read')
                       ->once()
                       ->andReturn($char);
        }
        $charStream->shouldReceive('flushContents')
                    ->once();
        $charStream->shouldReceive('importString')
                    ->once()
                    ->with($string);
        $charStream->shouldReceive('read')
                    ->atLeast()->times(1)
                    ->andReturn(false);
        $encoder = new Swift_Encoder_Rfc2231Encoder($charStream);

        $encoded = $encoder->encodeString($string, 0, 75);

        $this->assertEquals(
            str_repeat('a', 75)."\r\n".
            str_repeat('a', 75)."\r\n".
            str_repeat('a', 50),
            $encoded,
            '%s: Lines should be wrapped at each 75 characters'
            );
    }

    public function testFirstLineCanHaveShorterLength()
    {
        $charStream = $this->getMockery('Swift_CharacterStream');

        $string = '';
        for ($x = 0; $x < 200; ++$x) {
            $char = 'a';
            $string .= $char;
            $charStream->shouldReceive('read')
                       ->once()
                       ->andReturn($char);
        }
        $charStream->shouldReceive('flushContents')
                    ->once();
        $charStream->shouldReceive('importString')
                    ->once()
                    ->with($string);
        $charStream->shouldReceive('read')
                    ->atLeast()->times(1)
                    ->andReturn(false);
        $encoder = new Swift_Encoder_Rfc2231Encoder($charStream);
        $encoded = $encoder->encodeString($string, 25, 75);

        $this->assertEquals(
            str_repeat('a', 50)."\r\n".
            str_repeat('a', 75)."\r\n".
            str_repeat('a', 75),
            $encoded,
            '%s: First line should be 25 bytes shorter than the others.'
            );
    }
}
<?php

class Swift_Events_CommandEventTest extends \PHPUnit_Framework_TestCase
{
    public function testCommandCanBeFetchedByGetter()
    {
        $evt = $this->_createEvent($this->_createTransport(), "FOO\r\n");
        $this->assertEquals("FOO\r\n", $evt->getCommand());
    }

    public function testSuccessCodesCanBeFetchedViaGetter()
    {
        $evt = $this->_createEvent($this->_createTransport(), "FOO\r\n", array(250));
        $this->assertEquals(array(250), $evt->getSuccessCodes());
    }

    public function testSourceIsBuffer()
    {
        $transport = $this->_createTransport();
        $evt = $this->_createEvent($transport, "FOO\r\n");
        $ref = $evt->getSource();
        $this->assertEquals($transport, $ref);
    }

    // -- Creation Methods

    private function _createEvent(Swift_Transport $source, $command, $successCodes = array())
    {
        return new Swift_Events_CommandEvent($source, $command, $successCodes);
    }

    private function _createTransport()
    {
        return $this->getMockBuilder('Swift_Transport')->getMock();
    }
}
<?php

class Swift_Events_EventObjectTest extends \PHPUnit_Framework_TestCase
{
    public function testEventSourceCanBeReturnedViaGetter()
    {
        $source = new stdClass();
        $evt = $this->_createEvent($source);
        $ref = $evt->getSource();
        $this->assertEquals($source, $ref);
    }

    public function testEventDoesNotHaveCancelledBubbleWhenNew()
    {
        $source = new stdClass();
        $evt = $this->_createEvent($source);
        $this->assertFalse($evt->bubbleCancelled());
    }

    public function testBubbleCanBeCancelledInEvent()
    {
        $source = new stdClass();
        $evt = $this->_createEvent($source);
        $evt->cancelBubble();
        $this->assertTrue($evt->bubbleCancelled());
    }

    // -- Creation Methods

    private function _createEvent($source)
    {
        return new Swift_Events_EventObject($source);
    }
}
<?php

class Swift_Events_ResponseEventTest extends \PHPUnit_Framework_TestCase
{
    public function testResponseCanBeFetchViaGetter()
    {
        $evt = $this->_createEvent($this->_createTransport(), "250 Ok\r\n", true);
        $this->assertEquals("250 Ok\r\n", $evt->getResponse(),
            '%s: Response should be available via getResponse()'
            );
    }

    public function testResultCanBeFetchedViaGetter()
    {
        $evt = $this->_createEvent($this->_createTransport(), "250 Ok\r\n", false);
        $this->assertFalse($evt->isValid(),
            '%s: Result should be checkable via isValid()'
            );
    }

    public function testSourceIsBuffer()
    {
        $transport = $this->_createTransport();
        $evt = $this->_createEvent($transport, "250 Ok\r\n", true);
        $ref = $evt->getSource();
        $this->assertEquals($transport, $ref);
    }

    // -- Creation Methods

    private function _createEvent(Swift_Transport $source, $response, $result)
    {
        return new Swift_Events_ResponseEvent($source, $response, $result);
    }

    private function _createTransport()
    {
        return $this->getMockBuilder('Swift_Transport')->getMock();
    }
}
<?php

class Swift_Events_SendEventTest extends \PHPUnit_Framework_TestCase
{
    public function testMessageCanBeFetchedViaGetter()
    {
        $message = $this->_createMessage();
        $transport = $this->_createTransport();

        $evt = $this->_createEvent($transport, $message);

        $ref = $evt->getMessage();
        $this->assertEquals($message, $ref,
            '%s: Message should be returned from getMessage()'
            );
    }

    public function testTransportCanBeFetchViaGetter()
    {
        $message = $this->_createMessage();
        $transport = $this->_createTransport();

        $evt = $this->_createEvent($transport, $message);

        $ref = $evt->getTransport();
        $this->assertEquals($transport, $ref,
            '%s: Transport should be returned from getTransport()'
            );
    }

    public function testTransportCanBeFetchViaGetSource()
    {
        $message = $this->_createMessage();
        $transport = $this->_createTransport();

        $evt = $this->_createEvent($transport, $message);

        $ref = $evt->getSource();
        $this->assertEquals($transport, $ref,
            '%s: Transport should be returned from getSource()'
            );
    }

    public function testResultCanBeSetAndGet()
    {
        $message = $this->_createMessage();
        $transport = $this->_createTransport();

        $evt = $this->_createEvent($transport, $message);

        $evt->setResult(
            Swift_Events_SendEvent::RESULT_SUCCESS | Swift_Events_SendEvent::RESULT_TENTATIVE
            );

        $this->assertTrue((bool) ($evt->getResult() & Swift_Events_SendEvent::RESULT_SUCCESS));
        $this->assertTrue((bool) ($evt->getResult() & Swift_Events_SendEvent::RESULT_TENTATIVE));
    }

    public function testFailedRecipientsCanBeSetAndGet()
    {
        $message = $this->_createMessage();
        $transport = $this->_createTransport();

        $evt = $this->_createEvent($transport, $message);

        $evt->setFailedRecipients(array('foo@bar', 'zip@button'));

        $this->assertEquals(array('foo@bar', 'zip@button'), $evt->getFailedRecipients(),
            '%s: FailedRecipients should be returned from getter'
            );
    }

    public function testFailedRecipientsGetsPickedUpCorrectly()
    {
        $message = $this->_createMessage();
        $transport = $this->_createTransport();

        $evt = $this->_createEvent($transport, $message);
        $this->assertEquals(array(), $evt->getFailedRecipients());
    }

    // -- Creation Methods

    private function _createEvent(Swift_Transport $source,
        Swift_Mime_Message $message)
    {
        return new Swift_Events_SendEvent($source, $message);
    }

    private function _createTransport()
    {
        return $this->getMockBuilder('Swift_Transport')->getMock();
    }

    private function _createMessage()
    {
        return $this->getMockBuilder('Swift_Mime_Message')->getMock();
    }
}
<?php

class Swift_Events_SimpleEventDispatcherTest extends \PHPUnit_Framework_TestCase
{
    private $_dispatcher;

    public function setUp()
    {
        $this->_dispatcher = new Swift_Events_SimpleEventDispatcher();
    }

    public function testSendEventCanBeCreated()
    {
        $transport = $this->getMockBuilder('Swift_Transport')->getMock();
        $message = $this->getMockBuilder('Swift_Mime_Message')->getMock();
        $evt = $this->_dispatcher->createSendEvent($transport, $message);
        $this->assertInstanceof('Swift_Events_SendEvent', $evt);
        $this->assertSame($message, $evt->getMessage());
        $this->assertSame($transport, $evt->getTransport());
    }

    public function testCommandEventCanBeCreated()
    {
        $buf = $this->getMockBuilder('Swift_Transport')->getMock();
        $evt = $this->_dispatcher->createCommandEvent($buf, "FOO\r\n", array(250));
        $this->assertInstanceof('Swift_Events_CommandEvent', $evt);
        $this->assertSame($buf, $evt->getSource());
        $this->assertEquals("FOO\r\n", $evt->getCommand());
        $this->assertEquals(array(250), $evt->getSuccessCodes());
    }

    public function testResponseEventCanBeCreated()
    {
        $buf = $this->getMockBuilder('Swift_Transport')->getMock();
        $evt = $this->_dispatcher->createResponseEvent($buf, "250 Ok\r\n", true);
        $this->assertInstanceof('Swift_Events_ResponseEvent', $evt);
        $this->assertSame($buf, $evt->getSource());
        $this->assertEquals("250 Ok\r\n", $evt->getResponse());
        $this->assertTrue($evt->isValid());
    }

    public function testTransportChangeEventCanBeCreated()
    {
        $transport = $this->getMockBuilder('Swift_Transport')->getMock();
        $evt = $this->_dispatcher->createTransportChangeEvent($transport);
        $this->assertInstanceof('Swift_Events_TransportChangeEvent', $evt);
        $this->assertSame($transport, $evt->getSource());
    }

    public function testTransportExceptionEventCanBeCreated()
    {
        $transport = $this->getMockBuilder('Swift_Transport')->getMock();
        $ex = new Swift_TransportException('');
        $evt = $this->_dispatcher->createTransportExceptionEvent($transport, $ex);
        $this->assertInstanceof('Swift_Events_TransportExceptionEvent', $evt);
        $this->assertSame($transport, $evt->getSource());
        $this->assertSame($ex, $evt->getException());
    }

    public function testListenersAreNotifiedOfDispatchedEvent()
    {
        $transport = $this->getMockBuilder('Swift_Transport')->getMock();

        $evt = $this->_dispatcher->createTransportChangeEvent($transport);

        $listenerA = $this->getMockBuilder('Swift_Events_TransportChangeListener')->getMock();
        $listenerB = $this->getMockBuilder('Swift_Events_TransportChangeListener')->getMock();

        $this->_dispatcher->bindEventListener($listenerA);
        $this->_dispatcher->bindEventListener($listenerB);

        $listenerA->expects($this->once())
                  ->method('transportStarted')
                  ->with($evt);
        $listenerB->expects($this->once())
                  ->method('transportStarted')
                  ->with($evt);

        $this->_dispatcher->dispatchEvent($evt, 'transportStarted');
    }

    public function testListenersAreOnlyCalledIfImplementingCorrectInterface()
    {
        $transport = $this->getMockBuilder('Swift_Transport')->getMock();
        $message = $this->getMockBuilder('Swift_Mime_Message')->getMock();

        $evt = $this->_dispatcher->createSendEvent($transport, $message);

        $targetListener = $this->getMockBuilder('Swift_Events_SendListener')->getMock();
        $otherListener = $this->getMockBuilder('DummyListener')->getMock();

        $this->_dispatcher->bindEventListener($targetListener);
        $this->_dispatcher->bindEventListener($otherListener);

        $targetListener->expects($this->once())
                       ->method('sendPerformed')
                       ->with($evt);
        $otherListener->expects($this->never())
                    ->method('sendPerformed');

        $this->_dispatcher->dispatchEvent($evt, 'sendPerformed');
    }

    public function testListenersCanCancelBubblingOfEvent()
    {
        $transport = $this->getMockBuilder('Swift_Transport')->getMock();
        $message = $this->getMockBuilder('Swift_Mime_Message')->getMock();

        $evt = $this->_dispatcher->createSendEvent($transport, $message);

        $listenerA = $this->getMockBuilder('Swift_Events_SendListener')->getMock();
        $listenerB = $this->getMockBuilder('Swift_Events_SendListener')->getMock();

        $this->_dispatcher->bindEventListener($listenerA);
        $this->_dispatcher->bindEventListener($listenerB);

        $listenerA->expects($this->once())
                  ->method('sendPerformed')
                  ->with($evt)
                  ->will($this->returnCallback(function ($object) {
                      $object->cancelBubble(true);
                  }));
        $listenerB->expects($this->never())
                  ->method('sendPerformed');

        $this->_dispatcher->dispatchEvent($evt, 'sendPerformed');

        $this->assertTrue($evt->bubbleCancelled());
    }

    private function _createDispatcher(array $map)
    {
        return new Swift_Events_SimpleEventDispatcher($map);
    }
}

class DummyListener implements Swift_Events_EventListener
{
    public function sendPerformed(Swift_Events_SendEvent $evt)
    {
    }
}
<?php

class Swift_Events_TransportChangeEventTest extends \PHPUnit_Framework_TestCase
{
    public function testGetTransportReturnsTransport()
    {
        $transport = $this->_createTransport();
        $evt = $this->_createEvent($transport);
        $ref = $evt->getTransport();
        $this->assertEquals($transport, $ref);
    }

    public function testSourceIsTransport()
    {
        $transport = $this->_createTransport();
        $evt = $this->_createEvent($transport);
        $ref = $evt->getSource();
        $this->assertEquals($transport, $ref);
    }

    // -- Creation Methods

    private function _createEvent(Swift_Transport $source)
    {
        return new Swift_Events_TransportChangeEvent($source);
    }

    private function _createTransport()
    {
        return $this->getMockBuilder('Swift_Transport')->getMock();
    }
}
<?php

class Swift_Events_TransportExceptionEventTest extends \PHPUnit_Framework_TestCase
{
    public function testExceptionCanBeFetchViaGetter()
    {
        $ex = $this->_createException();
        $transport = $this->_createTransport();
        $evt = $this->_createEvent($transport, $ex);
        $ref = $evt->getException();
        $this->assertEquals($ex, $ref,
            '%s: Exception should be available via getException()'
            );
    }

    public function testSourceIsTransport()
    {
        $ex = $this->_createException();
        $transport = $this->_createTransport();
        $evt = $this->_createEvent($transport, $ex);
        $ref = $evt->getSource();
        $this->assertEquals($transport, $ref,
            '%s: Transport should be available via getSource()'
            );
    }

    // -- Creation Methods

    private function _createEvent(Swift_Transport $transport, Swift_TransportException $ex)
    {
        return new Swift_Events_TransportExceptionEvent($transport, $ex);
    }

    private function _createTransport()
    {
        return $this->getMockBuilder('Swift_Transport')->getMock();
    }

    private function _createException()
    {
        return new Swift_TransportException('');
    }
}
<?php

class Swift_KeyCache_ArrayKeyCacheTest extends \PHPUnit_Framework_TestCase
{
    private $_key1 = 'key1';
    private $_key2 = 'key2';

    public function testStringDataCanBeSetAndFetched()
    {
        $is = $this->_createKeyCacheInputStream();
        $cache = $this->_createCache($is);
        $cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->assertEquals('test', $cache->getString($this->_key1, 'foo'));
    }

    public function testStringDataCanBeOverwritten()
    {
        $is = $this->_createKeyCacheInputStream();
        $cache = $this->_createCache($is);
        $cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $cache->setString(
            $this->_key1, 'foo', 'whatever', Swift_KeyCache::MODE_WRITE
            );

        $this->assertEquals('whatever', $cache->getString($this->_key1, 'foo'));
    }

    public function testStringDataCanBeAppended()
    {
        $is = $this->_createKeyCacheInputStream();
        $cache = $this->_createCache($is);
        $cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $cache->setString(
            $this->_key1, 'foo', 'ing', Swift_KeyCache::MODE_APPEND
            );

        $this->assertEquals('testing', $cache->getString($this->_key1, 'foo'));
    }

    public function testHasKeyReturnValue()
    {
        $is = $this->_createKeyCacheInputStream();
        $cache = $this->_createCache($is);
        $cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );

        $this->assertTrue($cache->hasKey($this->_key1, 'foo'));
    }

    public function testNsKeyIsWellPartitioned()
    {
        $is = $this->_createKeyCacheInputStream();
        $cache = $this->_createCache($is);
        $cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $cache->setString(
            $this->_key2, 'foo', 'ing', Swift_KeyCache::MODE_WRITE
            );

        $this->assertEquals('test', $cache->getString($this->_key1, 'foo'));
        $this->assertEquals('ing', $cache->getString($this->_key2, 'foo'));
    }

    public function testItemKeyIsWellPartitioned()
    {
        $is = $this->_createKeyCacheInputStream();
        $cache = $this->_createCache($is);
        $cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $cache->setString(
            $this->_key1, 'bar', 'ing', Swift_KeyCache::MODE_WRITE
            );

        $this->assertEquals('test', $cache->getString($this->_key1, 'foo'));
        $this->assertEquals('ing', $cache->getString($this->_key1, 'bar'));
    }

    public function testByteStreamCanBeImported()
    {
        $os = $this->_createOutputStream();
        $os->expects($this->at(0))
           ->method('read')
           ->will($this->returnValue('abc'));
        $os->expects($this->at(1))
           ->method('read')
           ->will($this->returnValue('def'));
        $os->expects($this->at(2))
           ->method('read')
           ->will($this->returnValue(false));

        $is = $this->_createKeyCacheInputStream();
        $cache = $this->_createCache($is);
        $cache->importFromByteStream(
            $this->_key1, 'foo', $os, Swift_KeyCache::MODE_WRITE
            );
        $this->assertEquals('abcdef', $cache->getString($this->_key1, 'foo'));
    }

    public function testByteStreamCanBeAppended()
    {
        $os1 = $this->_createOutputStream();
        $os1->expects($this->at(0))
            ->method('read')
            ->will($this->returnValue('abc'));
        $os1->expects($this->at(1))
            ->method('read')
            ->will($this->returnValue('def'));
        $os1->expects($this->at(2))
            ->method('read')
            ->will($this->returnValue(false));

        $os2 = $this->_createOutputStream();
        $os2->expects($this->at(0))
            ->method('read')
            ->will($this->returnValue('xyz'));
        $os2->expects($this->at(1))
            ->method('read')
            ->will($this->returnValue('uvw'));
        $os2->expects($this->at(2))
            ->method('read')
            ->will($this->returnValue(false));

        $is = $this->_createKeyCacheInputStream(true);

        $cache = $this->_createCache($is);

        $cache->importFromByteStream(
            $this->_key1, 'foo', $os1, Swift_KeyCache::MODE_APPEND
            );
        $cache->importFromByteStream(
            $this->_key1, 'foo', $os2, Swift_KeyCache::MODE_APPEND
            );

        $this->assertEquals('abcdefxyzuvw', $cache->getString($this->_key1, 'foo'));
    }

    public function testByteStreamAndStringCanBeAppended()
    {
        $os = $this->_createOutputStream();
        $os->expects($this->at(0))
           ->method('read')
           ->will($this->returnValue('abc'));
        $os->expects($this->at(1))
           ->method('read')
           ->will($this->returnValue('def'));
        $os->expects($this->at(2))
           ->method('read')
           ->will($this->returnValue(false));

        $is = $this->_createKeyCacheInputStream(true);

        $cache = $this->_createCache($is);

        $cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_APPEND
            );
        $cache->importFromByteStream(
            $this->_key1, 'foo', $os, Swift_KeyCache::MODE_APPEND
            );
        $this->assertEquals('testabcdef', $cache->getString($this->_key1, 'foo'));
    }

    public function testDataCanBeExportedToByteStream()
    {
        //See acceptance test for more detail
        $is = $this->_createInputStream();
        $is->expects($this->atLeastOnce())
           ->method('write');

        $kcis = $this->_createKeyCacheInputStream(true);

        $cache = $this->_createCache($kcis);

        $cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );

        $cache->exportToByteStream($this->_key1, 'foo', $is);
    }

    public function testKeyCanBeCleared()
    {
        $is = $this->_createKeyCacheInputStream();
        $cache = $this->_createCache($is);

        $cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $this->assertTrue($cache->hasKey($this->_key1, 'foo'));
        $cache->clearKey($this->_key1, 'foo');
        $this->assertFalse($cache->hasKey($this->_key1, 'foo'));
    }

    public function testNsKeyCanBeCleared()
    {
        $is = $this->_createKeyCacheInputStream();
        $cache = $this->_createCache($is);

        $cache->setString(
            $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE
            );
        $cache->setString(
            $this->_key1, 'bar', 'xyz', Swift_KeyCache::MODE_WRITE
            );
        $this->assertTrue($cache->hasKey($this->_key1, 'foo'));
        $this->assertTrue($cache->hasKey($this->_key1, 'bar'));
        $cache->clearAll($this->_key1);
        $this->assertFalse($cache->hasKey($this->_key1, 'foo'));
        $this->assertFalse($cache->hasKey($this->_key1, 'bar'));
    }

    // -- Creation methods

    private function _createCache($is)
    {
        return new Swift_KeyCache_ArrayKeyCache($is);
    }

    private function _createKeyCacheInputStream()
    {
        return $this->getMockBuilder('Swift_KeyCache_KeyCacheInputStream')->getMock();
    }

    private function _createOutputStream()
    {
        return $this->getMockBuilder('Swift_OutputByteStream')->getMock();
    }

    private function _createInputStream()
    {
        return $this->getMockBuilder('Swift_InputByteStream')->getMock();
    }
}
<?php

class Swift_KeyCache_SimpleKeyCacheInputStreamTest extends \PHPUnit_Framework_TestCase
{
    private $_nsKey = 'ns1';

    public function testStreamWritesToCacheInAppendMode()
    {
        $cache = $this->getMockBuilder('Swift_KeyCache')->getMock();
        $cache->expects($this->at(0))
              ->method('setString')
              ->with($this->_nsKey, 'foo', 'a', Swift_KeyCache::MODE_APPEND);
        $cache->expects($this->at(1))
              ->method('setString')
              ->with($this->_nsKey, 'foo', 'b', Swift_KeyCache::MODE_APPEND);
        $cache->expects($this->at(2))
              ->method('setString')
              ->with($this->_nsKey, 'foo', 'c', Swift_KeyCache::MODE_APPEND);

        $stream = new Swift_KeyCache_SimpleKeyCacheInputStream();
        $stream->setKeyCache($cache);
        $stream->setNsKey($this->_nsKey);
        $stream->setItemKey('foo');

        $stream->write('a');
        $stream->write('b');
        $stream->write('c');
    }

    public function testFlushContentClearsKey()
    {
        $cache = $this->getMockBuilder('Swift_KeyCache')->getMock();
        $cache->expects($this->once())
              ->method('clearKey')
              ->with($this->_nsKey, 'foo');

        $stream = new Swift_KeyCache_SimpleKeyCacheInputStream();
        $stream->setKeyCache($cache);
        $stream->setNsKey($this->_nsKey);
        $stream->setItemKey('foo');

        $stream->flushBuffers();
    }

    public function testClonedStreamStillReferencesSameCache()
    {
        $cache = $this->getMockBuilder('Swift_KeyCache')->getMock();
        $cache->expects($this->at(0))
              ->method('setString')
              ->with($this->_nsKey, 'foo', 'a', Swift_KeyCache::MODE_APPEND);
        $cache->expects($this->at(1))
              ->method('setString')
              ->with($this->_nsKey, 'foo', 'b', Swift_KeyCache::MODE_APPEND);
        $cache->expects($this->at(2))
              ->method('setString')
              ->with('test', 'bar', 'x', Swift_KeyCache::MODE_APPEND);

        $stream = new Swift_KeyCache_SimpleKeyCacheInputStream();
        $stream->setKeyCache($cache);
        $stream->setNsKey($this->_nsKey);
        $stream->setItemKey('foo');

        $stream->write('a');
        $stream->write('b');

        $newStream = clone $stream;
        $newStream->setKeyCache($cache);
        $newStream->setNsKey('test');
        $newStream->setItemKey('bar');

        $newStream->write('x');
    }
}
<?php

class Swift_Mailer_ArrayRecipientIteratorTest extends \PHPUnit_Framework_TestCase
{
    public function testHasNextReturnsFalseForEmptyArray()
    {
        $it = new Swift_Mailer_ArrayRecipientIterator(array());
        $this->assertFalse($it->hasNext());
    }

    public function testHasNextReturnsTrueIfItemsLeft()
    {
        $it = new Swift_Mailer_ArrayRecipientIterator(array('foo@bar' => 'Foo'));
        $this->assertTrue($it->hasNext());
    }

    public function testReadingToEndOfListCausesHasNextToReturnFalse()
    {
        $it = new Swift_Mailer_ArrayRecipientIterator(array('foo@bar' => 'Foo'));
        $this->assertTrue($it->hasNext());
        $it->nextRecipient();
        $this->assertFalse($it->hasNext());
    }

    public function testReturnedValueHasPreservedKeyValuePair()
    {
        $it = new Swift_Mailer_ArrayRecipientIterator(array('foo@bar' => 'Foo'));
        $this->assertEquals(array('foo@bar' => 'Foo'), $it->nextRecipient());
    }

    public function testIteratorMovesNextAfterEachIteration()
    {
        $it = new Swift_Mailer_ArrayRecipientIterator(array(
            'foo@bar' => 'Foo',
            'zip@button' => 'Zip thing',
            'test@test' => null,
            ));
        $this->assertEquals(array('foo@bar' => 'Foo'), $it->nextRecipient());
        $this->assertEquals(array('zip@button' => 'Zip thing'), $it->nextRecipient());
        $this->assertEquals(array('test@test' => null), $it->nextRecipient());
    }
}
<?php

class Swift_MailerTest extends \SwiftMailerTestCase
{
    public function testTransportIsStartedWhenSending()
    {
        $transport = $this->_createTransport();
        $message = $this->_createMessage();

        $started = false;
        $transport->shouldReceive('isStarted')
                  ->zeroOrMoreTimes()
                  ->andReturnUsing(function () use (&$started) {
                      return $started;
                  });
        $transport->shouldReceive('start')
                  ->once()
                  ->andReturnUsing(function () use (&$started) {
                      $started = true;

                      return;
                  });

        $mailer = $this->_createMailer($transport);
        $mailer->send($message);
    }

    public function testTransportIsOnlyStartedOnce()
    {
        $transport = $this->_createTransport();
        $message = $this->_createMessage();

        $started = false;
        $transport->shouldReceive('isStarted')
                  ->zeroOrMoreTimes()
                  ->andReturnUsing(function () use (&$started) {
                      return $started;
                  });
        $transport->shouldReceive('start')
                  ->once()
                  ->andReturnUsing(function () use (&$started) {
                      $started = true;

                      return;
                  });

        $mailer = $this->_createMailer($transport);
        for ($i = 0; $i < 10; ++$i) {
            $mailer->send($message);
        }
    }

    public function testMessageIsPassedToTransport()
    {
        $transport = $this->_createTransport();
        $message = $this->_createMessage();
        $transport->shouldReceive('send')
                  ->once()
                  ->with($message, \Mockery::any());

        $mailer = $this->_createMailer($transport);
        $mailer->send($message);
    }

    public function testSendReturnsCountFromTransport()
    {
        $transport = $this->_createTransport();
        $message = $this->_createMessage();
        $transport->shouldReceive('send')
                  ->once()
                  ->with($message, \Mockery::any())
                  ->andReturn(57);

        $mailer = $this->_createMailer($transport);
        $this->assertEquals(57, $mailer->send($message));
    }

    public function testFailedRecipientReferenceIsPassedToTransport()
    {
        $failures = array();

        $transport = $this->_createTransport();
        $message = $this->_createMessage();
        $transport->shouldReceive('send')
                  ->once()
                  ->with($message, $failures)
                  ->andReturn(57);

        $mailer = $this->_createMailer($transport);
        $mailer->send($message, $failures);
    }

    public function testSendRecordsRfcComplianceExceptionAsEntireSendFailure()
    {
        $failures = array();

        $rfcException = new Swift_RfcComplianceException('test');
        $transport = $this->_createTransport();
        $message = $this->_createMessage();
        $message->shouldReceive('getTo')
                  ->once()
                  ->andReturn(array('foo&invalid' => 'Foo', 'bar@valid.tld' => 'Bar'));
        $transport->shouldReceive('send')
                  ->once()
                  ->with($message, $failures)
                  ->andThrow($rfcException);

        $mailer = $this->_createMailer($transport);
        $this->assertEquals(0, $mailer->send($message, $failures), '%s: Should return 0');
        $this->assertEquals(array('foo&invalid', 'bar@valid.tld'), $failures, '%s: Failures should contain all addresses since the entire message failed to compile');
    }

    public function testRegisterPluginDelegatesToTransport()
    {
        $plugin = $this->_createPlugin();
        $transport = $this->_createTransport();
        $mailer = $this->_createMailer($transport);

        $transport->shouldReceive('registerPlugin')
                  ->once()
                  ->with($plugin);

        $mailer->registerPlugin($plugin);
    }

    // -- Creation methods

    private function _createPlugin()
    {
        return $this->getMockery('Swift_Events_EventListener')->shouldIgnoreMissing();
    }

    private function _createTransport()
    {
        return $this->getMockery('Swift_Transport')->shouldIgnoreMissing();
    }

    private function _createMessage()
    {
        return $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing();
    }

    private function _createMailer(Swift_Transport $transport)
    {
        return new Swift_Mailer($transport);
    }
}
<?php

class Swift_MessageTest extends \PHPUnit_Framework_TestCase
{
    public function testCloning()
    {
        $message1 = new Swift_Message('subj', 'body', 'ctype');
        $message2 = new Swift_Message('subj', 'body', 'ctype');
        $message1_clone = clone $message1;

        $this->_recursiveObjectCloningCheck($message1, $message2, $message1_clone);
    }

    public function testCloningWithSigners()
    {
        $message1 = new Swift_Message('subj', 'body', 'ctype');
        $signer = new Swift_Signers_DKIMSigner(dirname(dirname(__DIR__)).'/_samples/dkim/dkim.test.priv', 'test.example', 'example');
        $message1->attachSigner($signer);
        $message2 = new Swift_Message('subj', 'body', 'ctype');
        $signer = new Swift_Signers_DKIMSigner(dirname(dirname(__DIR__)).'/_samples/dkim/dkim.test.priv', 'test.example', 'example');
        $message2->attachSigner($signer);
        $message1_clone = clone $message1;

        $this->_recursiveObjectCloningCheck($message1, $message2, $message1_clone);
    }

    public function testBodySwap()
    {
        $message1 = new Swift_Message('Test');
        $html = Swift_MimePart::newInstance('<html></html>', 'text/html');
        $html->getHeaders()->addTextHeader('X-Test-Remove', 'Test-Value');
        $html->getHeaders()->addTextHeader('X-Test-Alter', 'Test-Value');
        $message1->attach($html);
        $source = $message1->toString();
        $message2 = clone $message1;
        $message2->setSubject('Message2');
        foreach ($message2->getChildren() as $child) {
            $child->setBody('Test');
            $child->getHeaders()->removeAll('X-Test-Remove');
            $child->getHeaders()->get('X-Test-Alter')->setValue('Altered');
        }
        $final = $message1->toString();
        if ($source != $final) {
            $this->fail("Difference although object cloned \n [".$source."]\n[".$final."]\n");
        }
        $final = $message2->toString();
        if ($final == $source) {
            $this->fail('Two body matches although they should differ'."\n [".$source."]\n[".$final."]\n");
        }
        $id_1 = $message1->getId();
        $id_2 = $message2->getId();
        $this->assertEquals($id_1, $id_2, 'Message Ids differ');
        $id_2 = $message2->generateId();
        $this->assertNotEquals($id_1, $id_2, 'Message Ids are the same');
    }

    // -- Private helpers
    protected function _recursiveObjectCloningCheck($obj1, $obj2, $obj1_clone)
    {
        $obj1_properties = (array) $obj1;
        $obj2_properties = (array) $obj2;
        $obj1_clone_properties = (array) $obj1_clone;

        foreach ($obj1_properties as $property => $value) {
            if (is_object($value)) {
                $obj1_value = $obj1_properties[$property];
                $obj2_value = $obj2_properties[$property];
                $obj1_clone_value = $obj1_clone_properties[$property];

                if ($obj1_value !== $obj2_value) {
                    // two separetely instanciated objects property not referencing same object
                    $this->assertFalse(
                        // but object's clone does - not everything copied
                        $obj1_value === $obj1_clone_value,
                        "Property `$property` cloning error: source and cloned objects property is referencing same object"
                    );
                } else {
                    // two separetely instanciated objects have same reference
                    $this->assertFalse(
                        // but object's clone doesn't - overdone making copies
                        $obj1_value !== $obj1_clone_value,
                        "Property `$property` not properly cloned: it should reference same object as cloning source (overdone copping)"
                    );
                }
                // recurse
                $this->_recursiveObjectCloningCheck($obj1_value, $obj2_value, $obj1_clone_value);
            } elseif (is_array($value)) {
                $obj1_value = $obj1_properties[$property];
                $obj2_value = $obj2_properties[$property];
                $obj1_clone_value = $obj1_clone_properties[$property];

                return $this->_recursiveArrayCloningCheck($obj1_value, $obj2_value, $obj1_clone_value);
            }
        }
    }

    protected function _recursiveArrayCloningCheck($array1, $array2, $array1_clone)
    {
        foreach ($array1 as $key => $value) {
            if (is_object($value)) {
                $arr1_value = $array1[$key];
                $arr2_value = $array2[$key];
                $arr1_clone_value = $array1_clone[$key];
                if ($arr1_value !== $arr2_value) {
                    // two separetely instanciated objects property not referencing same object
                    $this->assertFalse(
                        // but object's clone does - not everything copied
                        $arr1_value === $arr1_clone_value,
                        "Key `$key` cloning error: source and cloned objects property is referencing same object"
                    );
                } else {
                    // two separetely instanciated objects have same reference
                    $this->assertFalse(
                        // but object's clone doesn't - overdone making copies
                        $arr1_value !== $arr1_clone_value,
                        "Key `$key` not properly cloned: it should reference same object as cloning source (overdone copping)"
                    );
                }
                // recurse
                $this->_recursiveObjectCloningCheck($arr1_value, $arr2_value, $arr1_clone_value);
            } elseif (is_array($value)) {
                $arr1_value = $array1[$key];
                $arr2_value = $array2[$key];
                $arr1_clone_value = $array1_clone[$key];

                return $this->_recursiveArrayCloningCheck($arr1_value, $arr2_value, $arr1_clone_value);
            }
        }
    }
}
<?php

require_once dirname(dirname(dirname(__DIR__))).'/fixtures/MimeEntityFixture.php';

abstract class Swift_Mime_AbstractMimeEntityTest extends \SwiftMailerTestCase
{
    public function testGetHeadersReturnsHeaderSet()
    {
        $headers = $this->_createHeaderSet();
        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $this->assertSame($headers, $entity->getHeaders());
    }

    public function testContentTypeIsReturnedFromHeader()
    {
        $ctype = $this->_createHeader('Content-Type', 'image/jpeg-test');
        $headers = $this->_createHeaderSet(array('Content-Type' => $ctype));
        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $this->assertEquals('image/jpeg-test', $entity->getContentType());
    }

    public function testContentTypeIsSetInHeader()
    {
        $ctype = $this->_createHeader('Content-Type', 'text/plain', array(), false);
        $headers = $this->_createHeaderSet(array('Content-Type' => $ctype));

        $ctype->shouldReceive('setFieldBodyModel')
              ->once()
              ->with('image/jpeg');
        $ctype->shouldReceive('setFieldBodyModel')
              ->zeroOrMoreTimes()
              ->with(\Mockery::not('image/jpeg'));

        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $entity->setContentType('image/jpeg');
    }

    public function testContentTypeHeaderIsAddedIfNoneSet()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addParameterizedHeader')
                ->once()
                ->with('Content-Type', 'image/jpeg');
        $headers->shouldReceive('addParameterizedHeader')
                ->zeroOrMoreTimes();

        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $entity->setContentType('image/jpeg');
    }

    public function testContentTypeCanBeSetViaSetBody()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addParameterizedHeader')
                ->once()
                ->with('Content-Type', 'text/html');
        $headers->shouldReceive('addParameterizedHeader')
                ->zeroOrMoreTimes();

        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $entity->setBody('<b>foo</b>', 'text/html');
    }

    public function testGetEncoderFromConstructor()
    {
        $encoder = $this->_createEncoder('base64');
        $entity = $this->_createEntity($this->_createHeaderSet(), $encoder,
            $this->_createCache()
            );
        $this->assertSame($encoder, $entity->getEncoder());
    }

    public function testSetAndGetEncoder()
    {
        $encoder = $this->_createEncoder('base64');
        $headers = $this->_createHeaderSet();
        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $entity->setEncoder($encoder);
        $this->assertSame($encoder, $entity->getEncoder());
    }

    public function testSettingEncoderUpdatesTransferEncoding()
    {
        $encoder = $this->_createEncoder('base64');
        $encoding = $this->_createHeader(
            'Content-Transfer-Encoding', '8bit', array(), false
            );
        $headers = $this->_createHeaderSet(array(
            'Content-Transfer-Encoding' => $encoding,
            ));
        $encoding->shouldReceive('setFieldBodyModel')
                 ->once()
                 ->with('base64');
        $encoding->shouldReceive('setFieldBodyModel')
                 ->zeroOrMoreTimes();

        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $entity->setEncoder($encoder);
    }

    public function testSettingEncoderAddsEncodingHeaderIfNonePresent()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addTextHeader')
                ->once()
                ->with('Content-Transfer-Encoding', 'something');
        $headers->shouldReceive('addTextHeader')
                ->zeroOrMoreTimes();

        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $entity->setEncoder($this->_createEncoder('something'));
    }

    public function testIdIsReturnedFromHeader()
    {
        /* -- RFC 2045, 7.
        In constructing a high-level user agent, it may be desirable to allow
        one body to make reference to another.  Accordingly, bodies may be
        labelled using the "Content-ID" header field, which is syntactically
        identical to the "Message-ID" header field
        */

        $cid = $this->_createHeader('Content-ID', 'zip@button');
        $headers = $this->_createHeaderSet(array('Content-ID' => $cid));
        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $this->assertEquals('zip@button', $entity->getId());
    }

    public function testIdIsSetInHeader()
    {
        $cid = $this->_createHeader('Content-ID', 'zip@button', array(), false);
        $headers = $this->_createHeaderSet(array('Content-ID' => $cid));

        $cid->shouldReceive('setFieldBodyModel')
            ->once()
            ->with('foo@bar');
        $cid->shouldReceive('setFieldBodyModel')
            ->zeroOrMoreTimes();

        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $entity->setId('foo@bar');
    }

    public function testIdIsAutoGenerated()
    {
        $entity = $this->_createEntity($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertRegExp('/^.*?@.*?$/D', $entity->getId());
    }

    public function testGenerateIdCreatesNewId()
    {
        $headers = $this->_createHeaderSet();
        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $id1 = $entity->generateId();
        $id2 = $entity->generateId();
        $this->assertNotEquals($id1, $id2);
    }

    public function testGenerateIdSetsNewId()
    {
        $headers = $this->_createHeaderSet();
        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $id = $entity->generateId();
        $this->assertEquals($id, $entity->getId());
    }

    public function testDescriptionIsReadFromHeader()
    {
        /* -- RFC 2045, 8.
        The ability to associate some descriptive information with a given
        body is often desirable.  For example, it may be useful to mark an
        "image" body as "a picture of the Space Shuttle Endeavor."  Such text
        may be placed in the Content-Description header field.  This header
        field is always optional.
        */

        $desc = $this->_createHeader('Content-Description', 'something');
        $headers = $this->_createHeaderSet(array('Content-Description' => $desc));
        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $this->assertEquals('something', $entity->getDescription());
    }

    public function testDescriptionIsSetInHeader()
    {
        $desc = $this->_createHeader('Content-Description', '', array(), false);
        $desc->shouldReceive('setFieldBodyModel')->once()->with('whatever');

        $headers = $this->_createHeaderSet(array('Content-Description' => $desc));

        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $entity->setDescription('whatever');
    }

    public function testDescriptionHeaderIsAddedIfNotPresent()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addTextHeader')
                ->once()
                ->with('Content-Description', 'whatever');
        $headers->shouldReceive('addTextHeader')
                ->zeroOrMoreTimes();

        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $entity->setDescription('whatever');
    }

    public function testSetAndGetMaxLineLength()
    {
        $entity = $this->_createEntity($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $entity->setMaxLineLength(60);
        $this->assertEquals(60, $entity->getMaxLineLength());
    }

    public function testEncoderIsUsedForStringGeneration()
    {
        $encoder = $this->_createEncoder('base64', false);
        $encoder->expects($this->once())
                ->method('encodeString')
                ->with('blah');

        $entity = $this->_createEntity($this->_createHeaderSet(),
            $encoder, $this->_createCache()
            );
        $entity->setBody('blah');
        $entity->toString();
    }

    public function testMaxLineLengthIsProvidedWhenEncoding()
    {
        $encoder = $this->_createEncoder('base64', false);
        $encoder->expects($this->once())
                ->method('encodeString')
                ->with('blah', 0, 65);

        $entity = $this->_createEntity($this->_createHeaderSet(),
            $encoder, $this->_createCache()
            );
        $entity->setBody('blah');
        $entity->setMaxLineLength(65);
        $entity->toString();
    }

    public function testHeadersAppearInString()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('toString')
                ->once()
                ->andReturn(
                    "Content-Type: text/plain; charset=utf-8\r\n".
                    "X-MyHeader: foobar\r\n"
                );

        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $this->assertEquals(
            "Content-Type: text/plain; charset=utf-8\r\n".
            "X-MyHeader: foobar\r\n",
            $entity->toString()
            );
    }

    public function testSetAndGetBody()
    {
        $entity = $this->_createEntity($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $entity->setBody("blah\r\nblah!");
        $this->assertEquals("blah\r\nblah!", $entity->getBody());
    }

    public function testBodyIsAppended()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('toString')
                ->once()
                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");

        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $entity->setBody("blah\r\nblah!");
        $this->assertEquals(
            "Content-Type: text/plain; charset=utf-8\r\n".
            "\r\n".
            "blah\r\nblah!",
            $entity->toString()
            );
    }

    public function testGetBodyReturnsStringFromByteStream()
    {
        $os = $this->_createOutputStream('byte stream string');
        $entity = $this->_createEntity($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $entity->setBody($os);
        $this->assertEquals('byte stream string', $entity->getBody());
    }

    public function testByteStreamBodyIsAppended()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $os = $this->_createOutputStream('streamed');
        $headers->shouldReceive('toString')
                ->once()
                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");

        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $entity->setBody($os);
        $this->assertEquals(
            "Content-Type: text/plain; charset=utf-8\r\n".
            "\r\n".
            'streamed',
            $entity->toString()
            );
    }

    public function testBoundaryCanBeRetrieved()
    {
        /* -- RFC 2046, 5.1.1.
     boundary := 0*69<bchars> bcharsnospace

     bchars := bcharsnospace / " "

     bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
                                            "+" / "_" / "," / "-" / "." /
                                            "/" / ":" / "=" / "?"
        */

        $entity = $this->_createEntity($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertRegExp(
            '/^[a-zA-Z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-zA-Z0-9\'\(\)\+_\-,\.\/:=\?]$/D',
            $entity->getBoundary()
            );
    }

    public function testBoundaryNeverChanges()
    {
        $entity = $this->_createEntity($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $firstBoundary = $entity->getBoundary();
        for ($i = 0; $i < 10; ++$i) {
            $this->assertEquals($firstBoundary, $entity->getBoundary());
        }
    }

    public function testBoundaryCanBeSet()
    {
        $entity = $this->_createEntity($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $entity->setBoundary('foobar');
        $this->assertEquals('foobar', $entity->getBoundary());
    }

    public function testAddingChildrenGeneratesBoundaryInHeaders()
    {
        $child = $this->_createChild();
        $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false);
        $cType->shouldReceive('setParameter')
              ->once()
              ->with('boundary', \Mockery::any());
        $cType->shouldReceive('setParameter')
              ->zeroOrMoreTimes();

        $entity = $this->_createEntity($this->_createHeaderSet(array(
            'Content-Type' => $cType,
            )),
            $this->_createEncoder(), $this->_createCache()
            );
        $entity->setChildren(array($child));
    }

    public function testChildrenOfLevelAttachmentAndLessCauseMultipartMixed()
    {
        for ($level = Swift_Mime_MimeEntity::LEVEL_MIXED;
            $level > Swift_Mime_MimeEntity::LEVEL_TOP; $level /= 2) {
            $child = $this->_createChild($level);
            $cType = $this->_createHeader(
                'Content-Type', 'text/plain', array(), false
                );
            $cType->shouldReceive('setFieldBodyModel')
                  ->once()
                  ->with('multipart/mixed');
            $cType->shouldReceive('setFieldBodyModel')
                  ->zeroOrMoreTimes();

            $entity = $this->_createEntity($this->_createHeaderSet(array(
                'Content-Type' => $cType, )),
                $this->_createEncoder(), $this->_createCache()
                );
            $entity->setChildren(array($child));
        }
    }

    public function testChildrenOfLevelAlternativeAndLessCauseMultipartAlternative()
    {
        for ($level = Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE;
            $level > Swift_Mime_MimeEntity::LEVEL_MIXED; $level /= 2) {
            $child = $this->_createChild($level);
            $cType = $this->_createHeader(
                'Content-Type', 'text/plain', array(), false
                );
            $cType->shouldReceive('setFieldBodyModel')
                  ->once()
                  ->with('multipart/alternative');
            $cType->shouldReceive('setFieldBodyModel')
                  ->zeroOrMoreTimes();

            $entity = $this->_createEntity($this->_createHeaderSet(array(
                'Content-Type' => $cType, )),
                $this->_createEncoder(), $this->_createCache()
                );
            $entity->setChildren(array($child));
        }
    }

    public function testChildrenOfLevelRelatedAndLessCauseMultipartRelated()
    {
        for ($level = Swift_Mime_MimeEntity::LEVEL_RELATED;
            $level > Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE; $level /= 2) {
            $child = $this->_createChild($level);
            $cType = $this->_createHeader(
                'Content-Type', 'text/plain', array(), false
                );
            $cType->shouldReceive('setFieldBodyModel')
                  ->once()
                  ->with('multipart/related');
            $cType->shouldReceive('setFieldBodyModel')
                  ->zeroOrMoreTimes();

            $entity = $this->_createEntity($this->_createHeaderSet(array(
                'Content-Type' => $cType, )),
                $this->_createEncoder(), $this->_createCache()
                );
            $entity->setChildren(array($child));
        }
    }

    public function testHighestLevelChildDeterminesContentType()
    {
        $combinations = array(
            array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED,
                Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
                Swift_Mime_MimeEntity::LEVEL_RELATED,
                ),
                'type' => 'multipart/mixed',
                ),
            array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED,
                Swift_Mime_MimeEntity::LEVEL_RELATED,
                ),
                'type' => 'multipart/mixed',
                ),
            array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED,
                Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
                ),
                'type' => 'multipart/mixed',
                ),
            array('levels' => array(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
                Swift_Mime_MimeEntity::LEVEL_RELATED,
                ),
                'type' => 'multipart/alternative',
                ),
            );

        foreach ($combinations as $combination) {
            $children = array();
            foreach ($combination['levels'] as $level) {
                $children[] = $this->_createChild($level);
            }

            $cType = $this->_createHeader(
                'Content-Type', 'text/plain', array(), false
                );
            $cType->shouldReceive('setFieldBodyModel')
                  ->once()
                  ->with($combination['type']);

            $headerSet = $this->_createHeaderSet(array('Content-Type' => $cType));
            $headerSet->shouldReceive('newInstance')
                      ->zeroOrMoreTimes()
                      ->andReturnUsing(function () use ($headerSet) {
                          return $headerSet;
                      });
            $entity = $this->_createEntity($headerSet,
                $this->_createEncoder(), $this->_createCache()
                );
            $entity->setChildren($children);
        }
    }

    public function testChildrenAppearNestedInString()
    {
        /* -- RFC 2046, 5.1.1.
     (excerpt too verbose to paste here)
     */

        $headers = $this->_createHeaderSet(array(), false);

        $child1 = new MimeEntityFixture(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
            "Content-Type: text/plain\r\n".
            "\r\n".
            'foobar', 'text/plain'
            );

        $child2 = new MimeEntityFixture(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
            "Content-Type: text/html\r\n".
            "\r\n".
            '<b>foobar</b>', 'text/html'
            );

        $headers->shouldReceive('toString')
              ->zeroOrMoreTimes()
              ->andReturn("Content-Type: multipart/alternative; boundary=\"xxx\"\r\n");

        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $entity->setBoundary('xxx');
        $entity->setChildren(array($child1, $child2));

        $this->assertEquals(
            "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n".
            "\r\n".
            "\r\n--xxx\r\n".
            "Content-Type: text/plain\r\n".
            "\r\n".
            "foobar\r\n".
            "\r\n--xxx\r\n".
            "Content-Type: text/html\r\n".
            "\r\n".
            "<b>foobar</b>\r\n".
            "\r\n--xxx--\r\n",
            $entity->toString()
            );
    }

    public function testMixingLevelsIsHierarchical()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $newHeaders = $this->_createHeaderSet(array(), false);

        $part = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
            "Content-Type: text/plain\r\n".
            "\r\n".
            'foobar'
            );

        $attachment = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_MIXED,
            "Content-Type: application/octet-stream\r\n".
            "\r\n".
            'data'
            );

        $headers->shouldReceive('toString')
              ->zeroOrMoreTimes()
              ->andReturn("Content-Type: multipart/mixed; boundary=\"xxx\"\r\n");
        $headers->shouldReceive('newInstance')
              ->zeroOrMoreTimes()
              ->andReturn($newHeaders);
        $newHeaders->shouldReceive('toString')
              ->zeroOrMoreTimes()
              ->andReturn("Content-Type: multipart/alternative; boundary=\"yyy\"\r\n");

        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $entity->setBoundary('xxx');
        $entity->setChildren(array($part, $attachment));

        $this->assertRegExp(
            '~^'.
            "Content-Type: multipart/mixed; boundary=\"xxx\"\r\n".
            "\r\n\r\n--xxx\r\n".
            "Content-Type: multipart/alternative; boundary=\"yyy\"\r\n".
            "\r\n\r\n--(.*?)\r\n".
            "Content-Type: text/plain\r\n".
            "\r\n".
            'foobar'.
            "\r\n\r\n--\\1--\r\n".
            "\r\n\r\n--xxx\r\n".
            "Content-Type: application/octet-stream\r\n".
            "\r\n".
            'data'.
            "\r\n\r\n--xxx--\r\n".
            '$~',
            $entity->toString()
            );
    }

    public function testSettingEncoderNotifiesChildren()
    {
        $child = $this->_createChild(0, '', false);
        $encoder = $this->_createEncoder('base64');

        $child->shouldReceive('encoderChanged')
              ->once()
              ->with($encoder);

        $entity = $this->_createEntity($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $entity->setChildren(array($child));
        $entity->setEncoder($encoder);
    }

    public function testReceiptOfEncoderChangeNotifiesChildren()
    {
        $child = $this->_createChild(0, '', false);
        $encoder = $this->_createEncoder('base64');

        $child->shouldReceive('encoderChanged')
              ->once()
              ->with($encoder);

        $entity = $this->_createEntity($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $entity->setChildren(array($child));
        $entity->encoderChanged($encoder);
    }

    public function testReceiptOfCharsetChangeNotifiesChildren()
    {
        $child = $this->_createChild(0, '', false);
        $child->shouldReceive('charsetChanged')
              ->once()
              ->with('windows-874');

        $entity = $this->_createEntity($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $entity->setChildren(array($child));
        $entity->charsetChanged('windows-874');
    }

    public function testEntityIsWrittenToByteStream()
    {
        $entity = $this->_createEntity($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $is = $this->_createInputStream(false);
        $is->expects($this->atLeastOnce())
           ->method('write');

        $entity->toByteStream($is);
    }

    public function testEntityHeadersAreComittedToByteStream()
    {
        $entity = $this->_createEntity($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $is = $this->_createInputStream(false);
        $is->expects($this->atLeastOnce())
           ->method('write');
        $is->expects($this->atLeastOnce())
           ->method('commit');

        $entity->toByteStream($is);
    }

    public function testOrderingTextBeforeHtml()
    {
        $htmlChild = new MimeEntityFixture(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
            "Content-Type: text/html\r\n".
            "\r\n".
            'HTML PART',
            'text/html'
            );
        $textChild = new MimeEntityFixture(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE,
            "Content-Type: text/plain\r\n".
            "\r\n".
            'TEXT PART',
            'text/plain'
            );
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('toString')
                ->zeroOrMoreTimes()
                ->andReturn("Content-Type: multipart/alternative; boundary=\"xxx\"\r\n");

        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $entity->setBoundary('xxx');
        $entity->setChildren(array($htmlChild, $textChild));

        $this->assertEquals(
            "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n".
            "\r\n\r\n--xxx\r\n".
            "Content-Type: text/plain\r\n".
            "\r\n".
            'TEXT PART'.
            "\r\n\r\n--xxx\r\n".
            "Content-Type: text/html\r\n".
            "\r\n".
            'HTML PART'.
            "\r\n\r\n--xxx--\r\n",
            $entity->toString()
            );
    }

    public function testUnsettingChildrenRestoresContentType()
    {
        $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false);
        $child = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE);

        $cType->shouldReceive('setFieldBodyModel')
              ->twice()
              ->with('image/jpeg');
        $cType->shouldReceive('setFieldBodyModel')
              ->once()
              ->with('multipart/alternative');
        $cType->shouldReceive('setFieldBodyModel')
              ->zeroOrMoreTimes()
              ->with(\Mockery::not('multipart/alternative', 'image/jpeg'));

        $entity = $this->_createEntity($this->_createHeaderSet(array(
            'Content-Type' => $cType,
            )),
            $this->_createEncoder(), $this->_createCache()
            );

        $entity->setContentType('image/jpeg');
        $entity->setChildren(array($child));
        $entity->setChildren(array());
    }

    public function testBodyIsReadFromCacheWhenUsingToStringIfPresent()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('toString')
                ->zeroOrMoreTimes()
                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");

        $cache = $this->_createCache(false);
        $cache->shouldReceive('hasKey')
              ->once()
              ->with(\Mockery::any(), 'body')
              ->andReturn(true);
        $cache->shouldReceive('getString')
              ->once()
              ->with(\Mockery::any(), 'body')
              ->andReturn("\r\ncache\r\ncache!");

        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $cache
            );

        $entity->setBody("blah\r\nblah!");
        $this->assertEquals(
            "Content-Type: text/plain; charset=utf-8\r\n".
            "\r\n".
            "cache\r\ncache!",
            $entity->toString()
            );
    }

    public function testBodyIsAddedToCacheWhenUsingToString()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('toString')
                ->zeroOrMoreTimes()
                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");

        $cache = $this->_createCache(false);
        $cache->shouldReceive('hasKey')
              ->once()
              ->with(\Mockery::any(), 'body')
              ->andReturn(false);
        $cache->shouldReceive('setString')
              ->once()
              ->with(\Mockery::any(), 'body', "\r\nblah\r\nblah!", Swift_KeyCache::MODE_WRITE);

        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $cache
            );

        $entity->setBody("blah\r\nblah!");
        $entity->toString();
    }

    public function testBodyIsClearedFromCacheIfNewBodySet()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('toString')
                ->zeroOrMoreTimes()
                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");

        $cache = $this->_createCache(false);
        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $cache
            );

        $entity->setBody("blah\r\nblah!");
        $entity->toString();

        // We set the expectation at this point because we only care what happens when calling setBody()
        $cache->shouldReceive('clearKey')
              ->once()
              ->with(\Mockery::any(), 'body');

        $entity->setBody("new\r\nnew!");
    }

    public function testBodyIsNotClearedFromCacheIfSameBodySet()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('toString')
                ->zeroOrMoreTimes()
                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");

        $cache = $this->_createCache(false);
        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $cache
            );

        $entity->setBody("blah\r\nblah!");
        $entity->toString();

        // We set the expectation at this point because we only care what happens when calling setBody()
        $cache->shouldReceive('clearKey')
              ->never();

        $entity->setBody("blah\r\nblah!");
    }

    public function testBodyIsClearedFromCacheIfNewEncoderSet()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('toString')
                ->zeroOrMoreTimes()
                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");

        $cache = $this->_createCache(false);
        $otherEncoder = $this->_createEncoder();
        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $cache
            );

        $entity->setBody("blah\r\nblah!");
        $entity->toString();

        // We set the expectation at this point because we only care what happens when calling setEncoder()
        $cache->shouldReceive('clearKey')
              ->once()
              ->with(\Mockery::any(), 'body');

        $entity->setEncoder($otherEncoder);
    }

    public function testBodyIsReadFromCacheWhenUsingToByteStreamIfPresent()
    {
        $is = $this->_createInputStream();
        $cache = $this->_createCache(false);
        $cache->shouldReceive('hasKey')
              ->once()
              ->with(\Mockery::any(), 'body')
              ->andReturn(true);
        $cache->shouldReceive('exportToByteStream')
              ->once()
              ->with(\Mockery::any(), 'body', $is);

        $entity = $this->_createEntity($this->_createHeaderSet(),
            $this->_createEncoder(), $cache
            );
        $entity->setBody('foo');

        $entity->toByteStream($is);
    }

    public function testBodyIsAddedToCacheWhenUsingToByteStream()
    {
        $is = $this->_createInputStream();
        $cache = $this->_createCache(false);
        $cache->shouldReceive('hasKey')
              ->once()
              ->with(\Mockery::any(), 'body')
              ->andReturn(false);
        $cache->shouldReceive('getInputByteStream')
              ->once()
              ->with(\Mockery::any(), 'body');

        $entity = $this->_createEntity($this->_createHeaderSet(),
            $this->_createEncoder(), $cache
            );
        $entity->setBody('foo');

        $entity->toByteStream($is);
    }

    public function testFluidInterface()
    {
        $entity = $this->_createEntity($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );

        $this->assertSame($entity,
            $entity
            ->setContentType('text/plain')
            ->setEncoder($this->_createEncoder())
            ->setId('foo@bar')
            ->setDescription('my description')
            ->setMaxLineLength(998)
            ->setBody('xx')
            ->setBoundary('xyz')
            ->setChildren(array())
            );
    }

    // -- Private helpers

    abstract protected function _createEntity($headers, $encoder, $cache);

    protected function _createChild($level = null, $string = '', $stub = true)
    {
        $child = $this->getMockery('Swift_Mime_MimeEntity')->shouldIgnoreMissing();
        if (isset($level)) {
            $child->shouldReceive('getNestingLevel')
                  ->zeroOrMoreTimes()
                  ->andReturn($level);
        }
        $child->shouldReceive('toString')
              ->zeroOrMoreTimes()
              ->andReturn($string);

        return $child;
    }

    protected function _createEncoder($name = 'quoted-printable', $stub = true)
    {
        $encoder = $this->getMockBuilder('Swift_Mime_ContentEncoder')->getMock();
        $encoder->expects($this->any())
                ->method('getName')
                ->will($this->returnValue($name));
        $encoder->expects($this->any())
                ->method('encodeString')
                ->will($this->returnCallback(function () {
                    $args = func_get_args();

                    return array_shift($args);
                }));

        return $encoder;
    }

    protected function _createCache($stub = true)
    {
        return $this->getMockery('Swift_KeyCache')->shouldIgnoreMissing();
    }

    protected function _createHeaderSet($headers = array(), $stub = true)
    {
        $set = $this->getMockery('Swift_Mime_HeaderSet')->shouldIgnoreMissing();
        $set->shouldReceive('get')
            ->zeroOrMoreTimes()
            ->andReturnUsing(function ($key) use ($headers) {
                return $headers[$key];
            });
        $set->shouldReceive('has')
            ->zeroOrMoreTimes()
            ->andReturnUsing(function ($key) use ($headers) {
                return array_key_exists($key, $headers);
            });

        return $set;
    }

    protected function _createHeader($name, $model = null, $params = array(), $stub = true)
    {
        $header = $this->getMockery('Swift_Mime_ParameterizedHeader')->shouldIgnoreMissing();
        $header->shouldReceive('getFieldName')
               ->zeroOrMoreTimes()
               ->andReturn($name);
        $header->shouldReceive('getFieldBodyModel')
               ->zeroOrMoreTimes()
               ->andReturn($model);
        $header->shouldReceive('getParameter')
               ->zeroOrMoreTimes()
               ->andReturnUsing(function ($key) use ($params) {
                   return $params[$key];
               });

        return $header;
    }

    protected function _createOutputStream($data = null, $stub = true)
    {
        $os = $this->getMockery('Swift_OutputByteStream');
        if (isset($data)) {
            $os->shouldReceive('read')
               ->zeroOrMoreTimes()
               ->andReturnUsing(function () use ($data) {
                   static $first = true;
                   if (!$first) {
                       return false;
                   }

                   $first = false;

                   return $data;
               });
            $os->shouldReceive('setReadPointer')
              ->zeroOrMoreTimes();
        }

        return $os;
    }

    protected function _createInputStream($stub = true)
    {
        return $this->getMockBuilder('Swift_InputByteStream')->getMock();
    }
}
<?php

class Swift_Mime_AttachmentTest extends Swift_Mime_AbstractMimeEntityTest
{
    public function testNestingLevelIsAttachment()
    {
        $attachment = $this->_createAttachment($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals(
            Swift_Mime_MimeEntity::LEVEL_MIXED, $attachment->getNestingLevel()
            );
    }

    public function testDispositionIsReturnedFromHeader()
    {
        /* -- RFC 2183, 2.1, 2.2.
     */

        $disposition = $this->_createHeader('Content-Disposition', 'attachment');
        $attachment = $this->_createAttachment($this->_createHeaderSet(array(
            'Content-Disposition' => $disposition, )),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals('attachment', $attachment->getDisposition());
    }

    public function testDispositionIsSetInHeader()
    {
        $disposition = $this->_createHeader('Content-Disposition', 'attachment',
            array(), false
            );
        $disposition->shouldReceive('setFieldBodyModel')
                    ->once()
                    ->with('inline');
        $disposition->shouldReceive('setFieldBodyModel')
                    ->zeroOrMoreTimes();

        $attachment = $this->_createAttachment($this->_createHeaderSet(array(
            'Content-Disposition' => $disposition, )),
            $this->_createEncoder(), $this->_createCache()
            );
        $attachment->setDisposition('inline');
    }

    public function testDispositionIsAddedIfNonePresent()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addParameterizedHeader')
                ->once()
                ->with('Content-Disposition', 'inline');
        $headers->shouldReceive('addParameterizedHeader')
                ->zeroOrMoreTimes();

        $attachment = $this->_createAttachment($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $attachment->setDisposition('inline');
    }

    public function testDispositionIsAutoDefaultedToAttachment()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addParameterizedHeader')
                ->once()
                ->with('Content-Disposition', 'attachment');
        $headers->shouldReceive('addParameterizedHeader')
                ->zeroOrMoreTimes();

        $attachment = $this->_createAttachment($headers, $this->_createEncoder(),
            $this->_createCache()
            );
    }

    public function testDefaultContentTypeInitializedToOctetStream()
    {
        $cType = $this->_createHeader('Content-Type', '',
            array(), false
            );
        $cType->shouldReceive('setFieldBodyModel')
              ->once()
              ->with('application/octet-stream');
        $cType->shouldReceive('setFieldBodyModel')
              ->zeroOrMoreTimes();

        $attachment = $this->_createAttachment($this->_createHeaderSet(array(
            'Content-Type' => $cType, )),
            $this->_createEncoder(), $this->_createCache()
            );
    }

    public function testFilenameIsReturnedFromHeader()
    {
        /* -- RFC 2183, 2.3.
     */

        $disposition = $this->_createHeader('Content-Disposition', 'attachment',
            array('filename' => 'foo.txt')
            );
        $attachment = $this->_createAttachment($this->_createHeaderSet(array(
            'Content-Disposition' => $disposition, )),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals('foo.txt', $attachment->getFilename());
    }

    public function testFilenameIsSetInHeader()
    {
        $disposition = $this->_createHeader('Content-Disposition', 'attachment',
            array('filename' => 'foo.txt'), false
            );
        $disposition->shouldReceive('setParameter')
                    ->once()
                    ->with('filename', 'bar.txt');
        $disposition->shouldReceive('setParameter')
                    ->zeroOrMoreTimes();

        $attachment = $this->_createAttachment($this->_createHeaderSet(array(
            'Content-Disposition' => $disposition, )),
            $this->_createEncoder(), $this->_createCache()
            );
        $attachment->setFilename('bar.txt');
    }

    public function testSettingFilenameSetsNameInContentType()
    {
        /*
     This is a legacy requirement which isn't covered by up-to-date RFCs.
     */

        $cType = $this->_createHeader('Content-Type', 'text/plain',
            array(), false
            );
        $cType->shouldReceive('setParameter')
              ->once()
              ->with('name', 'bar.txt');
        $cType->shouldReceive('setParameter')
              ->zeroOrMoreTimes();

        $attachment = $this->_createAttachment($this->_createHeaderSet(array(
            'Content-Type' => $cType, )),
            $this->_createEncoder(), $this->_createCache()
            );
        $attachment->setFilename('bar.txt');
    }

    public function testSizeIsReturnedFromHeader()
    {
        /* -- RFC 2183, 2.7.
     */

        $disposition = $this->_createHeader('Content-Disposition', 'attachment',
            array('size' => 1234)
            );
        $attachment = $this->_createAttachment($this->_createHeaderSet(array(
            'Content-Disposition' => $disposition, )),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals(1234, $attachment->getSize());
    }

    public function testSizeIsSetInHeader()
    {
        $disposition = $this->_createHeader('Content-Disposition', 'attachment',
            array(), false
            );
        $disposition->shouldReceive('setParameter')
                    ->once()
                    ->with('size', 12345);
        $disposition->shouldReceive('setParameter')
                    ->zeroOrMoreTimes();

        $attachment = $this->_createAttachment($this->_createHeaderSet(array(
            'Content-Disposition' => $disposition, )),
            $this->_createEncoder(), $this->_createCache()
            );
        $attachment->setSize(12345);
    }

    public function testFilnameCanBeReadFromFileStream()
    {
        $file = $this->_createFileStream('/bar/file.ext', '');
        $disposition = $this->_createHeader('Content-Disposition', 'attachment',
            array('filename' => 'foo.txt'), false
            );
        $disposition->shouldReceive('setParameter')
                    ->once()
                    ->with('filename', 'file.ext');

        $attachment = $this->_createAttachment($this->_createHeaderSet(array(
            'Content-Disposition' => $disposition, )),
            $this->_createEncoder(), $this->_createCache()
            );
        $attachment->setFile($file);
    }

    public function testContentTypeCanBeSetViaSetFile()
    {
        $file = $this->_createFileStream('/bar/file.ext', '');
        $disposition = $this->_createHeader('Content-Disposition', 'attachment',
            array('filename' => 'foo.txt'), false
            );
        $disposition->shouldReceive('setParameter')
                    ->once()
                    ->with('filename', 'file.ext');

        $ctype = $this->_createHeader('Content-Type', 'text/plain', array(), false);
        $ctype->shouldReceive('setFieldBodyModel')
              ->once()
              ->with('text/html');
        $ctype->shouldReceive('setFieldBodyModel')
              ->zeroOrMoreTimes();

        $headers = $this->_createHeaderSet(array(
            'Content-Disposition' => $disposition,
            'Content-Type' => $ctype,
            ));

        $attachment = $this->_createAttachment($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $attachment->setFile($file, 'text/html');
    }

    public function XtestContentTypeCanBeLookedUpFromCommonListIfNotProvided()
    {
        $file = $this->_createFileStream('/bar/file.zip', '');
        $disposition = $this->_createHeader('Content-Disposition', 'attachment',
            array('filename' => 'foo.zip'), false
            );
        $disposition->shouldReceive('setParameter')
                    ->once()
                    ->with('filename', 'file.zip');

        $ctype = $this->_createHeader('Content-Type', 'text/plain', array(), false);
        $ctype->shouldReceive('setFieldBodyModel')
              ->once()
              ->with('application/zip');
        $ctype->shouldReceive('setFieldBodyModel')
              ->zeroOrMoreTimes();

        $headers = $this->_createHeaderSet(array(
            'Content-Disposition' => $disposition,
            'Content-Type' => $ctype,
            ));

        $attachment = $this->_createAttachment($headers, $this->_createEncoder(),
            $this->_createCache(), array('zip' => 'application/zip', 'txt' => 'text/plain')
            );
        $attachment->setFile($file);
    }

    public function testDataCanBeReadFromFile()
    {
        $file = $this->_createFileStream('/foo/file.ext', '<some data>');
        $attachment = $this->_createAttachment($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $attachment->setFile($file);
        $this->assertEquals('<some data>', $attachment->getBody());
    }

    public function testFluidInterface()
    {
        $attachment = $this->_createAttachment($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertSame($attachment,
            $attachment
            ->setContentType('application/pdf')
            ->setEncoder($this->_createEncoder())
            ->setId('foo@bar')
            ->setDescription('my pdf')
            ->setMaxLineLength(998)
            ->setBody('xx')
            ->setBoundary('xyz')
            ->setChildren(array())
            ->setDisposition('inline')
            ->setFilename('afile.txt')
            ->setSize(123)
            ->setFile($this->_createFileStream('foo.txt', ''))
            );
    }

    // -- Private helpers

    protected function _createEntity($headers, $encoder, $cache)
    {
        return $this->_createAttachment($headers, $encoder, $cache);
    }

    protected function _createAttachment($headers, $encoder, $cache, $mimeTypes = array())
    {
        return new Swift_Mime_Attachment($headers, $encoder, $cache, new Swift_Mime_Grammar(), $mimeTypes);
    }

    protected function _createFileStream($path, $data, $stub = true)
    {
        $file = $this->getMockery('Swift_FileStream');
        $file->shouldReceive('getPath')
             ->zeroOrMoreTimes()
             ->andReturn($path);
        $file->shouldReceive('read')
             ->zeroOrMoreTimes()
             ->andReturnUsing(function () use ($data) {
                 static $first = true;
                 if (!$first) {
                     return false;
                 }

                 $first = false;

                 return $data;
             });
        $file->shouldReceive('setReadPointer')
             ->zeroOrMoreTimes();

        return $file;
    }
}
<?php

class Swift_Mime_ContentEncoder_Base64ContentEncoderTest extends \SwiftMailerTestCase
{
    private $_encoder;

    public function setUp()
    {
        $this->_encoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder();
    }

    public function testNameIsBase64()
    {
        $this->assertEquals('base64', $this->_encoder->getName());
    }

    /*
    There's really no point in testing the entire base64 encoding to the
    level QP encoding has been tested.  base64_encode() has been in PHP for
    years.
    */

    public function testInputOutputRatioIs3to4Bytes()
    {
        /*
        RFC 2045, 6.8

         The encoding process represents 24-bit groups of input bits as output
         strings of 4 encoded characters.  Proceeding from left to right, a
         24-bit input group is formed by concatenating 3 8bit input groups.
         These 24 bits are then treated as 4 concatenated 6-bit groups, each
         of which is translated into a single digit in the base64 alphabet.
         */

        $os = $this->_createOutputByteStream();
        $is = $this->_createInputByteStream();
        $collection = new Swift_StreamCollector();

        $is->shouldReceive('write')
           ->zeroOrMoreTimes()
           ->andReturnUsing($collection);
        $os->shouldReceive('read')
           ->once()
           ->andReturn('123');
        $os->shouldReceive('read')
           ->zeroOrMoreTimes()
           ->andReturn(false);

        $this->_encoder->encodeByteStream($os, $is);
        $this->assertEquals('MTIz', $collection->content);
    }

    public function testPadLength()
    {
        /*
        RFC 2045, 6.8

       Special processing is performed if fewer than 24 bits are available
       at the end of the data being encoded.  A full encoding quantum is
       always completed at the end of a body.  When fewer than 24 input bits
       are available in an input group, zero bits are added (on the right)
       to form an integral number of 6-bit groups.  Padding at the end of
       the data is performed using the "=" character.  Since all base64
       input is an integral number of octets, only the following cases can
       arise: (1) the final quantum of encoding input is an integral
       multiple of 24 bits; here, the final unit of encoded output will be
       an integral multiple of 4 characters with no "=" padding, (2) the
       final quantum of encoding input is exactly 8 bits; here, the final
       unit of encoded output will be two characters followed by two "="
       padding characters, or (3) the final quantum of encoding input is
       exactly 16 bits; here, the final unit of encoded output will be three
       characters followed by one "=" padding character.
       */

        for ($i = 0; $i < 30; ++$i) {
            $os = $this->_createOutputByteStream();
            $is = $this->_createInputByteStream();
            $collection = new Swift_StreamCollector();

            $is->shouldReceive('write')
               ->zeroOrMoreTimes()
               ->andReturnUsing($collection);
            $os->shouldReceive('read')
               ->once()
               ->andReturn(pack('C', rand(0, 255)));
            $os->shouldReceive('read')
               ->zeroOrMoreTimes()
               ->andReturn(false);

            $this->_encoder->encodeByteStream($os, $is);
            $this->assertRegExp('~^[a-zA-Z0-9/\+]{2}==$~', $collection->content,
                '%s: A single byte should have 2 bytes of padding'
                );
        }

        for ($i = 0; $i < 30; ++$i) {
            $os = $this->_createOutputByteStream();
            $is = $this->_createInputByteStream();
            $collection = new Swift_StreamCollector();

            $is->shouldReceive('write')
               ->zeroOrMoreTimes()
               ->andReturnUsing($collection);
            $os->shouldReceive('read')
               ->once()
               ->andReturn(pack('C*', rand(0, 255), rand(0, 255)));
            $os->shouldReceive('read')
               ->zeroOrMoreTimes()
               ->andReturn(false);

            $this->_encoder->encodeByteStream($os, $is);
            $this->assertRegExp('~^[a-zA-Z0-9/\+]{3}=$~', $collection->content,
                '%s: Two bytes should have 1 byte of padding'
                );
        }

        for ($i = 0; $i < 30; ++$i) {
            $os = $this->_createOutputByteStream();
            $is = $this->_createInputByteStream();
            $collection = new Swift_StreamCollector();

            $is->shouldReceive('write')
               ->zeroOrMoreTimes()
               ->andReturnUsing($collection);
            $os->shouldReceive('read')
               ->once()
               ->andReturn(pack('C*', rand(0, 255), rand(0, 255), rand(0, 255)));
            $os->shouldReceive('read')
               ->zeroOrMoreTimes()
               ->andReturn(false);

            $this->_encoder->encodeByteStream($os, $is);
            $this->assertRegExp('~^[a-zA-Z0-9/\+]{4}$~', $collection->content,
                '%s: Three bytes should have no padding'
                );
        }
    }

    public function testMaximumLineLengthIs76Characters()
    {
        /*
         The encoded output stream must be represented in lines of no more
         than 76 characters each.  All line breaks or other characters not
         found in Table 1 must be ignored by decoding software.
         */

        $os = $this->_createOutputByteStream();
        $is = $this->_createInputByteStream();
        $collection = new Swift_StreamCollector();

        $is->shouldReceive('write')
           ->zeroOrMoreTimes()
           ->andReturnUsing($collection);
        $os->shouldReceive('read')
           ->once()
           ->andReturn('abcdefghijkl'); //12
        $os->shouldReceive('read')
           ->once()
           ->andReturn('mnopqrstuvwx'); //24
        $os->shouldReceive('read')
           ->once()
           ->andReturn('yzabc1234567'); //36
        $os->shouldReceive('read')
           ->once()
           ->andReturn('890ABCDEFGHI'); //48
        $os->shouldReceive('read')
           ->once()
           ->andReturn('JKLMNOPQRSTU'); //60
        $os->shouldReceive('read')
           ->once()
           ->andReturn('VWXYZ1234567'); //72
        $os->shouldReceive('read')
           ->once()
           ->andReturn('abcdefghijkl'); //84
        $os->shouldReceive('read')
           ->zeroOrMoreTimes()
           ->andReturn(false);

        $this->_encoder->encodeByteStream($os, $is);
        $this->assertEquals(
            "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3ODkwQUJDREVGR0hJSktMTU5PUFFS\r\n".
            'U1RVVldYWVoxMjM0NTY3YWJjZGVmZ2hpamts',
            $collection->content
            );
    }

    public function testMaximumLineLengthCanBeDifferent()
    {
        $os = $this->_createOutputByteStream();
        $is = $this->_createInputByteStream();
        $collection = new Swift_StreamCollector();

        $is->shouldReceive('write')
           ->zeroOrMoreTimes()
           ->andReturnUsing($collection);
        $os->shouldReceive('read')
           ->once()
           ->andReturn('abcdefghijkl'); //12
        $os->shouldReceive('read')
           ->once()
           ->andReturn('mnopqrstuvwx'); //24
        $os->shouldReceive('read')
           ->once()
           ->andReturn('yzabc1234567'); //36
        $os->shouldReceive('read')
           ->once()
           ->andReturn('890ABCDEFGHI'); //48
        $os->shouldReceive('read')
           ->once()
           ->andReturn('JKLMNOPQRSTU'); //60
        $os->shouldReceive('read')
           ->once()
           ->andReturn('VWXYZ1234567'); //72
        $os->shouldReceive('read')
           ->once()
           ->andReturn('abcdefghijkl'); //84
        $os->shouldReceive('read')
           ->zeroOrMoreTimes()
           ->andReturn(false);

        $this->_encoder->encodeByteStream($os, $is, 0, 50);
        $this->assertEquals(
            "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3OD\r\n".
            "kwQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVoxMjM0NTY3YWJj\r\n".
            'ZGVmZ2hpamts',
            $collection->content
            );
    }

    public function testMaximumLineLengthIsNeverMoreThan76Chars()
    {
        $os = $this->_createOutputByteStream();
        $is = $this->_createInputByteStream();
        $collection = new Swift_StreamCollector();

        $is->shouldReceive('write')
           ->zeroOrMoreTimes()
           ->andReturnUsing($collection);
        $os->shouldReceive('read')
           ->once()
           ->andReturn('abcdefghijkl'); //12
        $os->shouldReceive('read')
           ->once()
           ->andReturn('mnopqrstuvwx'); //24
        $os->shouldReceive('read')
           ->once()
           ->andReturn('yzabc1234567'); //36
        $os->shouldReceive('read')
           ->once()
           ->andReturn('890ABCDEFGHI'); //48
        $os->shouldReceive('read')
           ->once()
           ->andReturn('JKLMNOPQRSTU'); //60
        $os->shouldReceive('read')
           ->once()
           ->andReturn('VWXYZ1234567'); //72
        $os->shouldReceive('read')
           ->once()
           ->andReturn('abcdefghijkl'); //84
        $os->shouldReceive('read')
           ->zeroOrMoreTimes()
           ->andReturn(false);

        $this->_encoder->encodeByteStream($os, $is, 0, 100);
        $this->assertEquals(
            "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3ODkwQUJDREVGR0hJSktMTU5PUFFS\r\n".
            'U1RVVldYWVoxMjM0NTY3YWJjZGVmZ2hpamts',
            $collection->content
            );
    }

    public function testFirstLineLengthCanBeDifferent()
    {
        $os = $this->_createOutputByteStream();
        $is = $this->_createInputByteStream();
        $collection = new Swift_StreamCollector();

        $is->shouldReceive('write')
           ->zeroOrMoreTimes()
           ->andReturnUsing($collection);
        $os->shouldReceive('read')
           ->once()
           ->andReturn('abcdefghijkl'); //12
        $os->shouldReceive('read')
           ->once()
           ->andReturn('mnopqrstuvwx'); //24
        $os->shouldReceive('read')
           ->once()
           ->andReturn('yzabc1234567'); //36
        $os->shouldReceive('read')
           ->once()
           ->andReturn('890ABCDEFGHI'); //48
        $os->shouldReceive('read')
           ->once()
           ->andReturn('JKLMNOPQRSTU'); //60
        $os->shouldReceive('read')
           ->once()
           ->andReturn('VWXYZ1234567'); //72
        $os->shouldReceive('read')
           ->once()
           ->andReturn('abcdefghijkl'); //84
        $os->shouldReceive('read')
           ->zeroOrMoreTimes()
           ->andReturn(false);

        $this->_encoder->encodeByteStream($os, $is, 19);
        $this->assertEquals(
            "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3ODkwQUJDR\r\n".
            'EVGR0hJSktMTU5PUFFSU1RVVldYWVoxMjM0NTY3YWJjZGVmZ2hpamts',
            $collection->content
            );
    }

    private function _createOutputByteStream($stub = false)
    {
        return $this->getMockery('Swift_OutputByteStream')->shouldIgnoreMissing();
    }

    private function _createInputByteStream($stub = false)
    {
        return $this->getMockery('Swift_InputByteStream')->shouldIgnoreMissing();
    }
}
<?php

class Swift_Mime_ContentEncoder_PlainContentEncoderTest extends \SwiftMailerTestCase
{
    public function testNameCanBeSpecifiedInConstructor()
    {
        $encoder = $this->_getEncoder('7bit');
        $this->assertEquals('7bit', $encoder->getName());

        $encoder = $this->_getEncoder('8bit');
        $this->assertEquals('8bit', $encoder->getName());
    }

    public function testNoOctetsAreModifiedInString()
    {
        $encoder = $this->_getEncoder('7bit');
        foreach (range(0x00, 0xFF) as $octet) {
            $byte = pack('C', $octet);
            $this->assertIdenticalBinary($byte, $encoder->encodeString($byte));
        }
    }

    public function testNoOctetsAreModifiedInByteStream()
    {
        $encoder = $this->_getEncoder('7bit');
        foreach (range(0x00, 0xFF) as $octet) {
            $byte = pack('C', $octet);

            $os = $this->_createOutputByteStream();
            $is = $this->_createInputByteStream();
            $collection = new Swift_StreamCollector();

            $is->shouldReceive('write')
               ->zeroOrMoreTimes()
               ->andReturnUsing($collection);
            $os->shouldReceive('read')
               ->once()
               ->andReturn($byte);
            $os->shouldReceive('read')
               ->zeroOrMoreTimes()
               ->andReturn(false);

            $encoder->encodeByteStream($os, $is);
            $this->assertIdenticalBinary($byte, $collection->content);
        }
    }

    public function testLineLengthCanBeSpecified()
    {
        $encoder = $this->_getEncoder('7bit');

        $chars = array();
        for ($i = 0; $i < 50; ++$i) {
            $chars[] = 'a';
        }
        $input = implode(' ', $chars); //99 chars long

        $this->assertEquals(
            'a a a a a a a a a a a a a a a a a a a a a a a a a '."\r\n".//50 *
            'a a a a a a a a a a a a a a a a a a a a a a a a a',            //99
            $encoder->encodeString($input, 0, 50),
            '%s: Lines should be wrapped at 50 chars'
            );
    }

    public function testLineLengthCanBeSpecifiedInByteStream()
    {
        $encoder = $this->_getEncoder('7bit');

        $os = $this->_createOutputByteStream();
        $is = $this->_createInputByteStream();
        $collection = new Swift_StreamCollector();

        $is->shouldReceive('write')
           ->zeroOrMoreTimes()
           ->andReturnUsing($collection);

        for ($i = 0; $i < 50; ++$i) {
            $os->shouldReceive('read')
               ->once()
               ->andReturn('a ');
        }

        $os->shouldReceive('read')
           ->zeroOrMoreTimes()
           ->andReturn(false);

        $encoder->encodeByteStream($os, $is, 0, 50);
        $this->assertEquals(
            str_repeat('a ', 25)."\r\n".str_repeat('a ', 25),
            $collection->content
            );
    }

    public function testencodeStringGeneratesCorrectCrlf()
    {
        $encoder = $this->_getEncoder('7bit', true);
        $this->assertEquals("a\r\nb", $encoder->encodeString("a\rb"),
            '%s: Line endings should be standardized'
            );
        $this->assertEquals("a\r\nb", $encoder->encodeString("a\nb"),
            '%s: Line endings should be standardized'
            );
        $this->assertEquals("a\r\n\r\nb", $encoder->encodeString("a\n\rb"),
            '%s: Line endings should be standardized'
            );
        $this->assertEquals("a\r\n\r\nb", $encoder->encodeString("a\r\rb"),
            '%s: Line endings should be standardized'
            );
        $this->assertEquals("a\r\n\r\nb", $encoder->encodeString("a\n\nb"),
            '%s: Line endings should be standardized'
            );
    }

    public function crlfProvider()
    {
        return array(
            array("\r", "a\r\nb"),
            array("\n", "a\r\nb"),
            array("\n\r", "a\r\n\r\nb"),
            array("\n\n", "a\r\n\r\nb"),
            array("\r\r", "a\r\n\r\nb"),
        );
    }

    /**
     * @dataProvider crlfProvider
     */
    public function testCanonicEncodeByteStreamGeneratesCorrectCrlf($test, $expected)
    {
        $encoder = $this->_getEncoder('7bit', true);

        $os = $this->_createOutputByteStream();
        $is = $this->_createInputByteStream();
        $collection = new Swift_StreamCollector();

        $is->shouldReceive('write')
           ->zeroOrMoreTimes()
           ->andReturnUsing($collection);
        $os->shouldReceive('read')
           ->once()
           ->andReturn('a');
        $os->shouldReceive('read')
           ->once()
           ->andReturn($test);
        $os->shouldReceive('read')
           ->once()
           ->andReturn('b');
        $os->shouldReceive('read')
           ->zeroOrMoreTimes()
           ->andReturn(false);

        $encoder->encodeByteStream($os, $is);
        $this->assertEquals($expected, $collection->content);
    }

    // -- Private helpers

    private function _getEncoder($name, $canonical = false)
    {
        return new Swift_Mime_ContentEncoder_PlainContentEncoder($name, $canonical);
    }

    private function _createOutputByteStream($stub = false)
    {
        return $this->getMockery('Swift_OutputByteStream')->shouldIgnoreMissing();
    }

    private function _createInputByteStream($stub = false)
    {
        return $this->getMockery('Swift_InputByteStream')->shouldIgnoreMissing();
    }
}
<?php

class Swift_Mime_ContentEncoder_QpContentEncoderTest extends \SwiftMailerTestCase
{
    public function testNameIsQuotedPrintable()
    {
        $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder(
            $this->_createCharacterStream(true)
            );
        $this->assertEquals('quoted-printable', $encoder->getName());
    }

    /* -- RFC 2045, 6.7 --
    (1)   (General 8bit representation) Any octet, except a CR or
                    LF that is part of a CRLF line break of the canonical
                    (standard) form of the data being encoded, may be
                    represented by an "=" followed by a two digit
                    hexadecimal representation of the octet's value.  The
                    digits of the hexadecimal alphabet, for this purpose,
                    are "0123456789ABCDEF".  Uppercase letters must be
                    used; lowercase letters are not allowed.  Thus, for
                    example, the decimal value 12 (US-ASCII form feed) can
                    be represented by "=0C", and the decimal value 61 (US-
                    ASCII EQUAL SIGN) can be represented by "=3D".  This
                    rule must be followed except when the following rules
                    allow an alternative encoding.
                    */

    public function testPermittedCharactersAreNotEncoded()
    {
        /* -- RFC 2045, 6.7 --
        (2)   (Literal representation) Octets with decimal values of
                    33 through 60 inclusive, and 62 through 126, inclusive,
                    MAY be represented as the US-ASCII characters which
                    correspond to those octets (EXCLAMATION POINT through
                    LESS THAN, and GREATER THAN through TILDE,
                    respectively).
                    */

        foreach (array_merge(range(33, 60), range(62, 126)) as $ordinal) {
            $char = chr($ordinal);

            $os = $this->_createOutputByteStream(true);
            $charStream = $this->_createCharacterStream();
            $is = $this->_createInputByteStream();
            $collection = new Swift_StreamCollector();

            $is->shouldReceive('write')
               ->zeroOrMoreTimes()
               ->andReturnUsing($collection);
            $charStream->shouldReceive('flushContents')
                       ->once();
            $charStream->shouldReceive('importByteStream')
                       ->once()
                       ->with($os);
            $charStream->shouldReceive('readBytes')
                       ->once()
                       ->andReturn(array($ordinal));
            $charStream->shouldReceive('readBytes')
                       ->zeroOrMoreTimes()
                       ->andReturn(false);

            $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
            $encoder->encodeByteStream($os, $is);
            $this->assertIdenticalBinary($char, $collection->content);
        }
    }

    public function testLinearWhiteSpaceAtLineEndingIsEncoded()
    {
        /* -- RFC 2045, 6.7 --
        (3)   (White Space) Octets with values of 9 and 32 MAY be
                    represented as US-ASCII TAB (HT) and SPACE characters,
                    respectively, but MUST NOT be so represented at the end
                    of an encoded line.  Any TAB (HT) or SPACE characters
                    on an encoded line MUST thus be followed on that line
                    by a printable character.  In particular, an "=" at the
                    end of an encoded line, indicating a soft line break
                    (see rule #5) may follow one or more TAB (HT) or SPACE
                    characters.  It follows that an octet with decimal
                    value 9 or 32 appearing at the end of an encoded line
                    must be represented according to Rule #1.  This rule is
                    necessary because some MTAs (Message Transport Agents,
                    programs which transport messages from one user to
                    another, or perform a portion of such transfers) are
                    known to pad lines of text with SPACEs, and others are
                    known to remove "white space" characters from the end
                    of a line.  Therefore, when decoding a Quoted-Printable
                    body, any trailing white space on a line must be
                    deleted, as it will necessarily have been added by
                    intermediate transport agents.
                    */

        $HT = chr(0x09); //9
        $SPACE = chr(0x20); //32

        //HT
        $os = $this->_createOutputByteStream(true);
        $charStream = $this->_createCharacterStream();
        $is = $this->_createInputByteStream();
        $collection = new Swift_StreamCollector();

        $is->shouldReceive('write')
               ->zeroOrMoreTimes()
               ->andReturnUsing($collection);
        $charStream->shouldReceive('flushContents')
                   ->once();
        $charStream->shouldReceive('importByteStream')
                   ->once()
                   ->with($os);
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(ord('a')));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(0x09));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(0x09));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(0x0D));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(0x0A));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(ord('b')));
        $charStream->shouldReceive('readBytes')
                   ->zeroOrMoreTimes()
                   ->andReturn(false);

        $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
        $encoder->encodeByteStream($os, $is);

        $this->assertEquals("a\t=09\r\nb", $collection->content);

        //SPACE
        $os = $this->_createOutputByteStream(true);
        $charStream = $this->_createCharacterStream();
        $is = $this->_createInputByteStream();
        $collection = new Swift_StreamCollector();

        $is->shouldReceive('write')
               ->zeroOrMoreTimes()
               ->andReturnUsing($collection);
        $charStream->shouldReceive('flushContents')
                   ->once();
        $charStream->shouldReceive('importByteStream')
                   ->once()
                   ->with($os);
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(ord('a')));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(0x20));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(0x20));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(0x0D));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(0x0A));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(ord('b')));
        $charStream->shouldReceive('readBytes')
                   ->zeroOrMoreTimes()
                   ->andReturn(false);

        $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
        $encoder->encodeByteStream($os, $is);

        $this->assertEquals("a =20\r\nb", $collection->content);
    }

    public function testCRLFIsLeftAlone()
    {
        /*
        (4)   (Line Breaks) A line break in a text body, represented
                    as a CRLF sequence in the text canonical form, must be
                    represented by a (RFC 822) line break, which is also a
                    CRLF sequence, in the Quoted-Printable encoding.  Since
                    the canonical representation of media types other than
                    text do not generally include the representation of
                    line breaks as CRLF sequences, no hard line breaks
                    (i.e. line breaks that are intended to be meaningful
                    and to be displayed to the user) can occur in the
                    quoted-printable encoding of such types.  Sequences
                    like "=0D", "=0A", "=0A=0D" and "=0D=0A" will routinely
                    appear in non-text data represented in quoted-
                    printable, of course.

                    Note that many implementations may elect to encode the
                    local representation of various content types directly
                    rather than converting to canonical form first,
                    encoding, and then converting back to local
                    representation.  In particular, this may apply to plain
                    text material on systems that use newline conventions
                    other than a CRLF terminator sequence.  Such an
                    implementation optimization is permissible, but only
                    when the combined canonicalization-encoding step is
                    equivalent to performing the three steps separately.
                    */

        $os = $this->_createOutputByteStream(true);
        $charStream = $this->_createCharacterStream();
        $is = $this->_createInputByteStream();
        $collection = new Swift_StreamCollector();

        $is->shouldReceive('write')
               ->zeroOrMoreTimes()
               ->andReturnUsing($collection);
        $charStream->shouldReceive('flushContents')
                   ->once();
        $charStream->shouldReceive('importByteStream')
                   ->once()
                   ->with($os);
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(ord('a')));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(0x0D));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(0x0A));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(ord('b')));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(0x0D));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(0x0A));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(ord('c')));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(0x0D));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(0x0A));
        $charStream->shouldReceive('readBytes')
                   ->zeroOrMoreTimes()
                   ->andReturn(false);

        $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
        $encoder->encodeByteStream($os, $is);
        $this->assertEquals("a\r\nb\r\nc\r\n", $collection->content);
    }

    public function testLinesLongerThan76CharactersAreSoftBroken()
    {
        /*
        (5)   (Soft Line Breaks) The Quoted-Printable encoding
                    REQUIRES that encoded lines be no more than 76
                    characters long.  If longer lines are to be encoded
                    with the Quoted-Printable encoding, "soft" line breaks
                    must be used.  An equal sign as the last character on a
                    encoded line indicates such a non-significant ("soft")
                    line break in the encoded text.
                    */

        $os = $this->_createOutputByteStream(true);
        $charStream = $this->_createCharacterStream();
        $is = $this->_createInputByteStream();
        $collection = new Swift_StreamCollector();

        $is->shouldReceive('write')
           ->zeroOrMoreTimes()
           ->andReturnUsing($collection);
        $charStream->shouldReceive('flushContents')
                   ->once();
        $charStream->shouldReceive('importByteStream')
                   ->once()
                   ->with($os);

        for ($seq = 0; $seq <= 140; ++$seq) {
            $charStream->shouldReceive('readBytes')
                       ->once()
                       ->andReturn(array(ord('a')));
        }
        $charStream->shouldReceive('readBytes')
                   ->zeroOrMoreTimes()
                   ->andReturn(false);

        $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
        $encoder->encodeByteStream($os, $is);
        $this->assertEquals(str_repeat('a', 75)."=\r\n".str_repeat('a', 66), $collection->content);
    }

    public function testMaxLineLengthCanBeSpecified()
    {
        $os = $this->_createOutputByteStream(true);
        $charStream = $this->_createCharacterStream();
        $is = $this->_createInputByteStream();
        $collection = new Swift_StreamCollector();

        $is->shouldReceive('write')
           ->zeroOrMoreTimes()
           ->andReturnUsing($collection);
        $charStream->shouldReceive('flushContents')
                   ->once();
        $charStream->shouldReceive('importByteStream')
                   ->once()
                   ->with($os);

        for ($seq = 0; $seq <= 100; ++$seq) {
            $charStream->shouldReceive('readBytes')
                       ->once()
                       ->andReturn(array(ord('a')));
        }
        $charStream->shouldReceive('readBytes')
                   ->zeroOrMoreTimes()
                   ->andReturn(false);

        $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
        $encoder->encodeByteStream($os, $is, 0, 54);
        $this->assertEquals(str_repeat('a', 53)."=\r\n".str_repeat('a', 48), $collection->content);
    }

    public function testBytesBelowPermittedRangeAreEncoded()
    {
        /*
        According to Rule (1 & 2)
        */

        foreach (range(0, 32) as $ordinal) {
            $char = chr($ordinal);

            $os = $this->_createOutputByteStream(true);
            $charStream = $this->_createCharacterStream();
            $is = $this->_createInputByteStream();
            $collection = new Swift_StreamCollector();

            $is->shouldReceive('write')
               ->zeroOrMoreTimes()
               ->andReturnUsing($collection);
            $charStream->shouldReceive('flushContents')
                       ->once();
            $charStream->shouldReceive('importByteStream')
                       ->once()
                       ->with($os);
            $charStream->shouldReceive('readBytes')
                       ->once()
                       ->andReturn(array($ordinal));
            $charStream->shouldReceive('readBytes')
                       ->zeroOrMoreTimes()
                       ->andReturn(false);

            $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
            $encoder->encodeByteStream($os, $is);
            $this->assertEquals(sprintf('=%02X', $ordinal), $collection->content);
        }
    }

    public function testDecimalByte61IsEncoded()
    {
        /*
        According to Rule (1 & 2)
        */

        $char = chr(61);

        $os = $this->_createOutputByteStream(true);
        $charStream = $this->_createCharacterStream();
        $is = $this->_createInputByteStream();
        $collection = new Swift_StreamCollector();

        $is->shouldReceive('write')
               ->zeroOrMoreTimes()
               ->andReturnUsing($collection);
        $charStream->shouldReceive('flushContents')
                       ->once();
        $charStream->shouldReceive('importByteStream')
                       ->once()
                       ->with($os);
        $charStream->shouldReceive('readBytes')
                       ->once()
                       ->andReturn(array(61));
        $charStream->shouldReceive('readBytes')
                       ->zeroOrMoreTimes()
                       ->andReturn(false);

        $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
        $encoder->encodeByteStream($os, $is);
        $this->assertEquals(sprintf('=%02X', 61), $collection->content);
    }

    public function testBytesAbovePermittedRangeAreEncoded()
    {
        /*
        According to Rule (1 & 2)
        */

        foreach (range(127, 255) as $ordinal) {
            $char = chr($ordinal);

            $os = $this->_createOutputByteStream(true);
            $charStream = $this->_createCharacterStream();
            $is = $this->_createInputByteStream();
            $collection = new Swift_StreamCollector();

            $is->shouldReceive('write')
               ->zeroOrMoreTimes()
               ->andReturnUsing($collection);
            $charStream->shouldReceive('flushContents')
                       ->once();
            $charStream->shouldReceive('importByteStream')
                       ->once()
                       ->with($os);
            $charStream->shouldReceive('readBytes')
                       ->once()
                       ->andReturn(array($ordinal));
            $charStream->shouldReceive('readBytes')
                       ->zeroOrMoreTimes()
                       ->andReturn(false);

            $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
            $encoder->encodeByteStream($os, $is);
            $this->assertEquals(sprintf('=%02X', $ordinal), $collection->content);
        }
    }

    public function testFirstLineLengthCanBeDifferent()
    {
        $os = $this->_createOutputByteStream(true);
        $charStream = $this->_createCharacterStream();
        $is = $this->_createInputByteStream();
        $collection = new Swift_StreamCollector();

        $is->shouldReceive('write')
               ->zeroOrMoreTimes()
               ->andReturnUsing($collection);
        $charStream->shouldReceive('flushContents')
                    ->once();
        $charStream->shouldReceive('importByteStream')
                    ->once()
                    ->with($os);

        for ($seq = 0; $seq <= 140; ++$seq) {
            $charStream->shouldReceive('readBytes')
                       ->once()
                       ->andReturn(array(ord('a')));
        }
        $charStream->shouldReceive('readBytes')
                    ->zeroOrMoreTimes()
                    ->andReturn(false);

        $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
        $encoder->encodeByteStream($os, $is, 22);
        $this->assertEquals(
            str_repeat('a', 53)."=\r\n".str_repeat('a', 75)."=\r\n".str_repeat('a', 13),
            $collection->content
            );
    }

    public function testObserverInterfaceCanChangeCharset()
    {
        $stream = $this->_createCharacterStream();
        $stream->shouldReceive('setCharacterSet')
               ->once()
               ->with('windows-1252');

        $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($stream);
        $encoder->charsetChanged('windows-1252');
    }

    public function testTextIsPreWrapped()
    {
        $encoder = $this->createEncoder();

        $input = str_repeat('a', 70)."\r\n".
                 str_repeat('a', 70)."\r\n".
                 str_repeat('a', 70);

        $os = new Swift_ByteStream_ArrayByteStream();
        $is = new Swift_ByteStream_ArrayByteStream();
        $is->write($input);

        $encoder->encodeByteStream($is, $os);

        $this->assertEquals(
            $input, $os->read(PHP_INT_MAX)
            );
    }

    // -- Creation Methods

    private function _createCharacterStream($stub = false)
    {
        return $this->getMockery('Swift_CharacterStream')->shouldIgnoreMissing();
    }

    private function createEncoder()
    {
        $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
        $charStream = new Swift_CharacterStream_NgCharacterStream($factory, 'utf-8');

        return new Swift_Mime_ContentEncoder_QpContentEncoder($charStream);
    }

    private function _createOutputByteStream($stub = false)
    {
        return $this->getMockery('Swift_OutputByteStream')->shouldIgnoreMissing();
    }

    private function _createInputByteStream($stub = false)
    {
        return $this->getMockery('Swift_InputByteStream')->shouldIgnoreMissing();
    }
}
<?php

class Swift_Mime_EmbeddedFileTest extends Swift_Mime_AttachmentTest
{
    public function testNestingLevelIsAttachment()
    {
        //Overridden
    }

    public function testNestingLevelIsEmbedded()
    {
        $file = $this->_createEmbeddedFile($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals(
            Swift_Mime_MimeEntity::LEVEL_RELATED, $file->getNestingLevel()
            );
    }

    public function testIdIsAutoGenerated()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addIdHeader')
                ->once()
                ->with('Content-ID', '/^.*?@.*?$/D');

        $file = $this->_createEmbeddedFile($headers, $this->_createEncoder(),
            $this->_createCache()
            );
    }

    public function testDefaultDispositionIsInline()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addParameterizedHeader')
                ->once()
                ->with('Content-Disposition', 'inline');
        $headers->shouldReceive('addParameterizedHeader')
                ->zeroOrMoreTimes();

        $file = $this->_createEmbeddedFile($headers, $this->_createEncoder(),
            $this->_createCache()
            );
    }

    // -- Private helpers

    protected function _createAttachment($headers, $encoder, $cache, $mimeTypes = array())
    {
        return $this->_createEmbeddedFile($headers, $encoder, $cache, $mimeTypes);
    }

    private function _createEmbeddedFile($headers, $encoder, $cache)
    {
        return new Swift_Mime_EmbeddedFile($headers, $encoder, $cache, new Swift_Mime_Grammar());
    }
}
<?php

class Swift_Mime_HeaderEncoder_Base64HeaderEncoderTest extends \PHPUnit_Framework_TestCase
{
    //Most tests are already covered in Base64EncoderTest since this subclass only
    // adds a getName() method

    public function testNameIsB()
    {
        $encoder = new Swift_Mime_HeaderEncoder_Base64HeaderEncoder();
        $this->assertEquals('B', $encoder->getName());
    }
}
<?php

class Swift_Mime_HeaderEncoder_QpHeaderEncoderTest extends \SwiftMailerTestCase
{
    //Most tests are already covered in QpEncoderTest since this subclass only
    // adds a getName() method

    public function testNameIsQ()
    {
        $encoder = $this->_createEncoder(
            $this->_createCharacterStream(true)
            );
        $this->assertEquals('Q', $encoder->getName());
    }

    public function testSpaceAndTabNeverAppear()
    {
        /* -- RFC 2047, 4.
     Only a subset of the printable ASCII characters may be used in
     'encoded-text'.  Space and tab characters are not allowed, so that
     the beginning and end of an 'encoded-word' are obvious.
     */

        $charStream = $this->_createCharacterStream();
        $charStream->shouldReceive('readBytes')
                   ->atLeast()->times(6)
                   ->andReturn(array(ord('a')), array(0x20), array(0x09), array(0x20), array(ord('b')), false);

        $encoder = $this->_createEncoder($charStream);
        $this->assertNotRegExp('~[ \t]~', $encoder->encodeString("a \t b"),
            '%s: encoded-words in headers cannot contain LWSP as per RFC 2047.'
            );
    }

    public function testSpaceIsRepresentedByUnderscore()
    {
        /* -- RFC 2047, 4.2.
        (2) The 8-bit hexadecimal value 20 (e.g., ISO-8859-1 SPACE) may be
       represented as "_" (underscore, ASCII 95.).  (This character may
       not pass through some internetwork mail gateways, but its use
       will greatly enhance readability of "Q" encoded data with mail
       readers that do not support this encoding.)  Note that the "_"
       always represents hexadecimal 20, even if the SPACE character
       occupies a different code position in the character set in use.
       */
        $charStream = $this->_createCharacterStream();
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(ord('a')));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(0x20));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(ord('b')));
        $charStream->shouldReceive('readBytes')
                   ->zeroOrMoreTimes()
                   ->andReturn(false);

        $encoder = $this->_createEncoder($charStream);
        $this->assertEquals('a_b', $encoder->encodeString('a b'),
            '%s: Spaces can be represented by more readable underscores as per RFC 2047.'
            );
    }

    public function testEqualsAndQuestionAndUnderscoreAreEncoded()
    {
        /* -- RFC 2047, 4.2.
        (3) 8-bit values which correspond to printable ASCII characters other
       than "=", "?", and "_" (underscore), MAY be represented as those
       characters.  (But see section 5 for restrictions.)  In
       particular, SPACE and TAB MUST NOT be represented as themselves
       within encoded words.
       */
        $charStream = $this->_createCharacterStream();
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(ord('=')));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(ord('?')));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(ord('_')));
        $charStream->shouldReceive('readBytes')
                   ->zeroOrMoreTimes()
                   ->andReturn(false);

        $encoder = $this->_createEncoder($charStream);
        $this->assertEquals('=3D=3F=5F', $encoder->encodeString('=?_'),
            '%s: Chars =, ? and _ (underscore) may not appear as per RFC 2047.'
            );
    }

    public function testParensAndQuotesAreEncoded()
    {
        /* -- RFC 2047, 5 (2).
     A "Q"-encoded 'encoded-word' which appears in a 'comment' MUST NOT
     contain the characters "(", ")" or "
     */

        $charStream = $this->_createCharacterStream();
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(ord('(')));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(ord('"')));
        $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array(ord(')')));
        $charStream->shouldReceive('readBytes')
                   ->zeroOrMoreTimes()
                   ->andReturn(false);

        $encoder = $this->_createEncoder($charStream);
        $this->assertEquals('=28=22=29', $encoder->encodeString('(")'),
            '%s: Chars (, " (DQUOTE) and ) may not appear as per RFC 2047.'
            );
    }

    public function testOnlyCharactersAllowedInPhrasesAreUsed()
    {
        /* -- RFC 2047, 5.
        (3) As a replacement for a 'word' entity within a 'phrase', for example,
        one that precedes an address in a From, To, or Cc header.  The ABNF
        definition for 'phrase' from RFC 822 thus becomes:

        phrase = 1*( encoded-word / word )

        In this case the set of characters that may be used in a "Q"-encoded
        'encoded-word' is restricted to: <upper and lower case ASCII
        letters, decimal digits, "!", "*", "+", "-", "/", "=", and "_"
        (underscore, ASCII 95.)>.  An 'encoded-word' that appears within a
        'phrase' MUST be separated from any adjacent 'word', 'text' or
        'special' by 'linear-white-space'.
        */

        $allowedBytes = array_merge(
            range(ord('a'), ord('z')), range(ord('A'), ord('Z')),
            range(ord('0'), ord('9')),
            array(ord('!'), ord('*'), ord('+'), ord('-'), ord('/'))
            );

        foreach (range(0x00, 0xFF) as $byte) {
            $char = pack('C', $byte);

            $charStream = $this->_createCharacterStream();
            $charStream->shouldReceive('readBytes')
                   ->once()
                   ->andReturn(array($byte));
            $charStream->shouldReceive('readBytes')
                   ->zeroOrMoreTimes()
                   ->andReturn(false);

            $encoder = $this->_createEncoder($charStream);
            $encodedChar = $encoder->encodeString($char);

            if (in_array($byte, $allowedBytes)) {
                $this->assertEquals($char, $encodedChar,
                    '%s: Character '.$char.' should not be encoded.'
                    );
            } elseif (0x20 == $byte) {
                //Special case
                $this->assertEquals('_', $encodedChar,
                    '%s: Space character should be replaced.'
                    );
            } else {
                $this->assertEquals(sprintf('=%02X', $byte), $encodedChar,
                    '%s: Byte '.$byte.' should be encoded.'
                    );
            }
        }
    }

    public function testEqualsNeverAppearsAtEndOfLine()
    {
        /* -- RFC 2047, 5 (3).
        The 'encoded-text' in an 'encoded-word' must be self-contained;
        'encoded-text' MUST NOT be continued from one 'encoded-word' to
        another.  This implies that the 'encoded-text' portion of a "B"
        'encoded-word' will be a multiple of 4 characters long; for a "Q"
        'encoded-word', any "=" character that appears in the 'encoded-text'
        portion will be followed by two hexadecimal characters.
        */

        $input = str_repeat('a', 140);

        $charStream = $this->_createCharacterStream();

        $output = '';
        $seq = 0;
        for (; $seq < 140; ++$seq) {
            $charStream->shouldReceive('readBytes')
                       ->once()
                       ->andReturn(array(ord('a')));

            if (75 == $seq) {
                $output .= "\r\n"; // =\r\n
            }
            $output .= 'a';
        }

        $charStream->shouldReceive('readBytes')
                   ->zeroOrMoreTimes()
                   ->andReturn(false);

        $encoder = $this->_createEncoder($charStream);
        $this->assertEquals($output, $encoder->encodeString($input));
    }

    // -- Creation Methods

    private function _createEncoder($charStream)
    {
        return new Swift_Mime_HeaderEncoder_QpHeaderEncoder($charStream);
    }

    private function _createCharacterStream($stub = false)
    {
        return $this->getMockery('Swift_CharacterStream')->shouldIgnoreMissing();
    }
}
<?php

class Swift_Mime_Headers_DateHeaderTest extends \PHPUnit_Framework_TestCase
{
    /* --
    The following tests refer to RFC 2822, section 3.6.1 and 3.3.
    */

    public function testTypeIsDateHeader()
    {
        $header = $this->_getHeader('Date');
        $this->assertEquals(Swift_Mime_Header::TYPE_DATE, $header->getFieldType());
    }

    public function testGetTimestamp()
    {
        $timestamp = time();
        $header = $this->_getHeader('Date');
        $header->setTimestamp($timestamp);
        $this->assertSame($timestamp, $header->getTimestamp());
    }

    public function testTimestampCanBeSetBySetter()
    {
        $timestamp = time();
        $header = $this->_getHeader('Date');
        $header->setTimestamp($timestamp);
        $this->assertSame($timestamp, $header->getTimestamp());
    }

    public function testIntegerTimestampIsConvertedToRfc2822Date()
    {
        $timestamp = time();
        $header = $this->_getHeader('Date');
        $header->setTimestamp($timestamp);
        $this->assertEquals(date('r', $timestamp), $header->getFieldBody());
    }

    public function testSetBodyModel()
    {
        $timestamp = time();
        $header = $this->_getHeader('Date');
        $header->setFieldBodyModel($timestamp);
        $this->assertEquals(date('r', $timestamp), $header->getFieldBody());
    }

    public function testGetBodyModel()
    {
        $timestamp = time();
        $header = $this->_getHeader('Date');
        $header->setTimestamp($timestamp);
        $this->assertEquals($timestamp, $header->getFieldBodyModel());
    }

    public function testToString()
    {
        $timestamp = time();
        $header = $this->_getHeader('Date');
        $header->setTimestamp($timestamp);
        $this->assertEquals('Date: '.date('r', $timestamp)."\r\n",
            $header->toString()
            );
    }

    private function _getHeader($name)
    {
        return new Swift_Mime_Headers_DateHeader($name, new Swift_Mime_Grammar());
    }
}
<?php

class Swift_Mime_Headers_IdentificationHeaderTest extends \PHPUnit_Framework_TestCase
{
    public function testTypeIsIdHeader()
    {
        $header = $this->_getHeader('Message-ID');
        $this->assertEquals(Swift_Mime_Header::TYPE_ID, $header->getFieldType());
    }

    public function testValueMatchesMsgIdSpec()
    {
        /* -- RFC 2822, 3.6.4.
     message-id      =       "Message-ID:" msg-id CRLF

     in-reply-to     =       "In-Reply-To:" 1*msg-id CRLF

     references      =       "References:" 1*msg-id CRLF

     msg-id          =       [CFWS] "<" id-left "@" id-right ">" [CFWS]

     id-left         =       dot-atom-text / no-fold-quote / obs-id-left

     id-right        =       dot-atom-text / no-fold-literal / obs-id-right

     no-fold-quote   =       DQUOTE *(qtext / quoted-pair) DQUOTE

     no-fold-literal =       "[" *(dtext / quoted-pair) "]"
     */

        $header = $this->_getHeader('Message-ID');
        $header->setId('id-left@id-right');
        $this->assertEquals('<id-left@id-right>', $header->getFieldBody());
    }

    public function testIdCanBeRetrievedVerbatim()
    {
        $header = $this->_getHeader('Message-ID');
        $header->setId('id-left@id-right');
        $this->assertEquals('id-left@id-right', $header->getId());
    }

    public function testMultipleIdsCanBeSet()
    {
        $header = $this->_getHeader('References');
        $header->setIds(array('a@b', 'x@y'));
        $this->assertEquals(array('a@b', 'x@y'), $header->getIds());
    }

    public function testSettingMultipleIdsProducesAListValue()
    {
        /* -- RFC 2822, 3.6.4.
     The "References:" and "In-Reply-To:" field each contain one or more
     unique message identifiers, optionally separated by CFWS.

     .. SNIP ..

     in-reply-to     =       "In-Reply-To:" 1*msg-id CRLF

     references      =       "References:" 1*msg-id CRLF
     */

        $header = $this->_getHeader('References');
        $header->setIds(array('a@b', 'x@y'));
        $this->assertEquals('<a@b> <x@y>', $header->getFieldBody());
    }

    public function testIdLeftCanBeQuoted()
    {
        /* -- RFC 2822, 3.6.4.
     id-left         =       dot-atom-text / no-fold-quote / obs-id-left
     */

        $header = $this->_getHeader('References');
        $header->setId('"ab"@c');
        $this->assertEquals('"ab"@c', $header->getId());
        $this->assertEquals('<"ab"@c>', $header->getFieldBody());
    }

    public function testIdLeftCanContainAnglesAsQuotedPairs()
    {
        /* -- RFC 2822, 3.6.4.
     no-fold-quote   =       DQUOTE *(qtext / quoted-pair) DQUOTE
     */

        $header = $this->_getHeader('References');
        $header->setId('"a\\<\\>b"@c');
        $this->assertEquals('"a\\<\\>b"@c', $header->getId());
        $this->assertEquals('<"a\\<\\>b"@c>', $header->getFieldBody());
    }

    public function testIdLeftCanBeDotAtom()
    {
        $header = $this->_getHeader('References');
        $header->setId('a.b+&%$.c@d');
        $this->assertEquals('a.b+&%$.c@d', $header->getId());
        $this->assertEquals('<a.b+&%$.c@d>', $header->getFieldBody());
    }

    public function testInvalidIdLeftThrowsException()
    {
        try {
            $header = $this->_getHeader('References');
            $header->setId('a b c@d');
            $this->fail(
                'Exception should be thrown since "a b c" is not valid id-left.'
                );
        } catch (Exception $e) {
        }
    }

    public function testIdRightCanBeDotAtom()
    {
        /* -- RFC 2822, 3.6.4.
     id-right        =       dot-atom-text / no-fold-literal / obs-id-right
     */

        $header = $this->_getHeader('References');
        $header->setId('a@b.c+&%$.d');
        $this->assertEquals('a@b.c+&%$.d', $header->getId());
        $this->assertEquals('<a@b.c+&%$.d>', $header->getFieldBody());
    }

    public function testIdRightCanBeLiteral()
    {
        /* -- RFC 2822, 3.6.4.
     no-fold-literal =       "[" *(dtext / quoted-pair) "]"
     */

        $header = $this->_getHeader('References');
        $header->setId('a@[1.2.3.4]');
        $this->assertEquals('a@[1.2.3.4]', $header->getId());
        $this->assertEquals('<a@[1.2.3.4]>', $header->getFieldBody());
    }

    public function testInvalidIdRightThrowsException()
    {
        try {
            $header = $this->_getHeader('References');
            $header->setId('a@b c d');
            $this->fail(
                'Exception should be thrown since "b c d" is not valid id-right.'
                );
        } catch (Exception $e) {
        }
    }

    public function testMissingAtSignThrowsException()
    {
        /* -- RFC 2822, 3.6.4.
     msg-id          =       [CFWS] "<" id-left "@" id-right ">" [CFWS]
     */

        try {
            $header = $this->_getHeader('References');
            $header->setId('abc');
            $this->fail(
                'Exception should be thrown since "abc" is does not contain @.'
                );
        } catch (Exception $e) {
        }
    }

    public function testSetBodyModel()
    {
        $header = $this->_getHeader('Message-ID');
        $header->setFieldBodyModel('a@b');
        $this->assertEquals(array('a@b'), $header->getIds());
    }

    public function testGetBodyModel()
    {
        $header = $this->_getHeader('Message-ID');
        $header->setId('a@b');
        $this->assertEquals(array('a@b'), $header->getFieldBodyModel());
    }

    public function testStringValue()
    {
        $header = $this->_getHeader('References');
        $header->setIds(array('a@b', 'x@y'));
        $this->assertEquals('References: <a@b> <x@y>'."\r\n", $header->toString());
    }

    private function _getHeader($name)
    {
        return new Swift_Mime_Headers_IdentificationHeader($name, new Swift_Mime_Grammar());
    }
}
<?php

class Swift_Mime_Headers_MailboxHeaderTest extends \SwiftMailerTestCase
{
    /* -- RFC 2822, 3.6.2 for all tests.
     */

    private $_charset = 'utf-8';

    public function testTypeIsMailboxHeader()
    {
        $header = $this->_getHeader('To', $this->_getEncoder('Q', true));
        $this->assertEquals(Swift_Mime_Header::TYPE_MAILBOX, $header->getFieldType());
    }

    public function testMailboxIsSetForAddress()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setAddresses('chris@swiftmailer.org');
        $this->assertEquals(array('chris@swiftmailer.org'),
            $header->getNameAddressStrings()
            );
    }

    public function testMailboxIsRenderedForNameAddress()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setNameAddresses(array('chris@swiftmailer.org' => 'Chris Corbyn'));
        $this->assertEquals(
            array('Chris Corbyn <chris@swiftmailer.org>'), $header->getNameAddressStrings()
            );
    }

    public function testAddressCanBeReturnedForAddress()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setAddresses('chris@swiftmailer.org');
        $this->assertEquals(array('chris@swiftmailer.org'), $header->getAddresses());
    }

    public function testAddressCanBeReturnedForNameAddress()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setNameAddresses(array('chris@swiftmailer.org' => 'Chris Corbyn'));
        $this->assertEquals(array('chris@swiftmailer.org'), $header->getAddresses());
    }

    public function testQuotesInNameAreQuoted()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setNameAddresses(array(
            'chris@swiftmailer.org' => 'Chris Corbyn, "DHE"',
            ));
        $this->assertEquals(
            array('"Chris Corbyn, \"DHE\"" <chris@swiftmailer.org>'),
            $header->getNameAddressStrings()
            );
    }

    public function testEscapeCharsInNameAreQuoted()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setNameAddresses(array(
            'chris@swiftmailer.org' => 'Chris Corbyn, \\escaped\\',
            ));
        $this->assertEquals(
            array('"Chris Corbyn, \\\\escaped\\\\" <chris@swiftmailer.org>'),
            $header->getNameAddressStrings()
            );
    }

    public function testGetMailboxesReturnsNameValuePairs()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setNameAddresses(array(
            'chris@swiftmailer.org' => 'Chris Corbyn, DHE',
            ));
        $this->assertEquals(
            array('chris@swiftmailer.org' => 'Chris Corbyn, DHE'), $header->getNameAddresses()
            );
    }

    public function testMultipleAddressesCanBeSetAndFetched()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setAddresses(array(
            'chris@swiftmailer.org', 'mark@swiftmailer.org',
            ));
        $this->assertEquals(
            array('chris@swiftmailer.org', 'mark@swiftmailer.org'),
            $header->getAddresses()
            );
    }

    public function testMultipleAddressesAsMailboxes()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setAddresses(array(
            'chris@swiftmailer.org', 'mark@swiftmailer.org',
            ));
        $this->assertEquals(
            array('chris@swiftmailer.org' => null, 'mark@swiftmailer.org' => null),
            $header->getNameAddresses()
            );
    }

    public function testMultipleAddressesAsMailboxStrings()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setAddresses(array(
            'chris@swiftmailer.org', 'mark@swiftmailer.org',
            ));
        $this->assertEquals(
            array('chris@swiftmailer.org', 'mark@swiftmailer.org'),
            $header->getNameAddressStrings()
            );
    }

    public function testMultipleNamedMailboxesReturnsMultipleAddresses()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setNameAddresses(array(
            'chris@swiftmailer.org' => 'Chris Corbyn',
            'mark@swiftmailer.org' => 'Mark Corbyn',
            ));
        $this->assertEquals(
            array('chris@swiftmailer.org', 'mark@swiftmailer.org'),
            $header->getAddresses()
            );
    }

    public function testMultipleNamedMailboxesReturnsMultipleMailboxes()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setNameAddresses(array(
            'chris@swiftmailer.org' => 'Chris Corbyn',
            'mark@swiftmailer.org' => 'Mark Corbyn',
            ));
        $this->assertEquals(array(
                'chris@swiftmailer.org' => 'Chris Corbyn',
                'mark@swiftmailer.org' => 'Mark Corbyn',
                ),
            $header->getNameAddresses()
            );
    }

    public function testMultipleMailboxesProducesMultipleMailboxStrings()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setNameAddresses(array(
            'chris@swiftmailer.org' => 'Chris Corbyn',
            'mark@swiftmailer.org' => 'Mark Corbyn',
            ));
        $this->assertEquals(array(
                'Chris Corbyn <chris@swiftmailer.org>',
                'Mark Corbyn <mark@swiftmailer.org>',
                ),
            $header->getNameAddressStrings()
            );
    }

    public function testSetAddressesOverwritesAnyMailboxes()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setNameAddresses(array(
            'chris@swiftmailer.org' => 'Chris Corbyn',
            'mark@swiftmailer.org' => 'Mark Corbyn',
            ));
        $this->assertEquals(
            array('chris@swiftmailer.org' => 'Chris Corbyn',
            'mark@swiftmailer.org' => 'Mark Corbyn', ),
            $header->getNameAddresses()
            );
        $this->assertEquals(
            array('chris@swiftmailer.org', 'mark@swiftmailer.org'),
            $header->getAddresses()
            );

        $header->setAddresses(array('chris@swiftmailer.org', 'mark@swiftmailer.org'));

        $this->assertEquals(
            array('chris@swiftmailer.org' => null, 'mark@swiftmailer.org' => null),
            $header->getNameAddresses()
            );
        $this->assertEquals(
            array('chris@swiftmailer.org', 'mark@swiftmailer.org'),
            $header->getAddresses()
            );
    }

    public function testNameIsEncodedIfNonAscii()
    {
        $name = 'C'.pack('C', 0x8F).'rbyn';

        $encoder = $this->_getEncoder('Q');
        $encoder->shouldReceive('encodeString')
                ->once()
                ->with($name, \Mockery::any(), \Mockery::any(), \Mockery::any())
                ->andReturn('C=8Frbyn');

        $header = $this->_getHeader('From', $encoder);
        $header->setNameAddresses(array('chris@swiftmailer.org' => 'Chris '.$name));

        $addresses = $header->getNameAddressStrings();
        $this->assertEquals(
            'Chris =?'.$this->_charset.'?Q?C=8Frbyn?= <chris@swiftmailer.org>',
            array_shift($addresses)
            );
    }

    public function testEncodingLineLengthCalculations()
    {
        /* -- RFC 2047, 2.
        An 'encoded-word' may not be more than 75 characters long, including
        'charset', 'encoding', 'encoded-text', and delimiters.
        */

        $name = 'C'.pack('C', 0x8F).'rbyn';

        $encoder = $this->_getEncoder('Q');
        $encoder->shouldReceive('encodeString')
                ->once()
                ->with($name, \Mockery::any(), \Mockery::any(), \Mockery::any())
                ->andReturn('C=8Frbyn');

        $header = $this->_getHeader('From', $encoder);
        $header->setNameAddresses(array('chris@swiftmailer.org' => 'Chris '.$name));

        $header->getNameAddressStrings();
    }

    public function testGetValueReturnsMailboxStringValue()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setNameAddresses(array(
            'chris@swiftmailer.org' => 'Chris Corbyn',
            ));
        $this->assertEquals(
            'Chris Corbyn <chris@swiftmailer.org>', $header->getFieldBody()
            );
    }

    public function testGetValueReturnsMailboxStringValueForMultipleMailboxes()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setNameAddresses(array(
            'chris@swiftmailer.org' => 'Chris Corbyn',
            'mark@swiftmailer.org' => 'Mark Corbyn',
            ));
        $this->assertEquals(
            'Chris Corbyn <chris@swiftmailer.org>, Mark Corbyn <mark@swiftmailer.org>',
            $header->getFieldBody()
            );
    }

    public function testRemoveAddressesWithSingleValue()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setNameAddresses(array(
            'chris@swiftmailer.org' => 'Chris Corbyn',
            'mark@swiftmailer.org' => 'Mark Corbyn',
            ));
        $header->removeAddresses('chris@swiftmailer.org');
        $this->assertEquals(array('mark@swiftmailer.org'),
            $header->getAddresses()
            );
    }

    public function testRemoveAddressesWithList()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setNameAddresses(array(
            'chris@swiftmailer.org' => 'Chris Corbyn',
            'mark@swiftmailer.org' => 'Mark Corbyn',
            ));
        $header->removeAddresses(
            array('chris@swiftmailer.org', 'mark@swiftmailer.org')
            );
        $this->assertEquals(array(), $header->getAddresses());
    }

    public function testSetBodyModel()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setFieldBodyModel('chris@swiftmailer.org');
        $this->assertEquals(array('chris@swiftmailer.org' => null), $header->getNameAddresses());
    }

    public function testGetBodyModel()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setAddresses(array('chris@swiftmailer.org'));
        $this->assertEquals(array('chris@swiftmailer.org' => null), $header->getFieldBodyModel());
    }

    public function testToString()
    {
        $header = $this->_getHeader('From', $this->_getEncoder('Q', true));
        $header->setNameAddresses(array(
            'chris@swiftmailer.org' => 'Chris Corbyn',
            'mark@swiftmailer.org' => 'Mark Corbyn',
            ));
        $this->assertEquals(
            'From: Chris Corbyn <chris@swiftmailer.org>, '.
            'Mark Corbyn <mark@swiftmailer.org>'."\r\n",
            $header->toString()
            );
    }

    private function _getHeader($name, $encoder)
    {
        $header = new Swift_Mime_Headers_MailboxHeader($name, $encoder, new Swift_Mime_Grammar());
        $header->setCharset($this->_charset);

        return $header;
    }

    private function _getEncoder($type, $stub = false)
    {
        $encoder = $this->getMockery('Swift_Mime_HeaderEncoder')->shouldIgnoreMissing();
        $encoder->shouldReceive('getName')
                ->zeroOrMoreTimes()
                ->andReturn($type);

        return $encoder;
    }
}
<?php

class Swift_Mime_Headers_ParameterizedHeaderTest extends \SwiftMailerTestCase
{
    private $_charset = 'utf-8';
    private $_lang = 'en-us';

    public function testTypeIsParameterizedHeader()
    {
        $header = $this->_getHeader('Content-Type',
            $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
            );
        $this->assertEquals(Swift_Mime_Header::TYPE_PARAMETERIZED, $header->getFieldType());
    }

    public function testValueIsReturnedVerbatim()
    {
        $header = $this->_getHeader('Content-Type',
            $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
            );
        $header->setValue('text/plain');
        $this->assertEquals('text/plain', $header->getValue());
    }

    public function testParametersAreAppended()
    {
        /* -- RFC 2045, 5.1
        parameter := attribute "=" value

     attribute := token
                                    ; Matching of attributes
                                    ; is ALWAYS case-insensitive.

     value := token / quoted-string

     token := 1*<any (US-ASCII) CHAR except SPACE, CTLs,
                 or tspecials>

     tspecials :=  "(" / ")" / "<" / ">" / "@" /
                   "," / ";" / ":" / "\" / <">
                   "/" / "[" / "]" / "?" / "="
                   ; Must be in quoted-string,
                   ; to use within parameter values
        */

        $header = $this->_getHeader('Content-Type',
            $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
            );
        $header->setValue('text/plain');
        $header->setParameters(array('charset' => 'utf-8'));
        $this->assertEquals('text/plain; charset=utf-8', $header->getFieldBody());
    }

    public function testSpaceInParamResultsInQuotedString()
    {
        $header = $this->_getHeader('Content-Disposition',
            $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
            );
        $header->setValue('attachment');
        $header->setParameters(array('filename' => 'my file.txt'));
        $this->assertEquals('attachment; filename="my file.txt"',
            $header->getFieldBody()
            );
    }

    public function testLongParamsAreBrokenIntoMultipleAttributeStrings()
    {
        /* -- RFC 2231, 3.
        The asterisk character ("*") followed
        by a decimal count is employed to indicate that multiple parameters
        are being used to encapsulate a single parameter value.  The count
        starts at 0 and increments by 1 for each subsequent section of the
        parameter value.  Decimal values are used and neither leading zeroes
        nor gaps in the sequence are allowed.

        The original parameter value is recovered by concatenating the
        various sections of the parameter, in order.  For example, the
        content-type field

                Content-Type: message/external-body; access-type=URL;
         URL*0="ftp://";
         URL*1="cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar"

        is semantically identical to

                Content-Type: message/external-body; access-type=URL;
                    URL="ftp://cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar"

        Note that quotes around parameter values are part of the value
        syntax; they are NOT part of the value itself.  Furthermore, it is
        explicitly permitted to have a mixture of quoted and unquoted
        continuation fields.
        */

        $value = str_repeat('a', 180);

        $encoder = $this->_getParameterEncoder();
        $encoder->shouldReceive('encodeString')
                ->once()
                ->with($value, \Mockery::any(), 63, \Mockery::any())
                ->andReturn(str_repeat('a', 63)."\r\n".
                    str_repeat('a', 63)."\r\n".str_repeat('a', 54));

        $header = $this->_getHeader('Content-Disposition',
            $this->_getHeaderEncoder('Q', true), $encoder
            );
        $header->setValue('attachment');
        $header->setParameters(array('filename' => $value));
        $header->setMaxLineLength(78);
        $this->assertEquals(
            'attachment; '.
            'filename*0*=utf-8\'\''.str_repeat('a', 63).";\r\n ".
            'filename*1*='.str_repeat('a', 63).";\r\n ".
            'filename*2*='.str_repeat('a', 54),
            $header->getFieldBody()
            );
    }

    public function testEncodedParamDataIncludesCharsetAndLanguage()
    {
        /* -- RFC 2231, 4.
        Asterisks ("*") are reused to provide the indicator that language and
        character set information is present and encoding is being used. A
        single quote ("'") is used to delimit the character set and language
        information at the beginning of the parameter value. Percent signs
        ("%") are used as the encoding flag, which agrees with RFC 2047.

        Specifically, an asterisk at the end of a parameter name acts as an
        indicator that character set and language information may appear at
        the beginning of the parameter value. A single quote is used to
        separate the character set, language, and actual value information in
        the parameter value string, and an percent sign is used to flag
        octets encoded in hexadecimal.  For example:

                Content-Type: application/x-stuff;
         title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A

        Note that it is perfectly permissible to leave either the character
        set or language field blank.  Note also that the single quote
        delimiters MUST be present even when one of the field values is
        omitted.
        */

        $value = str_repeat('a', 20).pack('C', 0x8F).str_repeat('a', 10);

        $encoder = $this->_getParameterEncoder();
        $encoder->shouldReceive('encodeString')
                ->once()
                ->with($value, 12, 62, \Mockery::any())
                ->andReturn(str_repeat('a', 20).'%8F'.str_repeat('a', 10));

        $header = $this->_getHeader('Content-Disposition',
            $this->_getHeaderEncoder('Q', true), $encoder
            );
        $header->setValue('attachment');
        $header->setParameters(array('filename' => $value));
        $header->setMaxLineLength(78);
        $header->setLanguage($this->_lang);
        $this->assertEquals(
            'attachment; filename*='.$this->_charset."'".$this->_lang."'".
            str_repeat('a', 20).'%8F'.str_repeat('a', 10),
            $header->getFieldBody()
            );
    }

    public function testMultipleEncodedParamLinesAreFormattedCorrectly()
    {
        /* -- RFC 2231, 4.1.
        Character set and language information may be combined with the
        parameter continuation mechanism. For example:

        Content-Type: application/x-stuff
     title*0*=us-ascii'en'This%20is%20even%20more%20
     title*1*=%2A%2A%2Afun%2A%2A%2A%20
     title*2="isn't it!"

        Note that:

     (1)   Language and character set information only appear at
           the beginning of a given parameter value.

     (2)   Continuations do not provide a facility for using more
           than one character set or language in the same
           parameter value.

     (3)   A value presented using multiple continuations may
           contain a mixture of encoded and unencoded segments.

     (4)   The first segment of a continuation MUST be encoded if
           language and character set information are given.

     (5)   If the first segment of a continued parameter value is
           encoded the language and character set field delimiters
           MUST be present even when the fields are left blank.
        */

        $value = str_repeat('a', 20).pack('C', 0x8F).str_repeat('a', 60);

        $encoder = $this->_getParameterEncoder();
        $encoder->shouldReceive('encodeString')
                ->once()
                ->with($value, 12, 62, \Mockery::any())
                ->andReturn(str_repeat('a', 20).'%8F'.str_repeat('a', 28)."\r\n".
                    str_repeat('a', 32));

        $header = $this->_getHeader('Content-Disposition',
            $this->_getHeaderEncoder('Q', true), $encoder
            );
        $header->setValue('attachment');
        $header->setParameters(array('filename' => $value));
        $header->setMaxLineLength(78);
        $header->setLanguage($this->_lang);
        $this->assertEquals(
            'attachment; filename*0*='.$this->_charset."'".$this->_lang."'".
            str_repeat('a', 20).'%8F'.str_repeat('a', 28).";\r\n ".
            'filename*1*='.str_repeat('a', 32),
            $header->getFieldBody()
            );
    }

    public function testToString()
    {
        $header = $this->_getHeader('Content-Type',
            $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
            );
        $header->setValue('text/html');
        $header->setParameters(array('charset' => 'utf-8'));
        $this->assertEquals('Content-Type: text/html; charset=utf-8'."\r\n",
            $header->toString()
            );
    }

    public function testValueCanBeEncodedIfNonAscii()
    {
        $value = 'fo'.pack('C', 0x8F).'bar';

        $encoder = $this->_getHeaderEncoder('Q');
        $encoder->shouldReceive('encodeString')
                ->once()
                ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any())
                ->andReturn('fo=8Fbar');

        $header = $this->_getHeader('X-Foo', $encoder, $this->_getParameterEncoder(true));
        $header->setValue($value);
        $header->setParameters(array('lookslike' => 'foobar'));
        $this->assertEquals('X-Foo: =?utf-8?Q?fo=8Fbar?=; lookslike=foobar'."\r\n",
            $header->toString()
            );
    }

    public function testValueAndParamCanBeEncodedIfNonAscii()
    {
        $value = 'fo'.pack('C', 0x8F).'bar';

        $encoder = $this->_getHeaderEncoder('Q');
        $encoder->shouldReceive('encodeString')
                ->once()
                ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any())
                ->andReturn('fo=8Fbar');

        $paramEncoder = $this->_getParameterEncoder();
        $paramEncoder->shouldReceive('encodeString')
                     ->once()
                     ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any())
                     ->andReturn('fo%8Fbar');

        $header = $this->_getHeader('X-Foo', $encoder, $paramEncoder);
        $header->setValue($value);
        $header->setParameters(array('says' => $value));
        $this->assertEquals("X-Foo: =?utf-8?Q?fo=8Fbar?=; says*=utf-8''fo%8Fbar\r\n",
            $header->toString()
            );
    }

    public function testParamsAreEncodedWithEncodedWordsIfNoParamEncoderSet()
    {
        $value = 'fo'.pack('C', 0x8F).'bar';

        $encoder = $this->_getHeaderEncoder('Q');
        $encoder->shouldReceive('encodeString')
                ->once()
                ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any())
                ->andReturn('fo=8Fbar');

        $header = $this->_getHeader('X-Foo', $encoder, null);
        $header->setValue('bar');
        $header->setParameters(array('says' => $value));
        $this->assertEquals("X-Foo: bar; says=\"=?utf-8?Q?fo=8Fbar?=\"\r\n",
            $header->toString()
            );
    }

    public function testLanguageInformationAppearsInEncodedWords()
    {
        /* -- RFC 2231, 5.
        5.  Language specification in Encoded Words

        RFC 2047 provides support for non-US-ASCII character sets in RFC 822
        message header comments, phrases, and any unstructured text field.
        This is done by defining an encoded word construct which can appear
        in any of these places.  Given that these are fields intended for
        display, it is sometimes necessary to associate language information
        with encoded words as well as just the character set.  This
        specification extends the definition of an encoded word to allow the
        inclusion of such information.  This is simply done by suffixing the
        character set specification with an asterisk followed by the language
        tag.  For example:

                    From: =?US-ASCII*EN?Q?Keith_Moore?= <moore@cs.utk.edu>
        */

        $value = 'fo'.pack('C', 0x8F).'bar';

        $encoder = $this->_getHeaderEncoder('Q');
        $encoder->shouldReceive('encodeString')
                ->once()
                ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any())
                ->andReturn('fo=8Fbar');

        $paramEncoder = $this->_getParameterEncoder();
        $paramEncoder->shouldReceive('encodeString')
                     ->once()
                     ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any())
                     ->andReturn('fo%8Fbar');

        $header = $this->_getHeader('X-Foo', $encoder, $paramEncoder);
        $header->setLanguage('en');
        $header->setValue($value);
        $header->setParameters(array('says' => $value));
        $this->assertEquals("X-Foo: =?utf-8*en?Q?fo=8Fbar?=; says*=utf-8'en'fo%8Fbar\r\n",
            $header->toString()
            );
    }

    public function testSetBodyModel()
    {
        $header = $this->_getHeader('Content-Type',
            $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
            );
        $header->setFieldBodyModel('text/html');
        $this->assertEquals('text/html', $header->getValue());
    }

    public function testGetBodyModel()
    {
        $header = $this->_getHeader('Content-Type',
            $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
            );
        $header->setValue('text/plain');
        $this->assertEquals('text/plain', $header->getFieldBodyModel());
    }

    public function testSetParameter()
    {
        $header = $this->_getHeader('Content-Type',
            $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
            );
        $header->setParameters(array('charset' => 'utf-8', 'delsp' => 'yes'));
        $header->setParameter('delsp', 'no');
        $this->assertEquals(array('charset' => 'utf-8', 'delsp' => 'no'),
            $header->getParameters()
            );
    }

    public function testGetParameter()
    {
        $header = $this->_getHeader('Content-Type',
            $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true)
            );
        $header->setParameters(array('charset' => 'utf-8', 'delsp' => 'yes'));
        $this->assertEquals('utf-8', $header->getParameter('charset'));
    }

    // -- Private helper

    private function _getHeader($name, $encoder, $paramEncoder)
    {
        $header = new Swift_Mime_Headers_ParameterizedHeader($name, $encoder,
            $paramEncoder, new Swift_Mime_Grammar()
            );
        $header->setCharset($this->_charset);

        return $header;
    }

    private function _getHeaderEncoder($type, $stub = false)
    {
        $encoder = $this->getMockery('Swift_Mime_HeaderEncoder')->shouldIgnoreMissing();
        $encoder->shouldReceive('getName')
                ->zeroOrMoreTimes()
                ->andReturn($type);

        return $encoder;
    }

    private function _getParameterEncoder($stub = false)
    {
        return $this->getMockery('Swift_Encoder')->shouldIgnoreMissing();
    }
}
<?php

class Swift_Mime_Headers_PathHeaderTest extends \PHPUnit_Framework_TestCase
{
    public function testTypeIsPathHeader()
    {
        $header = $this->_getHeader('Return-Path');
        $this->assertEquals(Swift_Mime_Header::TYPE_PATH, $header->getFieldType());
    }

    public function testSingleAddressCanBeSetAndFetched()
    {
        $header = $this->_getHeader('Return-Path');
        $header->setAddress('chris@swiftmailer.org');
        $this->assertEquals('chris@swiftmailer.org', $header->getAddress());
    }

    public function testAddressMustComplyWithRfc2822()
    {
        try {
            $header = $this->_getHeader('Return-Path');
            $header->setAddress('chr is@swiftmailer.org');
            $this->fail('Addresses not valid according to RFC 2822 addr-spec grammar must be rejected.');
        } catch (Exception $e) {
        }
    }

    public function testValueIsAngleAddrWithValidAddress()
    {
        /* -- RFC 2822, 3.6.7.

            return          =       "Return-Path:" path CRLF

            path            =       ([CFWS] "<" ([CFWS] / addr-spec) ">" [CFWS]) /
                                                            obs-path
     */

        $header = $this->_getHeader('Return-Path');
        $header->setAddress('chris@swiftmailer.org');
        $this->assertEquals('<chris@swiftmailer.org>', $header->getFieldBody());
    }

    public function testValueIsEmptyAngleBracketsIfEmptyAddressSet()
    {
        $header = $this->_getHeader('Return-Path');
        $header->setAddress('');
        $this->assertEquals('<>', $header->getFieldBody());
    }

    public function testSetBodyModel()
    {
        $header = $this->_getHeader('Return-Path');
        $header->setFieldBodyModel('foo@bar.tld');
        $this->assertEquals('foo@bar.tld', $header->getAddress());
    }

    public function testGetBodyModel()
    {
        $header = $this->_getHeader('Return-Path');
        $header->setAddress('foo@bar.tld');
        $this->assertEquals('foo@bar.tld', $header->getFieldBodyModel());
    }

    public function testToString()
    {
        $header = $this->_getHeader('Return-Path');
        $header->setAddress('chris@swiftmailer.org');
        $this->assertEquals('Return-Path: <chris@swiftmailer.org>'."\r\n",
            $header->toString()
            );
    }

    private function _getHeader($name)
    {
        return new Swift_Mime_Headers_PathHeader($name, new Swift_Mime_Grammar());
    }
}
<?php

class Swift_Mime_Headers_UnstructuredHeaderTest extends \SwiftMailerTestCase
{
    private $_charset = 'utf-8';

    public function testTypeIsTextHeader()
    {
        $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true));
        $this->assertEquals(Swift_Mime_Header::TYPE_TEXT, $header->getFieldType());
    }

    public function testGetNameReturnsNameVerbatim()
    {
        $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true));
        $this->assertEquals('Subject', $header->getFieldName());
    }

    public function testGetValueReturnsValueVerbatim()
    {
        $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true));
        $header->setValue('Test');
        $this->assertEquals('Test', $header->getValue());
    }

    public function testBasicStructureIsKeyValuePair()
    {
        /* -- RFC 2822, 2.2
        Header fields are lines composed of a field name, followed by a colon
        (":"), followed by a field body, and terminated by CRLF.
        */
        $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true));
        $header->setValue('Test');
        $this->assertEquals('Subject: Test'."\r\n", $header->toString());
    }

    public function testLongHeadersAreFoldedAtWordBoundary()
    {
        /* -- RFC 2822, 2.2.3
        Each header field is logically a single line of characters comprising
        the field name, the colon, and the field body.  For convenience
        however, and to deal with the 998/78 character limitations per line,
        the field body portion of a header field can be split into a multiple
        line representation; this is called "folding".  The general rule is
        that wherever this standard allows for folding white space (not
        simply WSP characters), a CRLF may be inserted before any WSP.
        */

        $value = 'The quick brown fox jumped over the fence, he was a very very '.
            'scary brown fox with a bushy tail';
        $header = $this->_getHeader('X-Custom-Header',
            $this->_getEncoder('Q', true)
            );
        $header->setValue($value);
        $header->setMaxLineLength(78); //A safe [RFC 2822, 2.2.3] default
        /*
        X-Custom-Header: The quick brown fox jumped over the fence, he was a very very
     scary brown fox with a bushy tail
        */
        $this->assertEquals(
            'X-Custom-Header: The quick brown fox jumped over the fence, he was a'.
            ' very very'."\r\n".//Folding
            ' scary brown fox with a bushy tail'."\r\n",
            $header->toString(), '%s: The header should have been folded at 78th char'
            );
    }

    public function testPrintableAsciiOnlyAppearsInHeaders()
    {
        /* -- RFC 2822, 2.2.
        A field name MUST be composed of printable US-ASCII characters (i.e.,
        characters that have values between 33 and 126, inclusive), except
        colon.  A field body may be composed of any US-ASCII characters,
        except for CR and LF.
        */

        $nonAsciiChar = pack('C', 0x8F);
        $header = $this->_getHeader('X-Test', $this->_getEncoder('Q', true));
        $header->setValue($nonAsciiChar);
        $this->assertRegExp(
            '~^[^:\x00-\x20\x80-\xFF]+: [^\x80-\xFF\r\n]+\r\n$~s',
            $header->toString()
            );
    }

    public function testEncodedWordsFollowGeneralStructure()
    {
        /* -- RFC 2047, 1.
        Generally, an "encoded-word" is a sequence of printable ASCII
        characters that begins with "=?", ends with "?=", and has two "?"s in
        between.
        */

        $nonAsciiChar = pack('C', 0x8F);
        $header = $this->_getHeader('X-Test', $this->_getEncoder('Q', true));
        $header->setValue($nonAsciiChar);
        $this->assertRegExp(
            '~^X-Test: \=?.*?\?.*?\?.*?\?=\r\n$~s',
            $header->toString()
            );
    }

    public function testEncodedWordIncludesCharsetAndEncodingMethodAndText()
    {
        /* -- RFC 2047, 2.
        An 'encoded-word' is defined by the following ABNF grammar.  The
        notation of RFC 822 is used, with the exception that white space
        characters MUST NOT appear between components of an 'encoded-word'.

        encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
        */

        $nonAsciiChar = pack('C', 0x8F);

        $encoder = $this->_getEncoder('Q');
        $encoder->shouldReceive('encodeString')
                ->once()
                ->with($nonAsciiChar, \Mockery::any(), \Mockery::any(), \Mockery::any())
                ->andReturn('=8F');

        $header = $this->_getHeader('X-Test', $encoder);
        $header->setValue($nonAsciiChar);
        $this->assertEquals(
            'X-Test: =?'.$this->_charset.'?Q?=8F?='."\r\n",
            $header->toString()
            );
    }

    public function testEncodedWordsAreUsedToEncodedNonPrintableAscii()
    {
        //SPACE and TAB permitted
        $nonPrintableBytes = array_merge(
            range(0x00, 0x08), range(0x10, 0x19), array(0x7F)
            );

        foreach ($nonPrintableBytes as $byte) {
            $char = pack('C', $byte);
            $encodedChar = sprintf('=%02X', $byte);

            $encoder = $this->_getEncoder('Q');
            $encoder->shouldReceive('encodeString')
                ->once()
                ->with($char, \Mockery::any(), \Mockery::any(), \Mockery::any())
                ->andReturn($encodedChar);

            $header = $this->_getHeader('X-A', $encoder);
            $header->setValue($char);

            $this->assertEquals(
                'X-A: =?'.$this->_charset.'?Q?'.$encodedChar.'?='."\r\n",
                $header->toString(), '%s: Non-printable ascii should be encoded'
                );
        }
    }

    public function testEncodedWordsAreUsedToEncode8BitOctets()
    {
        $_8BitBytes = range(0x80, 0xFF);

        foreach ($_8BitBytes as $byte) {
            $char = pack('C', $byte);
            $encodedChar = sprintf('=%02X', $byte);

            $encoder = $this->_getEncoder('Q');
            $encoder->shouldReceive('encodeString')
                ->once()
                ->with($char, \Mockery::any(), \Mockery::any(), \Mockery::any())
                ->andReturn($encodedChar);

            $header = $this->_getHeader('X-A', $encoder);
            $header->setValue($char);

            $this->assertEquals(
                'X-A: =?'.$this->_charset.'?Q?'.$encodedChar.'?='."\r\n",
                $header->toString(), '%s: 8-bit octets should be encoded'
                );
        }
    }

    public function testEncodedWordsAreNoMoreThan75CharsPerLine()
    {
        /* -- RFC 2047, 2.
        An 'encoded-word' may not be more than 75 characters long, including
        'charset', 'encoding', 'encoded-text', and delimiters.

        ... SNIP ...

        While there is no limit to the length of a multiple-line header
        field, each line of a header field that contains one or more
        'encoded-word's is limited to 76 characters.
        */

        $nonAsciiChar = pack('C', 0x8F);

        $encoder = $this->_getEncoder('Q');
        $encoder->shouldReceive('encodeString')
                ->once()
                ->with($nonAsciiChar, \Mockery::any(), \Mockery::any(), \Mockery::any())
                ->andReturn('=8F');
        //Note that multi-line headers begin with LWSP which makes 75 + 1 = 76
        //Note also that =?utf-8?q??= is 12 chars which makes 75 - 12 = 63

        //* X-Test: is 8 chars
        $header = $this->_getHeader('X-Test', $encoder);
        $header->setValue($nonAsciiChar);

        $this->assertEquals(
            'X-Test: =?'.$this->_charset.'?Q?=8F?='."\r\n",
            $header->toString()
            );
    }

    public function testFWSPIsUsedWhenEncoderReturnsMultipleLines()
    {
        /* --RFC 2047, 2.
        If it is desirable to encode more text than will fit in an 'encoded-word' of
        75 characters, multiple 'encoded-word's (separated by CRLF SPACE) may
        be used.
        */

        //Note the Mock does NOT return 8F encoded, the 8F merely triggers
        // encoding for the sake of testing
        $nonAsciiChar = pack('C', 0x8F);

        $encoder = $this->_getEncoder('Q');
        $encoder->shouldReceive('encodeString')
                ->once()
                ->with($nonAsciiChar, 8, 63, \Mockery::any())
                ->andReturn('line_one_here'."\r\n".'line_two_here');

        //Note that multi-line headers begin with LWSP which makes 75 + 1 = 76
        //Note also that =?utf-8?q??= is 12 chars which makes 75 - 12 = 63

        //* X-Test: is 8 chars
        $header = $this->_getHeader('X-Test', $encoder);
        $header->setValue($nonAsciiChar);

        $this->assertEquals(
            'X-Test: =?'.$this->_charset.'?Q?line_one_here?='."\r\n".
            ' =?'.$this->_charset.'?Q?line_two_here?='."\r\n",
            $header->toString()
            );
    }

    public function testAdjacentWordsAreEncodedTogether()
    {
        /* -- RFC 2047, 5 (1)
     Ordinary ASCII text and 'encoded-word's may appear together in the
     same header field.  However, an 'encoded-word' that appears in a
     header field defined as '*text' MUST be separated from any adjacent
     'encoded-word' or 'text' by 'linear-white-space'.

     -- RFC 2047, 2.
     IMPORTANT: 'encoded-word's are designed to be recognized as 'atom's
     by an RFC 822 parser.  As a consequence, unencoded white space
     characters (such as SPACE and HTAB) are FORBIDDEN within an
     'encoded-word'.
     */

        //It would be valid to encode all words needed, however it's probably
        // easiest to encode the longest amount required at a time

        $word = 'w'.pack('C', 0x8F).'rd';
        $text = 'start '.$word.' '.$word.' then end '.$word;
        // 'start', ' word word', ' and end', ' word'

        $encoder = $this->_getEncoder('Q');
        $encoder->shouldReceive('encodeString')
                ->once()
                ->with($word.' '.$word, \Mockery::any(), \Mockery::any(), \Mockery::any())
                ->andReturn('w=8Frd_w=8Frd');
        $encoder->shouldReceive('encodeString')
                ->once()
                ->with($word, \Mockery::any(), \Mockery::any(), \Mockery::any())
                ->andReturn('w=8Frd');

        $header = $this->_getHeader('X-Test', $encoder);
        $header->setValue($text);

        $headerString = $header->toString();

        $this->assertEquals('X-Test: start =?'.$this->_charset.'?Q?'.
            'w=8Frd_w=8Frd?= then end =?'.$this->_charset.'?Q?'.
            'w=8Frd?='."\r\n", $headerString,
            '%s: Adjacent encoded words should appear grouped with WSP encoded'
            );
    }

    public function testLanguageInformationAppearsInEncodedWords()
    {
        /* -- RFC 2231, 5.
        5.  Language specification in Encoded Words

        RFC 2047 provides support for non-US-ASCII character sets in RFC 822
        message header comments, phrases, and any unstructured text field.
        This is done by defining an encoded word construct which can appear
        in any of these places.  Given that these are fields intended for
        display, it is sometimes necessary to associate language information
        with encoded words as well as just the character set.  This
        specification extends the definition of an encoded word to allow the
        inclusion of such information.  This is simply done by suffixing the
        character set specification with an asterisk followed by the language
        tag.  For example:

                    From: =?US-ASCII*EN?Q?Keith_Moore?= <moore@cs.utk.edu>
        */

        $value = 'fo'.pack('C', 0x8F).'bar';

        $encoder = $this->_getEncoder('Q');
        $encoder->shouldReceive('encodeString')
                ->once()
                ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any())
                ->andReturn('fo=8Fbar');

        $header = $this->_getHeader('Subject', $encoder);
        $header->setLanguage('en');
        $header->setValue($value);
        $this->assertEquals("Subject: =?utf-8*en?Q?fo=8Fbar?=\r\n",
            $header->toString()
            );
    }

    public function testSetBodyModel()
    {
        $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true));
        $header->setFieldBodyModel('test');
        $this->assertEquals('test', $header->getValue());
    }

    public function testGetBodyModel()
    {
        $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true));
        $header->setValue('test');
        $this->assertEquals('test', $header->getFieldBodyModel());
    }

    private function _getHeader($name, $encoder)
    {
        $header = new Swift_Mime_Headers_UnstructuredHeader($name, $encoder, new Swift_Mime_Grammar());
        $header->setCharset($this->_charset);

        return $header;
    }

    private function _getEncoder($type, $stub = false)
    {
        $encoder = $this->getMockery('Swift_Mime_HeaderEncoder')->shouldIgnoreMissing();
        $encoder->shouldReceive('getName')
                ->zeroOrMoreTimes()
                ->andReturn($type);

        return $encoder;
    }
}
<?php

class Swift_Mime_MimePartTest extends Swift_Mime_AbstractMimeEntityTest
{
    public function testNestingLevelIsSubpart()
    {
        $part = $this->_createMimePart($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals(
            Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, $part->getNestingLevel()
            );
    }

    public function testCharsetIsReturnedFromHeader()
    {
        /* -- RFC 2046, 4.1.2.
        A critical parameter that may be specified in the Content-Type field
        for "text/plain" data is the character set.  This is specified with a
        "charset" parameter, as in:

     Content-type: text/plain; charset=iso-8859-1

        Unlike some other parameter values, the values of the charset
        parameter are NOT case sensitive.  The default character set, which
        must be assumed in the absence of a charset parameter, is US-ASCII.
        */

        $cType = $this->_createHeader('Content-Type', 'text/plain',
            array('charset' => 'iso-8859-1')
            );
        $part = $this->_createMimePart($this->_createHeaderSet(array(
            'Content-Type' => $cType, )),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals('iso-8859-1', $part->getCharset());
    }

    public function testCharsetIsSetInHeader()
    {
        $cType = $this->_createHeader('Content-Type', 'text/plain',
            array('charset' => 'iso-8859-1'), false
            );
        $cType->shouldReceive('setParameter')->once()->with('charset', 'utf-8');

        $part = $this->_createMimePart($this->_createHeaderSet(array(
            'Content-Type' => $cType, )),
            $this->_createEncoder(), $this->_createCache()
            );
        $part->setCharset('utf-8');
    }

    public function testCharsetIsSetInHeaderIfPassedToSetBody()
    {
        $cType = $this->_createHeader('Content-Type', 'text/plain',
            array('charset' => 'iso-8859-1'), false
            );
        $cType->shouldReceive('setParameter')->once()->with('charset', 'utf-8');

        $part = $this->_createMimePart($this->_createHeaderSet(array(
            'Content-Type' => $cType, )),
            $this->_createEncoder(), $this->_createCache()
            );
        $part->setBody('', 'text/plian', 'utf-8');
    }

    public function testSettingCharsetNotifiesEncoder()
    {
        $encoder = $this->_createEncoder('quoted-printable', false);
        $encoder->expects($this->once())
                ->method('charsetChanged')
                ->with('utf-8');

        $part = $this->_createMimePart($this->_createHeaderSet(),
            $encoder, $this->_createCache()
            );
        $part->setCharset('utf-8');
    }

    public function testSettingCharsetNotifiesHeaders()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('charsetChanged')
                ->zeroOrMoreTimes()
                ->with('utf-8');

        $part = $this->_createMimePart($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $part->setCharset('utf-8');
    }

    public function testSettingCharsetNotifiesChildren()
    {
        $child = $this->_createChild(0, '', false);
        $child->shouldReceive('charsetChanged')
              ->once()
              ->with('windows-874');

        $part = $this->_createMimePart($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $part->setChildren(array($child));
        $part->setCharset('windows-874');
    }

    public function testCharsetChangeUpdatesCharset()
    {
        $cType = $this->_createHeader('Content-Type', 'text/plain',
            array('charset' => 'iso-8859-1'), false
            );
        $cType->shouldReceive('setParameter')->once()->with('charset', 'utf-8');

        $part = $this->_createMimePart($this->_createHeaderSet(array(
            'Content-Type' => $cType, )),
            $this->_createEncoder(), $this->_createCache()
            );
        $part->charsetChanged('utf-8');
    }

    public function testSettingCharsetClearsCache()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('toString')
                ->zeroOrMoreTimes()
                ->andReturn("Content-Type: text/plain; charset=utf-8\r\n");

        $cache = $this->_createCache(false);

        $entity = $this->_createEntity($headers, $this->_createEncoder(),
            $cache
            );

        $entity->setBody("blah\r\nblah!");
        $entity->toString();

        // Initialize the expectation here because we only care about what happens in setCharset()
        $cache->shouldReceive('clearKey')
                ->once()
                ->with(\Mockery::any(), 'body');

        $entity->setCharset('iso-2022');
    }

    public function testFormatIsReturnedFromHeader()
    {
        /* -- RFC 3676.
     */

        $cType = $this->_createHeader('Content-Type', 'text/plain',
            array('format' => 'flowed')
            );
        $part = $this->_createMimePart($this->_createHeaderSet(array(
            'Content-Type' => $cType, )),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals('flowed', $part->getFormat());
    }

    public function testFormatIsSetInHeader()
    {
        $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false);
        $cType->shouldReceive('setParameter')->once()->with('format', 'fixed');

        $part = $this->_createMimePart($this->_createHeaderSet(array(
            'Content-Type' => $cType, )),
            $this->_createEncoder(), $this->_createCache()
            );
        $part->setFormat('fixed');
    }

    public function testDelSpIsReturnedFromHeader()
    {
        /* -- RFC 3676.
     */

        $cType = $this->_createHeader('Content-Type', 'text/plain',
            array('delsp' => 'no')
            );
        $part = $this->_createMimePart($this->_createHeaderSet(array(
            'Content-Type' => $cType, )),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertFalse($part->getDelSp());
    }

    public function testDelSpIsSetInHeader()
    {
        $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false);
        $cType->shouldReceive('setParameter')->once()->with('delsp', 'yes');

        $part = $this->_createMimePart($this->_createHeaderSet(array(
            'Content-Type' => $cType, )),
            $this->_createEncoder(), $this->_createCache()
            );
        $part->setDelSp(true);
    }

    public function testFluidInterface()
    {
        $part = $this->_createMimePart($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );

        $this->assertSame($part,
            $part
            ->setContentType('text/plain')
            ->setEncoder($this->_createEncoder())
            ->setId('foo@bar')
            ->setDescription('my description')
            ->setMaxLineLength(998)
            ->setBody('xx')
            ->setBoundary('xyz')
            ->setChildren(array())
            ->setCharset('utf-8')
            ->setFormat('flowed')
            ->setDelSp(true)
            );
    }

    // -- Private helpers

    //abstract
    protected function _createEntity($headers, $encoder, $cache)
    {
        return $this->_createMimePart($headers, $encoder, $cache);
    }

    protected function _createMimePart($headers, $encoder, $cache)
    {
        return new Swift_Mime_MimePart($headers, $encoder, $cache, new Swift_Mime_Grammar());
    }
}
<?php

class Swift_Mime_SimpleHeaderFactoryTest extends \PHPUnit_Framework_TestCase
{
    private $_factory;

    public function setUp()
    {
        $this->_factory = $this->_createFactory();
    }

    public function testMailboxHeaderIsCorrectType()
    {
        $header = $this->_factory->createMailboxHeader('X-Foo');
        $this->assertInstanceof('Swift_Mime_Headers_MailboxHeader', $header);
    }

    public function testMailboxHeaderHasCorrectName()
    {
        $header = $this->_factory->createMailboxHeader('X-Foo');
        $this->assertEquals('X-Foo', $header->getFieldName());
    }

    public function testMailboxHeaderHasCorrectModel()
    {
        $header = $this->_factory->createMailboxHeader('X-Foo',
            array('foo@bar' => 'FooBar')
            );
        $this->assertEquals(array('foo@bar' => 'FooBar'), $header->getFieldBodyModel());
    }

    public function testDateHeaderHasCorrectType()
    {
        $header = $this->_factory->createDateHeader('X-Date');
        $this->assertInstanceof('Swift_Mime_Headers_DateHeader', $header);
    }

    public function testDateHeaderHasCorrectName()
    {
        $header = $this->_factory->createDateHeader('X-Date');
        $this->assertEquals('X-Date', $header->getFieldName());
    }

    public function testDateHeaderHasCorrectModel()
    {
        $header = $this->_factory->createDateHeader('X-Date', 123);
        $this->assertEquals(123, $header->getFieldBodyModel());
    }

    public function testTextHeaderHasCorrectType()
    {
        $header = $this->_factory->createTextHeader('X-Foo');
        $this->assertInstanceof('Swift_Mime_Headers_UnstructuredHeader', $header);
    }

    public function testTextHeaderHasCorrectName()
    {
        $header = $this->_factory->createTextHeader('X-Foo');
        $this->assertEquals('X-Foo', $header->getFieldName());
    }

    public function testTextHeaderHasCorrectModel()
    {
        $header = $this->_factory->createTextHeader('X-Foo', 'bar');
        $this->assertEquals('bar', $header->getFieldBodyModel());
    }

    public function testParameterizedHeaderHasCorrectType()
    {
        $header = $this->_factory->createParameterizedHeader('X-Foo');
        $this->assertInstanceof('Swift_Mime_Headers_ParameterizedHeader', $header);
    }

    public function testParameterizedHeaderHasCorrectName()
    {
        $header = $this->_factory->createParameterizedHeader('X-Foo');
        $this->assertEquals('X-Foo', $header->getFieldName());
    }

    public function testParameterizedHeaderHasCorrectModel()
    {
        $header = $this->_factory->createParameterizedHeader('X-Foo', 'bar');
        $this->assertEquals('bar', $header->getFieldBodyModel());
    }

    public function testParameterizedHeaderHasCorrectParams()
    {
        $header = $this->_factory->createParameterizedHeader('X-Foo', 'bar',
            array('zip' => 'button')
            );
        $this->assertEquals(array('zip' => 'button'), $header->getParameters());
    }

    public function testIdHeaderHasCorrectType()
    {
        $header = $this->_factory->createIdHeader('X-ID');
        $this->assertInstanceof('Swift_Mime_Headers_IdentificationHeader', $header);
    }

    public function testIdHeaderHasCorrectName()
    {
        $header = $this->_factory->createIdHeader('X-ID');
        $this->assertEquals('X-ID', $header->getFieldName());
    }

    public function testIdHeaderHasCorrectModel()
    {
        $header = $this->_factory->createIdHeader('X-ID', 'xyz@abc');
        $this->assertEquals(array('xyz@abc'), $header->getFieldBodyModel());
    }

    public function testPathHeaderHasCorrectType()
    {
        $header = $this->_factory->createPathHeader('X-Path');
        $this->assertInstanceof('Swift_Mime_Headers_PathHeader', $header);
    }

    public function testPathHeaderHasCorrectName()
    {
        $header = $this->_factory->createPathHeader('X-Path');
        $this->assertEquals('X-Path', $header->getFieldName());
    }

    public function testPathHeaderHasCorrectModel()
    {
        $header = $this->_factory->createPathHeader('X-Path', 'foo@bar');
        $this->assertEquals('foo@bar', $header->getFieldBodyModel());
    }

    public function testCharsetChangeNotificationNotifiesEncoders()
    {
        $encoder = $this->_createHeaderEncoder();
        $encoder->expects($this->once())
                ->method('charsetChanged')
                ->with('utf-8');
        $paramEncoder = $this->_createParamEncoder();
        $paramEncoder->expects($this->once())
                     ->method('charsetChanged')
                     ->with('utf-8');

        $factory = $this->_createFactory($encoder, $paramEncoder);

        $factory->charsetChanged('utf-8');
    }

    // -- Creation methods

    private function _createFactory($encoder = null, $paramEncoder = null)
    {
        return new Swift_Mime_SimpleHeaderFactory(
            $encoder
                ? $encoder : $this->_createHeaderEncoder(),
            $paramEncoder
                ? $paramEncoder : $this->_createParamEncoder(),
            new Swift_Mime_Grammar()
            );
    }

    private function _createHeaderEncoder()
    {
        return $this->getMockBuilder('Swift_Mime_HeaderEncoder')->getMock();
    }

    private function _createParamEncoder()
    {
        return $this->getMockBuilder('Swift_Encoder')->getMock();
    }
}
<?php

class Swift_Mime_SimpleHeaderSetTest extends \PHPUnit_Framework_TestCase
{
    public function testAddMailboxHeaderDelegatesToFactory()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->once())
                ->method('createMailboxHeader')
                ->with('From', array('person@domain' => 'Person'))
                ->will($this->returnValue($this->_createHeader('From')));

        $set = $this->_createSet($factory);
        $set->addMailboxHeader('From', array('person@domain' => 'Person'));
    }

    public function testAddDateHeaderDelegatesToFactory()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->once())
                ->method('createDateHeader')
                ->with('Date', 1234)
                ->will($this->returnValue($this->_createHeader('Date')));

        $set = $this->_createSet($factory);
        $set->addDateHeader('Date', 1234);
    }

    public function testAddTextHeaderDelegatesToFactory()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->once())
                ->method('createTextHeader')
                ->with('Subject', 'some text')
                ->will($this->returnValue($this->_createHeader('Subject')));

        $set = $this->_createSet($factory);
        $set->addTextHeader('Subject', 'some text');
    }

    public function testAddParameterizedHeaderDelegatesToFactory()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->once())
                ->method('createParameterizedHeader')
                ->with('Content-Type', 'text/plain', array('charset' => 'utf-8'))
                ->will($this->returnValue($this->_createHeader('Content-Type')));

        $set = $this->_createSet($factory);
        $set->addParameterizedHeader('Content-Type', 'text/plain',
            array('charset' => 'utf-8')
            );
    }

    public function testAddIdHeaderDelegatesToFactory()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->once())
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($this->_createHeader('Message-ID')));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
    }

    public function testAddPathHeaderDelegatesToFactory()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->once())
                ->method('createPathHeader')
                ->with('Return-Path', 'some@path')
                ->will($this->returnValue($this->_createHeader('Return-Path')));

        $set = $this->_createSet($factory);
        $set->addPathHeader('Return-Path', 'some@path');
    }

    public function testHasReturnsFalseWhenNoHeaders()
    {
        $set = $this->_createSet($this->_createFactory());
        $this->assertFalse($set->has('Some-Header'));
    }

    public function testAddedMailboxHeaderIsSeenByHas()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->once())
                ->method('createMailboxHeader')
                ->with('From', array('person@domain' => 'Person'))
                ->will($this->returnValue($this->_createHeader('From')));

        $set = $this->_createSet($factory);
        $set->addMailboxHeader('From', array('person@domain' => 'Person'));
        $this->assertTrue($set->has('From'));
    }

    public function testAddedDateHeaderIsSeenByHas()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->once())
                ->method('createDateHeader')
                ->with('Date', 1234)
                ->will($this->returnValue($this->_createHeader('Date')));

        $set = $this->_createSet($factory);
        $set->addDateHeader('Date', 1234);
        $this->assertTrue($set->has('Date'));
    }

    public function testAddedTextHeaderIsSeenByHas()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->once())
                ->method('createTextHeader')
                ->with('Subject', 'some text')
                ->will($this->returnValue($this->_createHeader('Subject')));

        $set = $this->_createSet($factory);
        $set->addTextHeader('Subject', 'some text');
        $this->assertTrue($set->has('Subject'));
    }

    public function testAddedParameterizedHeaderIsSeenByHas()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->once())
                ->method('createParameterizedHeader')
                ->with('Content-Type', 'text/plain', array('charset' => 'utf-8'))
                ->will($this->returnValue($this->_createHeader('Content-Type')));

        $set = $this->_createSet($factory);
        $set->addParameterizedHeader('Content-Type', 'text/plain',
            array('charset' => 'utf-8')
            );
        $this->assertTrue($set->has('Content-Type'));
    }

    public function testAddedIdHeaderIsSeenByHas()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->once())
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($this->_createHeader('Message-ID')));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $this->assertTrue($set->has('Message-ID'));
    }

    public function testAddedPathHeaderIsSeenByHas()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->once())
                ->method('createPathHeader')
                ->with('Return-Path', 'some@path')
                ->will($this->returnValue($this->_createHeader('Return-Path')));

        $set = $this->_createSet($factory);
        $set->addPathHeader('Return-Path', 'some@path');
        $this->assertTrue($set->has('Return-Path'));
    }

    public function testNewlySetHeaderIsSeenByHas()
    {
        $factory = $this->_createFactory();
        $header = $this->_createHeader('X-Foo', 'bar');
        $set = $this->_createSet($factory);
        $set->set($header);
        $this->assertTrue($set->has('X-Foo'));
    }

    public function testHasCanAcceptOffset()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->once())
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($this->_createHeader('Message-ID')));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $this->assertTrue($set->has('Message-ID', 0));
    }

    public function testHasWithIllegalOffsetReturnsFalse()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->once())
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($this->_createHeader('Message-ID')));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $this->assertFalse($set->has('Message-ID', 1));
    }

    public function testHasCanDistinguishMultipleHeaders()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($this->_createHeader('Message-ID')));
        $factory->expects($this->at(1))
                ->method('createIdHeader')
                ->with('Message-ID', 'other@id')
                ->will($this->returnValue($this->_createHeader('Message-ID')));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $set->addIdHeader('Message-ID', 'other@id');
        $this->assertTrue($set->has('Message-ID', 1));
    }

    public function testGetWithUnspecifiedOffset()
    {
        $header = $this->_createHeader('Message-ID');
        $factory = $this->_createFactory();
        $factory->expects($this->once())
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($header));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $this->assertSame($header, $set->get('Message-ID'));
    }

    public function testGetWithSpeiciedOffset()
    {
        $header0 = $this->_createHeader('Message-ID');
        $header1 = $this->_createHeader('Message-ID');
        $header2 = $this->_createHeader('Message-ID');
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($header0));
        $factory->expects($this->at(1))
                ->method('createIdHeader')
                ->with('Message-ID', 'other@id')
                ->will($this->returnValue($header1));
        $factory->expects($this->at(2))
                ->method('createIdHeader')
                ->with('Message-ID', 'more@id')
                ->will($this->returnValue($header2));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $set->addIdHeader('Message-ID', 'other@id');
        $set->addIdHeader('Message-ID', 'more@id');
        $this->assertSame($header1, $set->get('Message-ID', 1));
    }

    public function testGetReturnsNullIfHeaderNotSet()
    {
        $set = $this->_createSet($this->_createFactory());
        $this->assertNull($set->get('Message-ID', 99));
    }

    public function testGetAllReturnsAllHeadersMatchingName()
    {
        $header0 = $this->_createHeader('Message-ID');
        $header1 = $this->_createHeader('Message-ID');
        $header2 = $this->_createHeader('Message-ID');
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($header0));
        $factory->expects($this->at(1))
                ->method('createIdHeader')
                ->with('Message-ID', 'other@id')
                ->will($this->returnValue($header1));
        $factory->expects($this->at(2))
                ->method('createIdHeader')
                ->with('Message-ID', 'more@id')
                ->will($this->returnValue($header2));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $set->addIdHeader('Message-ID', 'other@id');
        $set->addIdHeader('Message-ID', 'more@id');

        $this->assertEquals(array($header0, $header1, $header2),
            $set->getAll('Message-ID')
            );
    }

    public function testGetAllReturnsAllHeadersIfNoArguments()
    {
        $header0 = $this->_createHeader('Message-ID');
        $header1 = $this->_createHeader('Subject');
        $header2 = $this->_createHeader('To');
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($header0));
        $factory->expects($this->at(1))
                ->method('createIdHeader')
                ->with('Subject', 'thing')
                ->will($this->returnValue($header1));
        $factory->expects($this->at(2))
                ->method('createIdHeader')
                ->with('To', 'person@example.org')
                ->will($this->returnValue($header2));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $set->addIdHeader('Subject', 'thing');
        $set->addIdHeader('To', 'person@example.org');

        $this->assertEquals(array($header0, $header1, $header2),
            $set->getAll()
            );
    }

    public function testGetAllReturnsEmptyArrayIfNoneSet()
    {
        $set = $this->_createSet($this->_createFactory());
        $this->assertEquals(array(), $set->getAll('Received'));
    }

    public function testRemoveWithUnspecifiedOffset()
    {
        $header = $this->_createHeader('Message-ID');
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($header));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $set->remove('Message-ID');
        $this->assertFalse($set->has('Message-ID'));
    }

    public function testRemoveWithSpecifiedIndexRemovesHeader()
    {
        $header0 = $this->_createHeader('Message-ID');
        $header1 = $this->_createHeader('Message-ID');
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($header0));
        $factory->expects($this->at(1))
                ->method('createIdHeader')
                ->with('Message-ID', 'other@id')
                ->will($this->returnValue($header1));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $set->addIdHeader('Message-ID', 'other@id');
        $set->remove('Message-ID', 0);
        $this->assertFalse($set->has('Message-ID', 0));
        $this->assertTrue($set->has('Message-ID', 1));
        $this->assertTrue($set->has('Message-ID'));
        $set->remove('Message-ID', 1);
        $this->assertFalse($set->has('Message-ID', 1));
        $this->assertFalse($set->has('Message-ID'));
    }

    public function testRemoveWithSpecifiedIndexLeavesOtherHeaders()
    {
        $header0 = $this->_createHeader('Message-ID');
        $header1 = $this->_createHeader('Message-ID');
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($header0));
        $factory->expects($this->at(1))
                ->method('createIdHeader')
                ->with('Message-ID', 'other@id')
                ->will($this->returnValue($header1));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $set->addIdHeader('Message-ID', 'other@id');
        $set->remove('Message-ID', 1);
        $this->assertTrue($set->has('Message-ID', 0));
    }

    public function testRemoveWithInvalidOffsetDoesNothing()
    {
        $header = $this->_createHeader('Message-ID');
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($header));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $set->remove('Message-ID', 50);
        $this->assertTrue($set->has('Message-ID'));
    }

    public function testRemoveAllRemovesAllHeadersWithName()
    {
        $header0 = $this->_createHeader('Message-ID');
        $header1 = $this->_createHeader('Message-ID');
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($header0));
        $factory->expects($this->at(1))
                ->method('createIdHeader')
                ->with('Message-ID', 'other@id')
                ->will($this->returnValue($header1));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $set->addIdHeader('Message-ID', 'other@id');
        $set->removeAll('Message-ID');
        $this->assertFalse($set->has('Message-ID', 0));
        $this->assertFalse($set->has('Message-ID', 1));
    }

    public function testHasIsNotCaseSensitive()
    {
        $header = $this->_createHeader('Message-ID');
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($header));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $this->assertTrue($set->has('message-id'));
    }

    public function testGetIsNotCaseSensitive()
    {
        $header = $this->_createHeader('Message-ID');
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($header));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $this->assertSame($header, $set->get('message-id'));
    }

    public function testGetAllIsNotCaseSensitive()
    {
        $header = $this->_createHeader('Message-ID');
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($header));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $this->assertEquals(array($header), $set->getAll('message-id'));
    }

    public function testRemoveIsNotCaseSensitive()
    {
        $header = $this->_createHeader('Message-ID');
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($header));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $set->remove('message-id');
        $this->assertFalse($set->has('Message-ID'));
    }

    public function testRemoveAllIsNotCaseSensitive()
    {
        $header = $this->_createHeader('Message-ID');
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createIdHeader')
                ->with('Message-ID', 'some@id')
                ->will($this->returnValue($header));

        $set = $this->_createSet($factory);
        $set->addIdHeader('Message-ID', 'some@id');
        $set->removeAll('message-id');
        $this->assertFalse($set->has('Message-ID'));
    }

    public function testNewInstance()
    {
        $set = $this->_createSet($this->_createFactory());
        $instance = $set->newInstance();
        $this->assertInstanceof('Swift_Mime_HeaderSet', $instance);
    }

    public function testToStringJoinsHeadersTogether()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createTextHeader')
                ->with('Foo', 'bar')
                ->will($this->returnValue($this->_createHeader('Foo', 'bar')));
        $factory->expects($this->at(1))
                ->method('createTextHeader')
                ->with('Zip', 'buttons')
                ->will($this->returnValue($this->_createHeader('Zip', 'buttons')));

        $set = $this->_createSet($factory);
        $set->addTextHeader('Foo', 'bar');
        $set->addTextHeader('Zip', 'buttons');
        $this->assertEquals(
            "Foo: bar\r\n".
            "Zip: buttons\r\n",
            $set->toString()
            );
    }

    public function testHeadersWithoutBodiesAreNotDisplayed()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createTextHeader')
                ->with('Foo', 'bar')
                ->will($this->returnValue($this->_createHeader('Foo', 'bar')));
        $factory->expects($this->at(1))
                ->method('createTextHeader')
                ->with('Zip', '')
                ->will($this->returnValue($this->_createHeader('Zip', '')));

        $set = $this->_createSet($factory);
        $set->addTextHeader('Foo', 'bar');
        $set->addTextHeader('Zip', '');
        $this->assertEquals(
            "Foo: bar\r\n",
            $set->toString()
            );
    }

    public function testHeadersWithoutBodiesCanBeForcedToDisplay()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createTextHeader')
                ->with('Foo', '')
                ->will($this->returnValue($this->_createHeader('Foo', '')));
        $factory->expects($this->at(1))
                ->method('createTextHeader')
                ->with('Zip', '')
                ->will($this->returnValue($this->_createHeader('Zip', '')));

        $set = $this->_createSet($factory);
        $set->addTextHeader('Foo', '');
        $set->addTextHeader('Zip', '');
        $set->setAlwaysDisplayed(array('Foo', 'Zip'));
        $this->assertEquals(
            "Foo: \r\n".
            "Zip: \r\n",
            $set->toString()
            );
    }

    public function testHeaderSequencesCanBeSpecified()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createTextHeader')
                ->with('Third', 'three')
                ->will($this->returnValue($this->_createHeader('Third', 'three')));
        $factory->expects($this->at(1))
                ->method('createTextHeader')
                ->with('First', 'one')
                ->will($this->returnValue($this->_createHeader('First', 'one')));
        $factory->expects($this->at(2))
                ->method('createTextHeader')
                ->with('Second', 'two')
                ->will($this->returnValue($this->_createHeader('Second', 'two')));

        $set = $this->_createSet($factory);
        $set->addTextHeader('Third', 'three');
        $set->addTextHeader('First', 'one');
        $set->addTextHeader('Second', 'two');

        $set->defineOrdering(array('First', 'Second', 'Third'));

        $this->assertEquals(
            "First: one\r\n".
            "Second: two\r\n".
            "Third: three\r\n",
            $set->toString()
            );
    }

    public function testUnsortedHeadersAppearAtEnd()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createTextHeader')
                ->with('Fourth', 'four')
                ->will($this->returnValue($this->_createHeader('Fourth', 'four')));
        $factory->expects($this->at(1))
                ->method('createTextHeader')
                ->with('Fifth', 'five')
                ->will($this->returnValue($this->_createHeader('Fifth', 'five')));
        $factory->expects($this->at(2))
                ->method('createTextHeader')
                ->with('Third', 'three')
                ->will($this->returnValue($this->_createHeader('Third', 'three')));
        $factory->expects($this->at(3))
                ->method('createTextHeader')
                ->with('First', 'one')
                ->will($this->returnValue($this->_createHeader('First', 'one')));
        $factory->expects($this->at(4))
                ->method('createTextHeader')
                ->with('Second', 'two')
                ->will($this->returnValue($this->_createHeader('Second', 'two')));

        $set = $this->_createSet($factory);
        $set->addTextHeader('Fourth', 'four');
        $set->addTextHeader('Fifth', 'five');
        $set->addTextHeader('Third', 'three');
        $set->addTextHeader('First', 'one');
        $set->addTextHeader('Second', 'two');

        $set->defineOrdering(array('First', 'Second', 'Third'));

        $this->assertEquals(
            "First: one\r\n".
            "Second: two\r\n".
            "Third: three\r\n".
            "Fourth: four\r\n".
            "Fifth: five\r\n",
            $set->toString()
            );
    }

    public function testSettingCharsetNotifiesAlreadyExistingHeaders()
    {
        $subject = $this->_createHeader('Subject', 'some text');
        $xHeader = $this->_createHeader('X-Header', 'some text');
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createTextHeader')
                ->with('Subject', 'some text')
                ->will($this->returnValue($subject));
        $factory->expects($this->at(1))
                ->method('createTextHeader')
                ->with('X-Header', 'some text')
                ->will($this->returnValue($xHeader));
        $subject->expects($this->once())
                ->method('setCharset')
                ->with('utf-8');
        $xHeader->expects($this->once())
                ->method('setCharset')
                ->with('utf-8');

        $set = $this->_createSet($factory);
        $set->addTextHeader('Subject', 'some text');
        $set->addTextHeader('X-Header', 'some text');

        $set->setCharset('utf-8');
    }

    public function testCharsetChangeNotifiesAlreadyExistingHeaders()
    {
        $subject = $this->_createHeader('Subject', 'some text');
        $xHeader = $this->_createHeader('X-Header', 'some text');
        $factory = $this->_createFactory();
        $factory->expects($this->at(0))
                ->method('createTextHeader')
                ->with('Subject', 'some text')
                ->will($this->returnValue($subject));
        $factory->expects($this->at(1))
                ->method('createTextHeader')
                ->with('X-Header', 'some text')
                ->will($this->returnValue($xHeader));
        $subject->expects($this->once())
                ->method('setCharset')
                ->with('utf-8');
        $xHeader->expects($this->once())
                ->method('setCharset')
                ->with('utf-8');

        $set = $this->_createSet($factory);
        $set->addTextHeader('Subject', 'some text');
        $set->addTextHeader('X-Header', 'some text');

        $set->charsetChanged('utf-8');
    }

    public function testCharsetChangeNotifiesFactory()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->once())
                ->method('charsetChanged')
                ->with('utf-8');

        $set = $this->_createSet($factory);

        $set->setCharset('utf-8');
    }

    // -- Creation methods

    private function _createSet($factory)
    {
        return new Swift_Mime_SimpleHeaderSet($factory);
    }

    private function _createFactory()
    {
        return $this->getMockBuilder('Swift_Mime_HeaderFactory')->getMock();
    }

    private function _createHeader($name, $body = '')
    {
        $header = $this->getMockBuilder('Swift_Mime_Header')->getMock();
        $header->expects($this->any())
               ->method('getFieldName')
               ->will($this->returnValue($name));
        $header->expects($this->any())
               ->method('toString')
               ->will($this->returnValue(sprintf("%s: %s\r\n", $name, $body)));
        $header->expects($this->any())
               ->method('getFieldBody')
               ->will($this->returnValue($body));

        return $header;
    }
}
<?php

class Swift_Mime_SimpleMessageTest extends Swift_Mime_MimePartTest
{
    public function testNestingLevelIsSubpart()
    {
        //Overridden
    }

    public function testNestingLevelIsTop()
    {
        $message = $this->_createMessage($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals(
            Swift_Mime_MimeEntity::LEVEL_TOP, $message->getNestingLevel()
            );
    }

    public function testDateIsReturnedFromHeader()
    {
        $date = $this->_createHeader('Date', 123);
        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Date' => $date)),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals(123, $message->getDate());
    }

    public function testDateIsSetInHeader()
    {
        $date = $this->_createHeader('Date', 123, array(), false);
        $date->shouldReceive('setFieldBodyModel')
             ->once()
             ->with(1234);
        $date->shouldReceive('setFieldBodyModel')
             ->zeroOrMoreTimes();

        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Date' => $date)),
            $this->_createEncoder(), $this->_createCache()
            );
        $message->setDate(1234);
    }

    public function testDateHeaderIsCreatedIfNonePresent()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addDateHeader')
                ->once()
                ->with('Date', 1234);
        $headers->shouldReceive('addDateHeader')
                ->zeroOrMoreTimes();

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $message->setDate(1234);
    }

    public function testDateHeaderIsAddedDuringConstruction()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addDateHeader')
                ->once()
                ->with('Date', '/^[0-9]+$/D');

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
    }

    public function testIdIsReturnedFromHeader()
    {
        /* -- RFC 2045, 7.
        In constructing a high-level user agent, it may be desirable to allow
        one body to make reference to another.  Accordingly, bodies may be
        labelled using the "Content-ID" header field, which is syntactically
        identical to the "Message-ID" header field
        */

        $messageId = $this->_createHeader('Message-ID', 'a@b');
        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Message-ID' => $messageId)),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals('a@b', $message->getId());
    }

    public function testIdIsSetInHeader()
    {
        $messageId = $this->_createHeader('Message-ID', 'a@b', array(), false);
        $messageId->shouldReceive('setFieldBodyModel')
                  ->once()
                  ->with('x@y');
        $messageId->shouldReceive('setFieldBodyModel')
                  ->zeroOrMoreTimes();

        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Message-ID' => $messageId)),
            $this->_createEncoder(), $this->_createCache()
            );
        $message->setId('x@y');
    }

    public function testIdIsAutoGenerated()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addIdHeader')
                ->once()
                ->with('Message-ID', '/^.*?@.*?$/D');

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
    }

    public function testSubjectIsReturnedFromHeader()
    {
        /* -- RFC 2822, 3.6.5.
     */

        $subject = $this->_createHeader('Subject', 'example subject');
        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Subject' => $subject)),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals('example subject', $message->getSubject());
    }

    public function testSubjectIsSetInHeader()
    {
        $subject = $this->_createHeader('Subject', '', array(), false);
        $subject->shouldReceive('setFieldBodyModel')
                ->once()
                ->with('foo');

        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Subject' => $subject)),
            $this->_createEncoder(), $this->_createCache()
            );
        $message->setSubject('foo');
    }

    public function testSubjectHeaderIsCreatedIfNotPresent()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addTextHeader')
                ->once()
                ->with('Subject', 'example subject');
        $headers->shouldReceive('addTextHeader')
                ->zeroOrMoreTimes();

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $message->setSubject('example subject');
    }

    public function testReturnPathIsReturnedFromHeader()
    {
        /* -- RFC 2822, 3.6.7.
     */

        $path = $this->_createHeader('Return-Path', 'bounces@domain');
        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Return-Path' => $path)),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals('bounces@domain', $message->getReturnPath());
    }

    public function testReturnPathIsSetInHeader()
    {
        $path = $this->_createHeader('Return-Path', '', array(), false);
        $path->shouldReceive('setFieldBodyModel')
             ->once()
             ->with('bounces@domain');

        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Return-Path' => $path)),
            $this->_createEncoder(), $this->_createCache()
            );
        $message->setReturnPath('bounces@domain');
    }

    public function testReturnPathHeaderIsAddedIfNoneSet()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addPathHeader')
                ->once()
                ->with('Return-Path', 'bounces@domain');

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $message->setReturnPath('bounces@domain');
    }

    public function testSenderIsReturnedFromHeader()
    {
        /* -- RFC 2822, 3.6.2.
     */

        $sender = $this->_createHeader('Sender', array('sender@domain' => 'Name'));
        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Sender' => $sender)),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals(array('sender@domain' => 'Name'), $message->getSender());
    }

    public function testSenderIsSetInHeader()
    {
        $sender = $this->_createHeader('Sender', array('sender@domain' => 'Name'),
            array(), false
            );
        $sender->shouldReceive('setFieldBodyModel')
               ->once()
               ->with(array('other@domain' => 'Other'));

        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Sender' => $sender)),
            $this->_createEncoder(), $this->_createCache()
            );
        $message->setSender(array('other@domain' => 'Other'));
    }

    public function testSenderHeaderIsAddedIfNoneSet()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addMailboxHeader')
                ->once()
                ->with('Sender', (array) 'sender@domain');
        $headers->shouldReceive('addMailboxHeader')
                ->zeroOrMoreTimes();

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $message->setSender('sender@domain');
    }

    public function testNameCanBeUsedInSenderHeader()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addMailboxHeader')
                ->once()
                ->with('Sender', array('sender@domain' => 'Name'));
        $headers->shouldReceive('addMailboxHeader')
                ->zeroOrMoreTimes();

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $message->setSender('sender@domain', 'Name');
    }

    public function testFromIsReturnedFromHeader()
    {
        /* -- RFC 2822, 3.6.2.
     */

        $from = $this->_createHeader('From', array('from@domain' => 'Name'));
        $message = $this->_createMessage(
            $this->_createHeaderSet(array('From' => $from)),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals(array('from@domain' => 'Name'), $message->getFrom());
    }

    public function testFromIsSetInHeader()
    {
        $from = $this->_createHeader('From', array('from@domain' => 'Name'),
            array(), false
            );
        $from->shouldReceive('setFieldBodyModel')
             ->once()
             ->with(array('other@domain' => 'Other'));

        $message = $this->_createMessage(
            $this->_createHeaderSet(array('From' => $from)),
            $this->_createEncoder(), $this->_createCache()
            );
        $message->setFrom(array('other@domain' => 'Other'));
    }

    public function testFromIsAddedToHeadersDuringAddFrom()
    {
        $from = $this->_createHeader('From', array('from@domain' => 'Name'),
            array(), false
            );
        $from->shouldReceive('setFieldBodyModel')
             ->once()
             ->with(array('from@domain' => 'Name', 'other@domain' => 'Other'));

        $message = $this->_createMessage(
            $this->_createHeaderSet(array('From' => $from)),
            $this->_createEncoder(), $this->_createCache()
            );
        $message->addFrom('other@domain', 'Other');
    }

    public function testFromHeaderIsAddedIfNoneSet()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addMailboxHeader')
                ->once()
                ->with('From', (array) 'from@domain');
        $headers->shouldReceive('addMailboxHeader')
                ->zeroOrMoreTimes();

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $message->setFrom('from@domain');
    }

    public function testPersonalNameCanBeUsedInFromAddress()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addMailboxHeader')
                ->once()
                ->with('From', array('from@domain' => 'Name'));
        $headers->shouldReceive('addMailboxHeader')
                ->zeroOrMoreTimes();

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $message->setFrom('from@domain', 'Name');
    }

    public function testReplyToIsReturnedFromHeader()
    {
        /* -- RFC 2822, 3.6.2.
     */

        $reply = $this->_createHeader('Reply-To', array('reply@domain' => 'Name'));
        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Reply-To' => $reply)),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals(array('reply@domain' => 'Name'), $message->getReplyTo());
    }

    public function testReplyToIsSetInHeader()
    {
        $reply = $this->_createHeader('Reply-To', array('reply@domain' => 'Name'),
            array(), false
            );
        $reply->shouldReceive('setFieldBodyModel')
              ->once()
              ->with(array('other@domain' => 'Other'));

        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Reply-To' => $reply)),
            $this->_createEncoder(), $this->_createCache()
            );
        $message->setReplyTo(array('other@domain' => 'Other'));
    }

    public function testReplyToIsAddedToHeadersDuringAddReplyTo()
    {
        $replyTo = $this->_createHeader('Reply-To', array('from@domain' => 'Name'),
            array(), false
            );
        $replyTo->shouldReceive('setFieldBodyModel')
                ->once()
                ->with(array('from@domain' => 'Name', 'other@domain' => 'Other'));

        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Reply-To' => $replyTo)),
            $this->_createEncoder(), $this->_createCache()
            );
        $message->addReplyTo('other@domain', 'Other');
    }

    public function testReplyToHeaderIsAddedIfNoneSet()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addMailboxHeader')
                ->once()
                ->with('Reply-To', (array) 'reply@domain');
        $headers->shouldReceive('addMailboxHeader')
                ->zeroOrMoreTimes();

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $message->setReplyTo('reply@domain');
    }

    public function testNameCanBeUsedInReplyTo()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addMailboxHeader')
                ->once()
                ->with('Reply-To', array('reply@domain' => 'Name'));
        $headers->shouldReceive('addMailboxHeader')
                ->zeroOrMoreTimes();

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $message->setReplyTo('reply@domain', 'Name');
    }

    public function testToIsReturnedFromHeader()
    {
        /* -- RFC 2822, 3.6.3.
     */

        $to = $this->_createHeader('To', array('to@domain' => 'Name'));
        $message = $this->_createMessage(
            $this->_createHeaderSet(array('To' => $to)),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals(array('to@domain' => 'Name'), $message->getTo());
    }

    public function testToIsSetInHeader()
    {
        $to = $this->_createHeader('To', array('to@domain' => 'Name'),
            array(), false
            );
        $to->shouldReceive('setFieldBodyModel')
           ->once()
           ->with(array('other@domain' => 'Other'));

        $message = $this->_createMessage(
            $this->_createHeaderSet(array('To' => $to)),
            $this->_createEncoder(), $this->_createCache()
            );
        $message->setTo(array('other@domain' => 'Other'));
    }

    public function testToIsAddedToHeadersDuringAddTo()
    {
        $to = $this->_createHeader('To', array('from@domain' => 'Name'),
            array(), false
            );
        $to->shouldReceive('setFieldBodyModel')
           ->once()
           ->with(array('from@domain' => 'Name', 'other@domain' => 'Other'));

        $message = $this->_createMessage(
            $this->_createHeaderSet(array('To' => $to)),
            $this->_createEncoder(), $this->_createCache()
            );
        $message->addTo('other@domain', 'Other');
    }

    public function testToHeaderIsAddedIfNoneSet()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addMailboxHeader')
                ->once()
                ->with('To', (array) 'to@domain');
        $headers->shouldReceive('addMailboxHeader')
                ->zeroOrMoreTimes();

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $message->setTo('to@domain');
    }

    public function testNameCanBeUsedInToHeader()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addMailboxHeader')
                ->once()
                ->with('To', array('to@domain' => 'Name'));
        $headers->shouldReceive('addMailboxHeader')
                ->zeroOrMoreTimes();

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $message->setTo('to@domain', 'Name');
    }

    public function testCcIsReturnedFromHeader()
    {
        /* -- RFC 2822, 3.6.3.
     */

        $cc = $this->_createHeader('Cc', array('cc@domain' => 'Name'));
        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Cc' => $cc)),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals(array('cc@domain' => 'Name'), $message->getCc());
    }

    public function testCcIsSetInHeader()
    {
        $cc = $this->_createHeader('Cc', array('cc@domain' => 'Name'),
            array(), false
            );
        $cc->shouldReceive('setFieldBodyModel')
           ->once()
           ->with(array('other@domain' => 'Other'));

        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Cc' => $cc)),
            $this->_createEncoder(), $this->_createCache()
            );
        $message->setCc(array('other@domain' => 'Other'));
    }

    public function testCcIsAddedToHeadersDuringAddCc()
    {
        $cc = $this->_createHeader('Cc', array('from@domain' => 'Name'),
            array(), false
            );
        $cc->shouldReceive('setFieldBodyModel')
           ->once()
           ->with(array('from@domain' => 'Name', 'other@domain' => 'Other'));

        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Cc' => $cc)),
            $this->_createEncoder(), $this->_createCache()
            );
        $message->addCc('other@domain', 'Other');
    }

    public function testCcHeaderIsAddedIfNoneSet()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addMailboxHeader')
                ->once()
                ->with('Cc', (array) 'cc@domain');
        $headers->shouldReceive('addMailboxHeader')
                ->zeroOrMoreTimes();

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $message->setCc('cc@domain');
    }

    public function testNameCanBeUsedInCcHeader()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addMailboxHeader')
                ->once()
                ->with('Cc', array('cc@domain' => 'Name'));
        $headers->shouldReceive('addMailboxHeader')
                ->zeroOrMoreTimes();

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $message->setCc('cc@domain', 'Name');
    }

    public function testBccIsReturnedFromHeader()
    {
        /* -- RFC 2822, 3.6.3.
     */

        $bcc = $this->_createHeader('Bcc', array('bcc@domain' => 'Name'));
        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Bcc' => $bcc)),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals(array('bcc@domain' => 'Name'), $message->getBcc());
    }

    public function testBccIsSetInHeader()
    {
        $bcc = $this->_createHeader('Bcc', array('bcc@domain' => 'Name'),
            array(), false
            );
        $bcc->shouldReceive('setFieldBodyModel')
            ->once()
            ->with(array('other@domain' => 'Other'));

        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Bcc' => $bcc)),
            $this->_createEncoder(), $this->_createCache()
            );
        $message->setBcc(array('other@domain' => 'Other'));
    }

    public function testBccIsAddedToHeadersDuringAddBcc()
    {
        $bcc = $this->_createHeader('Bcc', array('from@domain' => 'Name'),
            array(), false
            );
        $bcc->shouldReceive('setFieldBodyModel')
            ->once()
            ->with(array('from@domain' => 'Name', 'other@domain' => 'Other'));

        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Bcc' => $bcc)),
            $this->_createEncoder(), $this->_createCache()
            );
        $message->addBcc('other@domain', 'Other');
    }

    public function testBccHeaderIsAddedIfNoneSet()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addMailboxHeader')
                ->once()
                ->with('Bcc', (array) 'bcc@domain');
        $headers->shouldReceive('addMailboxHeader')
                ->zeroOrMoreTimes();

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $message->setBcc('bcc@domain');
    }

    public function testNameCanBeUsedInBcc()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addMailboxHeader')
                ->once()
                ->with('Bcc', array('bcc@domain' => 'Name'));
        $headers->shouldReceive('addMailboxHeader')
                ->zeroOrMoreTimes();

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $message->setBcc('bcc@domain', 'Name');
    }

    public function testPriorityIsReadFromHeader()
    {
        $prio = $this->_createHeader('X-Priority', '2 (High)');
        $message = $this->_createMessage(
            $this->_createHeaderSet(array('X-Priority' => $prio)),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals(2, $message->getPriority());
    }

    public function testPriorityIsSetInHeader()
    {
        $prio = $this->_createHeader('X-Priority', '2 (High)', array(), false);
        $prio->shouldReceive('setFieldBodyModel')
             ->once()
             ->with('5 (Lowest)');

        $message = $this->_createMessage(
            $this->_createHeaderSet(array('X-Priority' => $prio)),
            $this->_createEncoder(), $this->_createCache()
            );
        $message->setPriority($message::PRIORITY_LOWEST);
    }

    public function testPriorityHeaderIsAddedIfNoneSet()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addTextHeader')
                ->once()
                ->with('X-Priority', '4 (Low)');
        $headers->shouldReceive('addTextHeader')
                ->zeroOrMoreTimes();

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $message->setPriority($message::PRIORITY_LOW);
    }

    public function testReadReceiptAddressReadFromHeader()
    {
        $rcpt = $this->_createHeader('Disposition-Notification-To',
            array('chris@swiftmailer.org' => 'Chris')
            );
        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Disposition-Notification-To' => $rcpt)),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertEquals(array('chris@swiftmailer.org' => 'Chris'),
            $message->getReadReceiptTo()
            );
    }

    public function testReadReceiptIsSetInHeader()
    {
        $rcpt = $this->_createHeader('Disposition-Notification-To', array(), array(), false);
        $rcpt->shouldReceive('setFieldBodyModel')
             ->once()
             ->with('mark@swiftmailer.org');

        $message = $this->_createMessage(
            $this->_createHeaderSet(array('Disposition-Notification-To' => $rcpt)),
            $this->_createEncoder(), $this->_createCache()
            );
        $message->setReadReceiptTo('mark@swiftmailer.org');
    }

    public function testReadReceiptHeaderIsAddedIfNoneSet()
    {
        $headers = $this->_createHeaderSet(array(), false);
        $headers->shouldReceive('addMailboxHeader')
                ->once()
                ->with('Disposition-Notification-To', 'mark@swiftmailer.org');
        $headers->shouldReceive('addMailboxHeader')
                ->zeroOrMoreTimes();

        $message = $this->_createMessage($headers, $this->_createEncoder(),
            $this->_createCache()
            );
        $message->setReadReceiptTo('mark@swiftmailer.org');
    }

    public function testChildrenCanBeAttached()
    {
        $child1 = $this->_createChild();
        $child2 = $this->_createChild();

        $message = $this->_createMessage($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );

        $message->attach($child1);
        $message->attach($child2);

        $this->assertEquals(array($child1, $child2), $message->getChildren());
    }

    public function testChildrenCanBeDetached()
    {
        $child1 = $this->_createChild();
        $child2 = $this->_createChild();

        $message = $this->_createMessage($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );

        $message->attach($child1);
        $message->attach($child2);

        $message->detach($child1);

        $this->assertEquals(array($child2), $message->getChildren());
    }

    public function testEmbedAttachesChild()
    {
        $child = $this->_createChild();

        $message = $this->_createMessage($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );

        $message->embed($child);

        $this->assertEquals(array($child), $message->getChildren());
    }

    public function testEmbedReturnsValidCid()
    {
        $child = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_RELATED, '',
            false
            );
        $child->shouldReceive('getId')
              ->zeroOrMoreTimes()
              ->andReturn('foo@bar');

        $message = $this->_createMessage($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );

        $this->assertEquals('cid:foo@bar', $message->embed($child));
    }

    public function testFluidInterface()
    {
        $child = $this->_createChild();
        $message = $this->_createMessage($this->_createHeaderSet(),
            $this->_createEncoder(), $this->_createCache()
            );
        $this->assertSame($message,
            $message
            ->setContentType('text/plain')
            ->setEncoder($this->_createEncoder())
            ->setId('foo@bar')
            ->setDescription('my description')
            ->setMaxLineLength(998)
            ->setBody('xx')
            ->setBoundary('xyz')
            ->setChildren(array())
            ->setCharset('iso-8859-1')
            ->setFormat('flowed')
            ->setDelSp(false)
            ->setSubject('subj')
            ->setDate(123)
            ->setReturnPath('foo@bar')
            ->setSender('foo@bar')
            ->setFrom(array('x@y' => 'XY'))
            ->setReplyTo(array('ab@cd' => 'ABCD'))
            ->setTo(array('chris@site.tld', 'mark@site.tld'))
            ->setCc('john@somewhere.tld')
            ->setBcc(array('one@site', 'two@site' => 'Two'))
            ->setPriority($message::PRIORITY_LOW)
            ->setReadReceiptTo('a@b')
            ->attach($child)
            ->detach($child)
            );
    }

    // -- Private helpers

    //abstract
    protected function _createEntity($headers, $encoder, $cache)
    {
        return $this->_createMessage($headers, $encoder, $cache);
    }

    protected function _createMimePart($headers, $encoder, $cache)
    {
        return $this->_createMessage($headers, $encoder, $cache);
    }

    private function _createMessage($headers, $encoder, $cache)
    {
        return new Swift_Mime_SimpleMessage($headers, $encoder, $cache, new Swift_Mime_Grammar());
    }
}
<?php

class Swift_Mime_SimpleMimeEntityTest extends Swift_Mime_AbstractMimeEntityTest
{
    // -- Private helpers

    protected function _createEntity($headerFactory, $encoder, $cache)
    {
        return new Swift_Mime_SimpleMimeEntity($headerFactory, $encoder, $cache, new Swift_Mime_Grammar());
    }
}
<?php

class Swift_Plugins_AntiFloodPluginTest extends \PHPUnit_Framework_TestCase
{
    public function testThresholdCanBeSetAndFetched()
    {
        $plugin = new Swift_Plugins_AntiFloodPlugin(10);
        $this->assertEquals(10, $plugin->getThreshold());
        $plugin->setThreshold(100);
        $this->assertEquals(100, $plugin->getThreshold());
    }

    public function testSleepTimeCanBeSetAndFetched()
    {
        $plugin = new Swift_Plugins_AntiFloodPlugin(10, 5);
        $this->assertEquals(5, $plugin->getSleepTime());
        $plugin->setSleepTime(1);
        $this->assertEquals(1, $plugin->getSleepTime());
    }

    public function testPluginStopsConnectionAfterThreshold()
    {
        $transport = $this->_createTransport();
        $transport->expects($this->once())
                  ->method('start');
        $transport->expects($this->once())
                  ->method('stop');

        $evt = $this->_createSendEvent($transport);

        $plugin = new Swift_Plugins_AntiFloodPlugin(10);
        for ($i = 0; $i < 12; ++$i) {
            $plugin->sendPerformed($evt);
        }
    }

    public function testPluginCanStopAndStartMultipleTimes()
    {
        $transport = $this->_createTransport();
        $transport->expects($this->exactly(5))
                  ->method('start');
        $transport->expects($this->exactly(5))
                  ->method('stop');

        $evt = $this->_createSendEvent($transport);

        $plugin = new Swift_Plugins_AntiFloodPlugin(2);
        for ($i = 0; $i < 11; ++$i) {
            $plugin->sendPerformed($evt);
        }
    }

    public function testPluginCanSleepDuringRestart()
    {
        $sleeper = $this->getMockBuilder('Swift_Plugins_Sleeper')->getMock();
        $sleeper->expects($this->once())
                ->method('sleep')
                ->with(10);

        $transport = $this->_createTransport();
        $transport->expects($this->once())
                  ->method('start');
        $transport->expects($this->once())
                  ->method('stop');

        $evt = $this->_createSendEvent($transport);

        $plugin = new Swift_Plugins_AntiFloodPlugin(99, 10, $sleeper);
        for ($i = 0; $i < 101; ++$i) {
            $plugin->sendPerformed($evt);
        }
    }

    // -- Creation Methods

    private function _createTransport()
    {
        return $this->getMockBuilder('Swift_Transport')->getMock();
    }

    private function _createSendEvent($transport)
    {
        $evt = $this->getMockBuilder('Swift_Events_SendEvent')
                    ->disableOriginalConstructor()
                    ->getMock();
        $evt->expects($this->any())
            ->method('getSource')
            ->will($this->returnValue($transport));
        $evt->expects($this->any())
            ->method('getTransport')
            ->will($this->returnValue($transport));

        return $evt;
    }
}
<?php

class Swift_Plugins_BandwidthMonitorPluginTest extends \PHPUnit_Framework_TestCase
{
    private $_monitor;

    private $_bytes = 0;

    public function setUp()
    {
        $this->_monitor = new Swift_Plugins_BandwidthMonitorPlugin();
    }

    public function testBytesOutIncreasesWhenCommandsSent()
    {
        $evt = $this->_createCommandEvent("RCPT TO:<foo@bar.com>\r\n");

        $this->assertEquals(0, $this->_monitor->getBytesOut());
        $this->_monitor->commandSent($evt);
        $this->assertEquals(23, $this->_monitor->getBytesOut());
        $this->_monitor->commandSent($evt);
        $this->assertEquals(46, $this->_monitor->getBytesOut());
    }

    public function testBytesInIncreasesWhenResponsesReceived()
    {
        $evt = $this->_createResponseEvent("250 Ok\r\n");

        $this->assertEquals(0, $this->_monitor->getBytesIn());
        $this->_monitor->responseReceived($evt);
        $this->assertEquals(8, $this->_monitor->getBytesIn());
        $this->_monitor->responseReceived($evt);
        $this->assertEquals(16, $this->_monitor->getBytesIn());
    }

    public function testCountersCanBeReset()
    {
        $evt = $this->_createResponseEvent("250 Ok\r\n");

        $this->assertEquals(0, $this->_monitor->getBytesIn());
        $this->_monitor->responseReceived($evt);
        $this->assertEquals(8, $this->_monitor->getBytesIn());
        $this->_monitor->responseReceived($evt);
        $this->assertEquals(16, $this->_monitor->getBytesIn());

        $evt = $this->_createCommandEvent("RCPT TO:<foo@bar.com>\r\n");

        $this->assertEquals(0, $this->_monitor->getBytesOut());
        $this->_monitor->commandSent($evt);
        $this->assertEquals(23, $this->_monitor->getBytesOut());
        $this->_monitor->commandSent($evt);
        $this->assertEquals(46, $this->_monitor->getBytesOut());

        $this->_monitor->reset();

        $this->assertEquals(0, $this->_monitor->getBytesOut());
        $this->assertEquals(0, $this->_monitor->getBytesIn());
    }

    public function testBytesOutIncreasesAccordingToMessageLength()
    {
        $message = $this->_createMessageWithByteCount(6);
        $evt = $this->_createSendEvent($message);

        $this->assertEquals(0, $this->_monitor->getBytesOut());
        $this->_monitor->sendPerformed($evt);
        $this->assertEquals(6, $this->_monitor->getBytesOut());
        $this->_monitor->sendPerformed($evt);
        $this->assertEquals(12, $this->_monitor->getBytesOut());
    }

    // -- Creation Methods

    private function _createSendEvent($message)
    {
        $evt = $this->getMockBuilder('Swift_Events_SendEvent')
                    ->disableOriginalConstructor()
                    ->getMock();
        $evt->expects($this->any())
            ->method('getMessage')
            ->will($this->returnValue($message));

        return $evt;
    }

    private function _createCommandEvent($command)
    {
        $evt = $this->getMockBuilder('Swift_Events_CommandEvent')
                    ->disableOriginalConstructor()
                    ->getMock();
        $evt->expects($this->any())
            ->method('getCommand')
            ->will($this->returnValue($command));

        return $evt;
    }

    private function _createResponseEvent($response)
    {
        $evt = $this->getMockBuilder('Swift_Events_ResponseEvent')
                    ->disableOriginalConstructor()
                    ->getMock();
        $evt->expects($this->any())
            ->method('getResponse')
            ->will($this->returnValue($response));

        return $evt;
    }

    private function _createMessageWithByteCount($bytes)
    {
        $this->_bytes = $bytes;
        $msg = $this->getMockBuilder('Swift_Mime_Message')->getMock();
        $msg->expects($this->any())
            ->method('toByteStream')
            ->will($this->returnCallback(array($this, '_write')));
      /*  $this->_checking(Expectations::create()
            -> ignoring($msg)->toByteStream(any()) -> calls(array($this, '_write'))
        ); */

        return $msg;
    }

    public function _write($is)
    {
        for ($i = 0; $i < $this->_bytes; ++$i) {
            $is->write('x');
        }
    }
}
<?php

class Swift_Plugins_DecoratorPluginTest extends \SwiftMailerTestCase
{
    public function testMessageBodyReceivesReplacements()
    {
        $message = $this->_createMessage(
            $this->_createHeaders(),
            array('zip@button.tld' => 'Zipathon'),
            array('chris.corbyn@swiftmailer.org' => 'Chris'),
            'Subject',
            'Hello {name}, you are customer #{id}'
            );
        $message->shouldReceive('setBody')
                ->once()
                ->with('Hello Zip, you are customer #456');
        $message->shouldReceive('setBody')
                ->zeroOrMoreTimes();

        $plugin = $this->_createPlugin(
            array('zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456'))
            );

        $evt = $this->_createSendEvent($message);

        $plugin->beforeSendPerformed($evt);
        $plugin->sendPerformed($evt);
    }

    public function testReplacementsCanBeAppliedToSameMessageMultipleTimes()
    {
        $message = $this->_createMessage(
            $this->_createHeaders(),
            array('zip@button.tld' => 'Zipathon', 'foo@bar.tld' => 'Foo'),
            array('chris.corbyn@swiftmailer.org' => 'Chris'),
            'Subject',
            'Hello {name}, you are customer #{id}'
            );
        $message->shouldReceive('setBody')
                ->once()
                ->with('Hello Zip, you are customer #456');
        $message->shouldReceive('setBody')
                ->once()
                ->with('Hello {name}, you are customer #{id}');
        $message->shouldReceive('setBody')
                ->once()
                ->with('Hello Foo, you are customer #123');
        $message->shouldReceive('setBody')
                ->zeroOrMoreTimes();

        $plugin = $this->_createPlugin(
            array(
                'foo@bar.tld' => array('{name}' => 'Foo', '{id}' => '123'),
                'zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456'),
                )
            );

        $evt = $this->_createSendEvent($message);

        $plugin->beforeSendPerformed($evt);
        $plugin->sendPerformed($evt);
        $plugin->beforeSendPerformed($evt);
        $plugin->sendPerformed($evt);
    }

    public function testReplacementsCanBeMadeInHeaders()
    {
        $headers = $this->_createHeaders(array(
            $returnPathHeader = $this->_createHeader('Return-Path', 'foo-{id}@swiftmailer.org'),
            $toHeader = $this->_createHeader('Subject', 'A message for {name}!'),
        ));

        $message = $this->_createMessage(
            $headers,
            array('zip@button.tld' => 'Zipathon'),
            array('chris.corbyn@swiftmailer.org' => 'Chris'),
            'A message for {name}!',
            'Hello {name}, you are customer #{id}'
            );

        $message->shouldReceive('setBody')
                ->once()
                ->with('Hello Zip, you are customer #456');
        $toHeader->shouldReceive('setFieldBodyModel')
                 ->once()
                 ->with('A message for Zip!');
        $returnPathHeader->shouldReceive('setFieldBodyModel')
                         ->once()
                         ->with('foo-456@swiftmailer.org');
        $message->shouldReceive('setBody')
                ->zeroOrMoreTimes();
        $toHeader->shouldReceive('setFieldBodyModel')
                 ->zeroOrMoreTimes();
        $returnPathHeader->shouldReceive('setFieldBodyModel')
                         ->zeroOrMoreTimes();

        $plugin = $this->_createPlugin(
            array('zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456'))
            );
        $evt = $this->_createSendEvent($message);

        $plugin->beforeSendPerformed($evt);
        $plugin->sendPerformed($evt);
    }

    public function testReplacementsAreMadeOnSubparts()
    {
        $part1 = $this->_createPart('text/plain', 'Your name is {name}?', '1@x');
        $part2 = $this->_createPart('text/html', 'Your <em>name</em> is {name}?', '2@x');
        $message = $this->_createMessage(
            $this->_createHeaders(),
            array('zip@button.tld' => 'Zipathon'),
            array('chris.corbyn@swiftmailer.org' => 'Chris'),
            'A message for {name}!',
            'Subject'
            );
        $message->shouldReceive('getChildren')
                ->zeroOrMoreTimes()
                ->andReturn(array($part1, $part2));
        $part1->shouldReceive('setBody')
              ->once()
              ->with('Your name is Zip?');
        $part2->shouldReceive('setBody')
              ->once()
              ->with('Your <em>name</em> is Zip?');
        $part1->shouldReceive('setBody')
              ->zeroOrMoreTimes();
        $part2->shouldReceive('setBody')
              ->zeroOrMoreTimes();

        $plugin = $this->_createPlugin(
            array('zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456'))
            );

        $evt = $this->_createSendEvent($message);

        $plugin->beforeSendPerformed($evt);
        $plugin->sendPerformed($evt);
    }

    public function testReplacementsCanBeTakenFromCustomReplacementsObject()
    {
        $message = $this->_createMessage(
            $this->_createHeaders(),
            array('foo@bar' => 'Foobar', 'zip@zap' => 'Zip zap'),
            array('chris.corbyn@swiftmailer.org' => 'Chris'),
            'Subject',
            'Something {a}'
            );

        $replacements = $this->_createReplacements();

        $message->shouldReceive('setBody')
                ->once()
                ->with('Something b');
        $message->shouldReceive('setBody')
                ->once()
                ->with('Something c');
        $message->shouldReceive('setBody')
                ->zeroOrMoreTimes();
        $replacements->shouldReceive('getReplacementsFor')
                     ->once()
                     ->with('foo@bar')
                     ->andReturn(array('{a}' => 'b'));
        $replacements->shouldReceive('getReplacementsFor')
                     ->once()
                     ->with('zip@zap')
                     ->andReturn(array('{a}' => 'c'));

        $plugin = $this->_createPlugin($replacements);

        $evt = $this->_createSendEvent($message);

        $plugin->beforeSendPerformed($evt);
        $plugin->sendPerformed($evt);
        $plugin->beforeSendPerformed($evt);
        $plugin->sendPerformed($evt);
    }

    // -- Creation methods

    private function _createMessage($headers, $to = array(), $from = null, $subject = null,
        $body = null)
    {
        $message = $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing();
        foreach ($to as $addr => $name) {
            $message->shouldReceive('getTo')
                    ->once()
                    ->andReturn(array($addr => $name));
        }
        $message->shouldReceive('getHeaders')
                ->zeroOrMoreTimes()
                ->andReturn($headers);
        $message->shouldReceive('getFrom')
                ->zeroOrMoreTimes()
                ->andReturn($from);
        $message->shouldReceive('getSubject')
                ->zeroOrMoreTimes()
                ->andReturn($subject);
        $message->shouldReceive('getBody')
                ->zeroOrMoreTimes()
                ->andReturn($body);

        return $message;
    }

    private function _createPlugin($replacements)
    {
        return new Swift_Plugins_DecoratorPlugin($replacements);
    }

    private function _createReplacements()
    {
        return $this->getMockery('Swift_Plugins_Decorator_Replacements')->shouldIgnoreMissing();
    }

    private function _createSendEvent(Swift_Mime_Message $message)
    {
        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
        $evt->shouldReceive('getMessage')
            ->zeroOrMoreTimes()
            ->andReturn($message);

        return $evt;
    }

    private function _createPart($type, $body, $id)
    {
        $part = $this->getMockery('Swift_Mime_MimeEntity')->shouldIgnoreMissing();
        $part->shouldReceive('getContentType')
             ->zeroOrMoreTimes()
             ->andReturn($type);
        $part->shouldReceive('getBody')
             ->zeroOrMoreTimes()
             ->andReturn($body);
        $part->shouldReceive('getId')
             ->zeroOrMoreTimes()
             ->andReturn($id);

        return $part;
    }

    private function _createHeaders($headers = array())
    {
        $set = $this->getMockery('Swift_Mime_HeaderSet')->shouldIgnoreMissing();
        $set->shouldReceive('getAll')
            ->zeroOrMoreTimes()
            ->andReturn($headers);

        foreach ($headers as $header) {
            $set->set($header);
        }

        return $set;
    }

    private function _createHeader($name, $body = '')
    {
        $header = $this->getMockery('Swift_Mime_Header')->shouldIgnoreMissing();
        $header->shouldReceive('getFieldName')
               ->zeroOrMoreTimes()
               ->andReturn($name);
        $header->shouldReceive('getFieldBodyModel')
               ->zeroOrMoreTimes()
               ->andReturn($body);

        return $header;
    }
}
<?php

class Swift_Plugins_LoggerPluginTest extends \SwiftMailerTestCase
{
    public function testLoggerDelegatesAddingEntries()
    {
        $logger = $this->_createLogger();
        $logger->expects($this->once())
               ->method('add')
               ->with('foo');

        $plugin = $this->_createPlugin($logger);
        $plugin->add('foo');
    }

    public function testLoggerDelegatesDumpingEntries()
    {
        $logger = $this->_createLogger();
        $logger->expects($this->once())
               ->method('dump')
               ->will($this->returnValue('foobar'));

        $plugin = $this->_createPlugin($logger);
        $this->assertEquals('foobar', $plugin->dump());
    }

    public function testLoggerDelegatesClearingEntries()
    {
        $logger = $this->_createLogger();
        $logger->expects($this->once())
               ->method('clear');

        $plugin = $this->_createPlugin($logger);
        $plugin->clear();
    }

    public function testCommandIsSentToLogger()
    {
        $evt = $this->_createCommandEvent("foo\r\n");
        $logger = $this->_createLogger();
        $logger->expects($this->once())
               ->method('add')
               ->with($this->regExp('~foo\r\n~'));

        $plugin = $this->_createPlugin($logger);
        $plugin->commandSent($evt);
    }

    public function testResponseIsSentToLogger()
    {
        $evt = $this->_createResponseEvent("354 Go ahead\r\n");
        $logger = $this->_createLogger();
        $logger->expects($this->once())
               ->method('add')
               ->with($this->regExp('~354 Go ahead\r\n~'));

        $plugin = $this->_createPlugin($logger);
        $plugin->responseReceived($evt);
    }

    public function testTransportBeforeStartChangeIsSentToLogger()
    {
        $evt = $this->_createTransportChangeEvent();
        $logger = $this->_createLogger();
        $logger->expects($this->once())
               ->method('add')
               ->with($this->anything());

        $plugin = $this->_createPlugin($logger);
        $plugin->beforeTransportStarted($evt);
    }

    public function testTransportStartChangeIsSentToLogger()
    {
        $evt = $this->_createTransportChangeEvent();
        $logger = $this->_createLogger();
        $logger->expects($this->once())
               ->method('add')
               ->with($this->anything());

        $plugin = $this->_createPlugin($logger);
        $plugin->transportStarted($evt);
    }

    public function testTransportStopChangeIsSentToLogger()
    {
        $evt = $this->_createTransportChangeEvent();
        $logger = $this->_createLogger();
        $logger->expects($this->once())
               ->method('add')
               ->with($this->anything());

        $plugin = $this->_createPlugin($logger);
        $plugin->transportStopped($evt);
    }

    public function testTransportBeforeStopChangeIsSentToLogger()
    {
        $evt = $this->_createTransportChangeEvent();
        $logger = $this->_createLogger();
        $logger->expects($this->once())
               ->method('add')
               ->with($this->anything());

        $plugin = $this->_createPlugin($logger);
        $plugin->beforeTransportStopped($evt);
    }

    public function testExceptionsArePassedToDelegateAndLeftToBubbleUp()
    {
        $transport = $this->_createTransport();
        $evt = $this->_createTransportExceptionEvent();
        $logger = $this->_createLogger();
        $logger->expects($this->once())
               ->method('add')
               ->with($this->anything());

        $plugin = $this->_createPlugin($logger);
        try {
            $plugin->exceptionThrown($evt);
            $this->fail('Exception should bubble up.');
        } catch (Swift_TransportException $ex) {
        }
    }

    // -- Creation Methods

    private function _createLogger()
    {
        return $this->getMockBuilder('Swift_Plugins_Logger')->getMock();
    }

    private function _createPlugin($logger)
    {
        return new Swift_Plugins_LoggerPlugin($logger);
    }

    private function _createCommandEvent($command)
    {
        $evt = $this->getMockBuilder('Swift_Events_CommandEvent')
                    ->disableOriginalConstructor()
                    ->getMock();
        $evt->expects($this->any())
            ->method('getCommand')
            ->will($this->returnValue($command));

        return $evt;
    }

    private function _createResponseEvent($response)
    {
        $evt = $this->getMockBuilder('Swift_Events_ResponseEvent')
                    ->disableOriginalConstructor()
                    ->getMock();
        $evt->expects($this->any())
            ->method('getResponse')
            ->will($this->returnValue($response));

        return $evt;
    }

    private function _createTransport()
    {
        return $this->getMockBuilder('Swift_Transport')->getMock();
    }

    private function _createTransportChangeEvent()
    {
        $evt = $this->getMockBuilder('Swift_Events_TransportChangeEvent')
                    ->disableOriginalConstructor()
                    ->getMock();
        $evt->expects($this->any())
            ->method('getSource')
            ->will($this->returnValue($this->_createTransport()));

        return $evt;
    }

    public function _createTransportExceptionEvent()
    {
        $evt = $this->getMockBuilder('Swift_Events_TransportExceptionEvent')
                    ->disableOriginalConstructor()
                    ->getMock();
        $evt->expects($this->any())
            ->method('getException')
            ->will($this->returnValue(new Swift_TransportException('')));

        return $evt;
    }
}
<?php

class Swift_Plugins_Loggers_ArrayLoggerTest extends \PHPUnit_Framework_TestCase
{
    public function testAddingSingleEntryDumpsSingleLine()
    {
        $logger = new Swift_Plugins_Loggers_ArrayLogger();
        $logger->add(">> Foo\r\n");
        $this->assertEquals(">> Foo\r\n", $logger->dump());
    }

    public function testAddingMultipleEntriesDumpsMultipleLines()
    {
        $logger = new Swift_Plugins_Loggers_ArrayLogger();
        $logger->add(">> FOO\r\n");
        $logger->add("<< 502 That makes no sense\r\n");
        $logger->add(">> RSET\r\n");
        $logger->add("<< 250 OK\r\n");

        $this->assertEquals(
            ">> FOO\r\n".PHP_EOL.
            "<< 502 That makes no sense\r\n".PHP_EOL.
            ">> RSET\r\n".PHP_EOL.
            "<< 250 OK\r\n",
            $logger->dump()
            );
    }

    public function testLogCanBeCleared()
    {
        $logger = new Swift_Plugins_Loggers_ArrayLogger();
        $logger->add(">> FOO\r\n");
        $logger->add("<< 502 That makes no sense\r\n");
        $logger->add(">> RSET\r\n");
        $logger->add("<< 250 OK\r\n");

        $this->assertEquals(
            ">> FOO\r\n".PHP_EOL.
            "<< 502 That makes no sense\r\n".PHP_EOL.
            ">> RSET\r\n".PHP_EOL.
            "<< 250 OK\r\n",
            $logger->dump()
            );

        $logger->clear();

        $this->assertEquals('', $logger->dump());
    }

    public function testLengthCanBeTruncated()
    {
        $logger = new Swift_Plugins_Loggers_ArrayLogger(2);
        $logger->add(">> FOO\r\n");
        $logger->add("<< 502 That makes no sense\r\n");
        $logger->add(">> RSET\r\n");
        $logger->add("<< 250 OK\r\n");

        $this->assertEquals(
            ">> RSET\r\n".PHP_EOL.
            "<< 250 OK\r\n",
            $logger->dump(),
            '%s: Log should be truncated to last 2 entries'
            );
    }
}
<?php

class Swift_Plugins_Loggers_EchoLoggerTest extends \PHPUnit_Framework_TestCase
{
    public function testAddingEntryDumpsSingleLineWithoutHtml()
    {
        $logger = new Swift_Plugins_Loggers_EchoLogger(false);
        ob_start();
        $logger->add('>> Foo');
        $data = ob_get_clean();

        $this->assertEquals('>> Foo'.PHP_EOL, $data);
    }

    public function testAddingEntryDumpsEscapedLineWithHtml()
    {
        $logger = new Swift_Plugins_Loggers_EchoLogger(true);
        ob_start();
        $logger->add('>> Foo');
        $data = ob_get_clean();

        $this->assertEquals('&gt;&gt; Foo<br />'.PHP_EOL, $data);
    }
}
<?php

class Swift_Plugins_PopBeforeSmtpPluginTest extends \PHPUnit_Framework_TestCase
{
    public function testPluginConnectsToPop3HostBeforeTransportStarts()
    {
        $connection = $this->_createConnection();
        $connection->expects($this->once())
                   ->method('connect');

        $plugin = $this->_createPlugin('pop.host.tld', 110);
        $plugin->setConnection($connection);

        $transport = $this->_createTransport();
        $evt = $this->_createTransportChangeEvent($transport);

        $plugin->beforeTransportStarted($evt);
    }

    public function testPluginDisconnectsFromPop3HostBeforeTransportStarts()
    {
        $connection = $this->_createConnection();
        $connection->expects($this->once())
                   ->method('disconnect');

        $plugin = $this->_createPlugin('pop.host.tld', 110);
        $plugin->setConnection($connection);

        $transport = $this->_createTransport();
        $evt = $this->_createTransportChangeEvent($transport);

        $plugin->beforeTransportStarted($evt);
    }

    public function testPluginDoesNotConnectToSmtpIfBoundToDifferentTransport()
    {
        $connection = $this->_createConnection();
        $connection->expects($this->never())
                   ->method('disconnect');
        $connection->expects($this->never())
                   ->method('connect');

        $smtp = $this->_createTransport();

        $plugin = $this->_createPlugin('pop.host.tld', 110);
        $plugin->setConnection($connection);
        $plugin->bindSmtp($smtp);

        $transport = $this->_createTransport();
        $evt = $this->_createTransportChangeEvent($transport);

        $plugin->beforeTransportStarted($evt);
    }

    public function testPluginCanBindToSpecificTransport()
    {
        $connection = $this->_createConnection();
        $connection->expects($this->once())
                   ->method('connect');

        $smtp = $this->_createTransport();

        $plugin = $this->_createPlugin('pop.host.tld', 110);
        $plugin->setConnection($connection);
        $plugin->bindSmtp($smtp);

        $evt = $this->_createTransportChangeEvent($smtp);

        $plugin->beforeTransportStarted($evt);
    }

    // -- Creation Methods

    private function _createTransport()
    {
        return $this->getMockBuilder('Swift_Transport')->getMock();
    }

    private function _createTransportChangeEvent($transport)
    {
        $evt = $this->getMockBuilder('Swift_Events_TransportChangeEvent')
                    ->disableOriginalConstructor()
                    ->getMock();
        $evt->expects($this->any())
            ->method('getSource')
            ->will($this->returnValue($transport));
        $evt->expects($this->any())
            ->method('getTransport')
            ->will($this->returnValue($transport));

        return $evt;
    }

    public function _createConnection()
    {
        return $this->getMockBuilder('Swift_Plugins_Pop_Pop3Connection')->getMock();
    }

    public function _createPlugin($host, $port, $crypto = null)
    {
        return new Swift_Plugins_PopBeforeSmtpPlugin($host, $port, $crypto);
    }
}
<?php

class Swift_Plugins_RedirectingPluginTest extends \PHPUnit_Framework_TestCase
{
    public function testRecipientCanBeSetAndFetched()
    {
        $plugin = new Swift_Plugins_RedirectingPlugin('fabien@example.com');
        $this->assertEquals('fabien@example.com', $plugin->getRecipient());
        $plugin->setRecipient('chris@example.com');
        $this->assertEquals('chris@example.com', $plugin->getRecipient());
    }

    public function testPluginChangesRecipients()
    {
        $message = Swift_Message::newInstance()
            ->setSubject('...')
            ->setFrom(array('john@example.com' => 'John Doe'))
            ->setTo($to = array(
                'fabien-to@example.com' => 'Fabien (To)',
                'chris-to@example.com' => 'Chris (To)',
            ))
            ->setCc($cc = array(
                'fabien-cc@example.com' => 'Fabien (Cc)',
                'chris-cc@example.com' => 'Chris (Cc)',
            ))
            ->setBcc($bcc = array(
                'fabien-bcc@example.com' => 'Fabien (Bcc)',
                'chris-bcc@example.com' => 'Chris (Bcc)',
            ))
            ->setBody('...')
        ;

        $plugin = new Swift_Plugins_RedirectingPlugin('god@example.com');

        $evt = $this->_createSendEvent($message);

        $plugin->beforeSendPerformed($evt);

        $this->assertEquals($message->getTo(), array('god@example.com' => ''));
        $this->assertEquals($message->getCc(), array());
        $this->assertEquals($message->getBcc(), array());

        $plugin->sendPerformed($evt);

        $this->assertEquals($message->getTo(), $to);
        $this->assertEquals($message->getCc(), $cc);
        $this->assertEquals($message->getBcc(), $bcc);
    }

    public function testPluginRespectsUnsetToList()
    {
        $message = Swift_Message::newInstance()
            ->setSubject('...')
            ->setFrom(array('john@example.com' => 'John Doe'))
            ->setCc($cc = array(
                'fabien-cc@example.com' => 'Fabien (Cc)',
                'chris-cc@example.com' => 'Chris (Cc)',
            ))
            ->setBcc($bcc = array(
                'fabien-bcc@example.com' => 'Fabien (Bcc)',
                'chris-bcc@example.com' => 'Chris (Bcc)',
            ))
            ->setBody('...')
        ;

        $plugin = new Swift_Plugins_RedirectingPlugin('god@example.com');

        $evt = $this->_createSendEvent($message);

        $plugin->beforeSendPerformed($evt);

        $this->assertEquals($message->getTo(), array('god@example.com' => ''));
        $this->assertEquals($message->getCc(), array());
        $this->assertEquals($message->getBcc(), array());

        $plugin->sendPerformed($evt);

        $this->assertEquals($message->getTo(), array());
        $this->assertEquals($message->getCc(), $cc);
        $this->assertEquals($message->getBcc(), $bcc);
    }

    public function testPluginRespectsAWhitelistOfPatterns()
    {
        $message = Swift_Message::newInstance()
            ->setSubject('...')
            ->setFrom(array('john@example.com' => 'John Doe'))
            ->setTo($to = array(
                'fabien-to@example.com' => 'Fabien (To)',
                'chris-to@example.com' => 'Chris (To)',
                'lars-to@internal.com' => 'Lars (To)',
            ))
            ->setCc($cc = array(
                'fabien-cc@example.com' => 'Fabien (Cc)',
                'chris-cc@example.com' => 'Chris (Cc)',
                'lars-cc@internal.org' => 'Lars (Cc)',
            ))
            ->setBcc($bcc = array(
                'fabien-bcc@example.com' => 'Fabien (Bcc)',
                'chris-bcc@example.com' => 'Chris (Bcc)',
                'john-bcc@example.org' => 'John (Bcc)',
            ))
            ->setBody('...')
        ;

        $recipient = 'god@example.com';
        $patterns = array('/^.*@internal.[a-z]+$/', '/^john-.*$/');

        $plugin = new Swift_Plugins_RedirectingPlugin($recipient, $patterns);

        $this->assertEquals($recipient, $plugin->getRecipient());
        $this->assertEquals($plugin->getWhitelist(), $patterns);

        $evt = $this->_createSendEvent($message);

        $plugin->beforeSendPerformed($evt);

        $this->assertEquals($message->getTo(), array('lars-to@internal.com' => 'Lars (To)', 'god@example.com' => null));
        $this->assertEquals($message->getCc(), array('lars-cc@internal.org' => 'Lars (Cc)'));
        $this->assertEquals($message->getBcc(), array('john-bcc@example.org' => 'John (Bcc)'));

        $plugin->sendPerformed($evt);

        $this->assertEquals($message->getTo(), $to);
        $this->assertEquals($message->getCc(), $cc);
        $this->assertEquals($message->getBcc(), $bcc);
    }

    public function testArrayOfRecipientsCanBeExplicitlyDefined()
    {
        $message = Swift_Message::newInstance()
            ->setSubject('...')
            ->setFrom(array('john@example.com' => 'John Doe'))
            ->setTo(array(
            'fabien@example.com' => 'Fabien',
            'chris@example.com' => 'Chris (To)',
            'lars-to@internal.com' => 'Lars (To)',
        ))
            ->setCc(array(
            'fabien@example.com' => 'Fabien',
            'chris-cc@example.com' => 'Chris (Cc)',
            'lars-cc@internal.org' => 'Lars (Cc)',
        ))
            ->setBcc(array(
            'fabien@example.com' => 'Fabien',
            'chris-bcc@example.com' => 'Chris (Bcc)',
            'john-bcc@example.org' => 'John (Bcc)',
        ))
            ->setBody('...')
        ;

        $recipients = array('god@example.com', 'fabien@example.com');
        $patterns = array('/^.*@internal.[a-z]+$/');

        $plugin = new Swift_Plugins_RedirectingPlugin($recipients, $patterns);

        $evt = $this->_createSendEvent($message);

        $plugin->beforeSendPerformed($evt);

        $this->assertEquals(
            $message->getTo(),
            array('fabien@example.com' => 'Fabien', 'lars-to@internal.com' => 'Lars (To)', 'god@example.com' => null)
        );
        $this->assertEquals(
            $message->getCc(),
            array('fabien@example.com' => 'Fabien', 'lars-cc@internal.org' => 'Lars (Cc)')
        );
        $this->assertEquals($message->getBcc(), array('fabien@example.com' => 'Fabien'));
    }

    // -- Creation Methods

    private function _createSendEvent(Swift_Mime_Message $message)
    {
        $evt = $this->getMockBuilder('Swift_Events_SendEvent')
                    ->disableOriginalConstructor()
                    ->getMock();
        $evt->expects($this->any())
            ->method('getMessage')
            ->will($this->returnValue($message));

        return $evt;
    }
}
<?php

class Swift_Plugins_ReporterPluginTest extends \SwiftMailerTestCase
{
    public function testReportingPasses()
    {
        $message = $this->_createMessage();
        $evt = $this->_createSendEvent();
        $reporter = $this->_createReporter();

        $message->shouldReceive('getTo')->zeroOrMoreTimes()->andReturn(array('foo@bar.tld' => 'Foo'));
        $evt->shouldReceive('getMessage')->zeroOrMoreTimes()->andReturn($message);
        $evt->shouldReceive('getFailedRecipients')->zeroOrMoreTimes()->andReturn(array());
        $reporter->shouldReceive('notify')->once()->with($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS);

        $plugin = new Swift_Plugins_ReporterPlugin($reporter);
        $plugin->sendPerformed($evt);
    }

    public function testReportingFailedTo()
    {
        $message = $this->_createMessage();
        $evt = $this->_createSendEvent();
        $reporter = $this->_createReporter();

        $message->shouldReceive('getTo')->zeroOrMoreTimes()->andReturn(array('foo@bar.tld' => 'Foo', 'zip@button' => 'Zip'));
        $evt->shouldReceive('getMessage')->zeroOrMoreTimes()->andReturn($message);
        $evt->shouldReceive('getFailedRecipients')->zeroOrMoreTimes()->andReturn(array('zip@button'));
        $reporter->shouldReceive('notify')->once()->with($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS);
        $reporter->shouldReceive('notify')->once()->with($message, 'zip@button', Swift_Plugins_Reporter::RESULT_FAIL);

        $plugin = new Swift_Plugins_ReporterPlugin($reporter);
        $plugin->sendPerformed($evt);
    }

    public function testReportingFailedCc()
    {
        $message = $this->_createMessage();
        $evt = $this->_createSendEvent();
        $reporter = $this->_createReporter();

        $message->shouldReceive('getTo')->zeroOrMoreTimes()->andReturn(array('foo@bar.tld' => 'Foo'));
        $message->shouldReceive('getCc')->zeroOrMoreTimes()->andReturn(array('zip@button' => 'Zip', 'test@test.com' => 'Test'));
        $evt->shouldReceive('getMessage')->zeroOrMoreTimes()->andReturn($message);
        $evt->shouldReceive('getFailedRecipients')->zeroOrMoreTimes()->andReturn(array('zip@button'));
        $reporter->shouldReceive('notify')->once()->with($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS);
        $reporter->shouldReceive('notify')->once()->with($message, 'zip@button', Swift_Plugins_Reporter::RESULT_FAIL);
        $reporter->shouldReceive('notify')->once()->with($message, 'test@test.com', Swift_Plugins_Reporter::RESULT_PASS);

        $plugin = new Swift_Plugins_ReporterPlugin($reporter);
        $plugin->sendPerformed($evt);
    }

    public function testReportingFailedBcc()
    {
        $message = $this->_createMessage();
        $evt = $this->_createSendEvent();
        $reporter = $this->_createReporter();

        $message->shouldReceive('getTo')->zeroOrMoreTimes()->andReturn(array('foo@bar.tld' => 'Foo'));
        $message->shouldReceive('getBcc')->zeroOrMoreTimes()->andReturn(array('zip@button' => 'Zip', 'test@test.com' => 'Test'));
        $evt->shouldReceive('getMessage')->zeroOrMoreTimes()->andReturn($message);
        $evt->shouldReceive('getFailedRecipients')->zeroOrMoreTimes()->andReturn(array('zip@button'));
        $reporter->shouldReceive('notify')->once()->with($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS);
        $reporter->shouldReceive('notify')->once()->with($message, 'zip@button', Swift_Plugins_Reporter::RESULT_FAIL);
        $reporter->shouldReceive('notify')->once()->with($message, 'test@test.com', Swift_Plugins_Reporter::RESULT_PASS);

        $plugin = new Swift_Plugins_ReporterPlugin($reporter);
        $plugin->sendPerformed($evt);
    }

    // -- Creation Methods

    private function _createMessage()
    {
        return $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing();
    }

    private function _createSendEvent()
    {
        return $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
    }

    private function _createReporter()
    {
        return $this->getMockery('Swift_Plugins_Reporter')->shouldIgnoreMissing();
    }
}
<?php

class Swift_Plugins_Reporters_HitReporterTest extends \PHPUnit_Framework_TestCase
{
    private $_hitReporter;
    private $_message;

    public function setUp()
    {
        $this->_hitReporter = new Swift_Plugins_Reporters_HitReporter();
        $this->_message = $this->getMockBuilder('Swift_Mime_Message')->getMock();
    }

    public function testReportingFail()
    {
        $this->_hitReporter->notify($this->_message, 'foo@bar.tld',
            Swift_Plugins_Reporter::RESULT_FAIL
            );
        $this->assertEquals(array('foo@bar.tld'),
            $this->_hitReporter->getFailedRecipients()
            );
    }

    public function testMultipleReports()
    {
        $this->_hitReporter->notify($this->_message, 'foo@bar.tld',
            Swift_Plugins_Reporter::RESULT_FAIL
            );
        $this->_hitReporter->notify($this->_message, 'zip@button',
            Swift_Plugins_Reporter::RESULT_FAIL
            );
        $this->assertEquals(array('foo@bar.tld', 'zip@button'),
            $this->_hitReporter->getFailedRecipients()
            );
    }

    public function testReportingPassIsIgnored()
    {
        $this->_hitReporter->notify($this->_message, 'foo@bar.tld',
            Swift_Plugins_Reporter::RESULT_FAIL
            );
        $this->_hitReporter->notify($this->_message, 'zip@button',
            Swift_Plugins_Reporter::RESULT_PASS
            );
        $this->assertEquals(array('foo@bar.tld'),
            $this->_hitReporter->getFailedRecipients()
            );
    }

    public function testBufferCanBeCleared()
    {
        $this->_hitReporter->notify($this->_message, 'foo@bar.tld',
            Swift_Plugins_Reporter::RESULT_FAIL
            );
        $this->_hitReporter->notify($this->_message, 'zip@button',
            Swift_Plugins_Reporter::RESULT_FAIL
            );
        $this->assertEquals(array('foo@bar.tld', 'zip@button'),
            $this->_hitReporter->getFailedRecipients()
            );
        $this->_hitReporter->clear();
        $this->assertEquals(array(), $this->_hitReporter->getFailedRecipients());
    }
}
<?php

class Swift_Plugins_Reporters_HtmlReporterTest extends \PHPUnit_Framework_TestCase
{
    private $_html;
    private $_message;

    public function setUp()
    {
        $this->_html = new Swift_Plugins_Reporters_HtmlReporter();
        $this->_message = $this->getMockBuilder('Swift_Mime_Message')->getMock();
    }

    public function testReportingPass()
    {
        ob_start();
        $this->_html->notify($this->_message, 'foo@bar.tld',
            Swift_Plugins_Reporter::RESULT_PASS
            );
        $html = ob_get_clean();

        $this->assertRegExp('~ok|pass~i', $html, '%s: Reporter should indicate pass');
        $this->assertRegExp('~foo@bar\.tld~', $html, '%s: Reporter should show address');
    }

    public function testReportingFail()
    {
        ob_start();
        $this->_html->notify($this->_message, 'zip@button',
            Swift_Plugins_Reporter::RESULT_FAIL
            );
        $html = ob_get_clean();

        $this->assertRegExp('~fail~i', $html, '%s: Reporter should indicate fail');
        $this->assertRegExp('~zip@button~', $html, '%s: Reporter should show address');
    }

    public function testMultipleReports()
    {
        ob_start();
        $this->_html->notify($this->_message, 'foo@bar.tld',
            Swift_Plugins_Reporter::RESULT_PASS
            );
        $this->_html->notify($this->_message, 'zip@button',
            Swift_Plugins_Reporter::RESULT_FAIL
            );
        $html = ob_get_clean();

        $this->assertRegExp('~ok|pass~i', $html, '%s: Reporter should indicate pass');
        $this->assertRegExp('~foo@bar\.tld~', $html, '%s: Reporter should show address');
        $this->assertRegExp('~fail~i', $html, '%s: Reporter should indicate fail');
        $this->assertRegExp('~zip@button~', $html, '%s: Reporter should show address');
    }
}
<?php

class Swift_Plugins_ThrottlerPluginTest extends \SwiftMailerTestCase
{
    public function testBytesPerMinuteThrottling()
    {
        $sleeper = $this->_createSleeper();
        $timer = $this->_createTimer();

        //10MB/min
        $plugin = new Swift_Plugins_ThrottlerPlugin(
            10000000, Swift_Plugins_ThrottlerPlugin::BYTES_PER_MINUTE,
            $sleeper, $timer
            );

        $timer->shouldReceive('getTimestamp')->once()->andReturn(0);
        $timer->shouldReceive('getTimestamp')->once()->andReturn(1); //expected 0.6
        $timer->shouldReceive('getTimestamp')->once()->andReturn(1); //expected 1.2 (sleep 1)
        $timer->shouldReceive('getTimestamp')->once()->andReturn(2); //expected 1.8
        $timer->shouldReceive('getTimestamp')->once()->andReturn(2); //expected 2.4 (sleep 1)
        $sleeper->shouldReceive('sleep')->twice()->with(1);

        //10,000,000 bytes per minute
        //100,000 bytes per email

        // .: (10,000,000/100,000)/60 emails per second = 1.667 emais/sec

        $message = $this->_createMessageWithByteCount(100000); //100KB

        $evt = $this->_createSendEvent($message);

        for ($i = 0; $i < 5; ++$i) {
            $plugin->beforeSendPerformed($evt);
            $plugin->sendPerformed($evt);
        }
    }

    public function testMessagesPerMinuteThrottling()
    {
        $sleeper = $this->_createSleeper();
        $timer = $this->_createTimer();

        //60/min
        $plugin = new Swift_Plugins_ThrottlerPlugin(
            60, Swift_Plugins_ThrottlerPlugin::MESSAGES_PER_MINUTE,
            $sleeper, $timer
            );

        $timer->shouldReceive('getTimestamp')->once()->andReturn(0);
        $timer->shouldReceive('getTimestamp')->once()->andReturn(0); //expected 1 (sleep 1)
        $timer->shouldReceive('getTimestamp')->once()->andReturn(2); //expected 2
        $timer->shouldReceive('getTimestamp')->once()->andReturn(2); //expected 3 (sleep 1)
        $timer->shouldReceive('getTimestamp')->once()->andReturn(4); //expected 4
        $sleeper->shouldReceive('sleep')->twice()->with(1);

        //60 messages per minute
        //1 message per second

        $message = $this->_createMessageWithByteCount(10);

        $evt = $this->_createSendEvent($message);

        for ($i = 0; $i < 5; ++$i) {
            $plugin->beforeSendPerformed($evt);
            $plugin->sendPerformed($evt);
        }
    }

    // -- Creation Methods

    private function _createSleeper()
    {
        return $this->getMockery('Swift_Plugins_Sleeper');
    }

    private function _createTimer()
    {
        return $this->getMockery('Swift_Plugins_Timer');
    }

    private function _createMessageWithByteCount($bytes)
    {
        $msg = $this->getMockery('Swift_Mime_Message');
        $msg->shouldReceive('toByteStream')
            ->zeroOrMoreTimes()
            ->andReturnUsing(function ($is) use ($bytes) {
                for ($i = 0; $i < $bytes; ++$i) {
                    $is->write('x');
                }
            });

        return $msg;
    }

    private function _createSendEvent($message)
    {
        $evt = $this->getMockery('Swift_Events_SendEvent');
        $evt->shouldReceive('getMessage')
            ->zeroOrMoreTimes()
            ->andReturn($message);

        return $evt;
    }
}
<?php

class Swift_Signers_DKIMSignerTest extends \SwiftMailerTestCase
{
    public function setUp()
    {
        if (version_compare(phpversion(), '5.4', '<') && !defined('OPENSSL_ALGO_SHA256')) {
            $this->markTestSkipped(
                'skipping because of https://bugs.php.net/bug.php?id=61421'
             );
        }
    }

    public function testBasicSigningHeaderManipulation()
    {
        $headers = $this->_createHeaders();
        $messageContent = 'Hello World';
        $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector');
        /* @var $signer Swift_Signers_HeaderSigner */
        $altered = $signer->getAlteredHeaders();
        $signer->reset();
        // Headers
        $signer->setHeaders($headers);
        // Body
        $signer->startBody();
        $signer->write($messageContent);
        $signer->endBody();
        // Signing
        $signer->addSignature($headers);
    }

    // Default Signing
    public function testSigningDefaults()
    {
        $headerSet = $this->_createHeaderSet();
        $messageContent = 'Hello World';
        $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector');
        $signer->setSignatureTimestamp('1299879181');
        $altered = $signer->getAlteredHeaders();
        $this->assertEquals(array('DKIM-Signature'), $altered);
        $signer->reset();
        $signer->setHeaders($headerSet);
        $this->assertFalse($headerSet->has('DKIM-Signature'));
        $signer->startBody();
        $signer->write($messageContent);
        $signer->endBody();
        $signer->addSignature($headerSet);
        $this->assertTrue($headerSet->has('DKIM-Signature'));
        $dkim = $headerSet->getAll('DKIM-Signature');
        $sig = reset($dkim);
        $this->assertEquals($sig->getValue(), 'v=1; a=rsa-sha1; bh=wlbYcY9O9OPInGJ4D0E/rGsvMLE=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; t=1299879181; b=RMSNelzM2O5MAAnMjT3G3/VF36S3DGJXoPCXR001F1WDReu0prGphWjuzK/m6V1pwqQL8cCNg Hi74mTx2bvyAvmkjvQtJf1VMUOCc9WHGcm1Yec66I3ZWoNMGSWZ1EKAm2CtTzyG0IFw4ml9DI wSkyAFxlgicckDD6FibhqwX4w=');
    }

    // SHA256 Signing
    public function testSigning256()
    {
        $headerSet = $this->_createHeaderSet();
        $messageContent = 'Hello World';
        $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector');
        $signer->setHashAlgorithm('rsa-sha256');
        $signer->setSignatureTimestamp('1299879181');
        $altered = $signer->getAlteredHeaders();
        $this->assertEquals(array('DKIM-Signature'), $altered);
        $signer->reset();
        $signer->setHeaders($headerSet);
        $this->assertFalse($headerSet->has('DKIM-Signature'));
        $signer->startBody();
        $signer->write($messageContent);
        $signer->endBody();
        $signer->addSignature($headerSet);
        $this->assertTrue($headerSet->has('DKIM-Signature'));
        $dkim = $headerSet->getAll('DKIM-Signature');
        $sig = reset($dkim);
        $this->assertEquals($sig->getValue(), 'v=1; a=rsa-sha256; bh=f+W+hu8dIhf2VAni89o8lF6WKTXi7nViA4RrMdpD5/U=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; t=1299879181; b=jqPmieHzF5vR9F4mXCAkowuphpO4iJ8IAVuioh1BFZ3VITXZj5jlOFxULJMBiiApm2keJirnh u4mzogj444QkpT3lJg8/TBGAYQPdcvkG3KC0jdyN6QpSgpITBJG2BwWa+keXsv2bkQgLRAzNx qRhP45vpHCKun0Tg9LrwW/KCg=');
    }

    // Relaxed/Relaxed Hash Signing
    public function testSigningRelaxedRelaxed256()
    {
        $headerSet = $this->_createHeaderSet();
        $messageContent = 'Hello World';
        $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector');
        $signer->setHashAlgorithm('rsa-sha256');
        $signer->setSignatureTimestamp('1299879181');
        $signer->setBodyCanon('relaxed');
        $signer->setHeaderCanon('relaxed');
        $altered = $signer->getAlteredHeaders();
        $this->assertEquals(array('DKIM-Signature'), $altered);
        $signer->reset();
        $signer->setHeaders($headerSet);
        $this->assertFalse($headerSet->has('DKIM-Signature'));
        $signer->startBody();
        $signer->write($messageContent);
        $signer->endBody();
        $signer->addSignature($headerSet);
        $this->assertTrue($headerSet->has('DKIM-Signature'));
        $dkim = $headerSet->getAll('DKIM-Signature');
        $sig = reset($dkim);
        $this->assertEquals($sig->getValue(), 'v=1; a=rsa-sha256; bh=f+W+hu8dIhf2VAni89o8lF6WKTXi7nViA4RrMdpD5/U=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; c=relaxed/relaxed; t=1299879181; b=gzOI+PX6HpZKQFzwwmxzcVJsyirdLXOS+4pgfCpVHQIdqYusKLrhlLeFBTNoz75HrhNvGH6T0 Rt3w5aTqkrWfUuAEYt0Ns14GowLM7JojaFN+pZ4eYnRB3CBBgW6fee4NEMD5WPca3uS09tr1E 10RYh9ILlRtl+84sovhx5id3Y=');
    }

    // Relaxed/Simple Hash Signing
    public function testSigningRelaxedSimple256()
    {
        $headerSet = $this->_createHeaderSet();
        $messageContent = 'Hello World';
        $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector');
        $signer->setHashAlgorithm('rsa-sha256');
        $signer->setSignatureTimestamp('1299879181');
        $signer->setHeaderCanon('relaxed');
        $altered = $signer->getAlteredHeaders();
        $this->assertEquals(array('DKIM-Signature'), $altered);
        $signer->reset();
        $signer->setHeaders($headerSet);
        $this->assertFalse($headerSet->has('DKIM-Signature'));
        $signer->startBody();
        $signer->write($messageContent);
        $signer->endBody();
        $signer->addSignature($headerSet);
        $this->assertTrue($headerSet->has('DKIM-Signature'));
        $dkim = $headerSet->getAll('DKIM-Signature');
        $sig = reset($dkim);
        $this->assertEquals($sig->getValue(), 'v=1; a=rsa-sha256; bh=f+W+hu8dIhf2VAni89o8lF6WKTXi7nViA4RrMdpD5/U=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; c=relaxed; t=1299879181; b=dLPJNec5v81oelyzGOY0qPqTlGnQeNfUNBOrV/JKbStr3NqWGI9jH4JAe2YvO2V32lfPNoby1 4MMzZ6EPkaZkZDDSPa+53YbCPQAlqiD9QZZIUe2UNM33HN8yAMgiWEF5aP7MbQnxeVZMfVLEl 9S8qOImu+K5JZqhQQTL0dgLwA=');
    }

    // Simple/Relaxed Hash Signing
    public function testSigningSimpleRelaxed256()
    {
        $headerSet = $this->_createHeaderSet();
        $messageContent = 'Hello World';
        $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector');
        $signer->setHashAlgorithm('rsa-sha256');
        $signer->setSignatureTimestamp('1299879181');
        $signer->setBodyCanon('relaxed');
        $altered = $signer->getAlteredHeaders();
        $this->assertEquals(array('DKIM-Signature'), $altered);
        $signer->reset();
        $signer->setHeaders($headerSet);
        $this->assertFalse($headerSet->has('DKIM-Signature'));
        $signer->startBody();
        $signer->write($messageContent);
        $signer->endBody();
        $signer->addSignature($headerSet);
        $this->assertTrue($headerSet->has('DKIM-Signature'));
        $dkim = $headerSet->getAll('DKIM-Signature');
        $sig = reset($dkim);
        $this->assertEquals($sig->getValue(), 'v=1; a=rsa-sha256; bh=f+W+hu8dIhf2VAni89o8lF6WKTXi7nViA4RrMdpD5/U=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; c=simple/relaxed; t=1299879181; b=M5eomH/zamyzix9kOes+6YLzQZxuJdBP4x3nP9zF2N26eMLG2/cBKbnNyqiOTDhJdYfWPbLIa 1CWnjST0j5p4CpeOkGYuiE+M4TWEZwhRmRWootlPO3Ii6XpbBJKFk1o9zviS7OmXblUUE4aqb yRSIMDhtLdCK5GlaCneFLN7RQ=');
    }

    // -- Creation Methods
    private function _createHeaderSet()
    {
        $cache = new Swift_KeyCache_ArrayKeyCache(new Swift_KeyCache_SimpleKeyCacheInputStream());
        $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
        $contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder();

        $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder(new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'));
        $paramEncoder = new Swift_Encoder_Rfc2231Encoder(new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'));
        $grammar = new Swift_Mime_Grammar();
        $headers = new Swift_Mime_SimpleHeaderSet(new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $grammar));

        return $headers;
    }

    /**
     * @return Swift_Mime_Headers
     */
    private function _createHeaders()
    {
        $x = 0;
        $cache = new Swift_KeyCache_ArrayKeyCache(new Swift_KeyCache_SimpleKeyCacheInputStream());
        $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory();
        $contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder();

        $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder(new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'));
        $paramEncoder = new Swift_Encoder_Rfc2231Encoder(new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'));
        $grammar = new Swift_Mime_Grammar();
        $headerFactory = new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $grammar);
        $headers = $this->getMockery('Swift_Mime_HeaderSet');

        $headers->shouldReceive('listAll')
                ->zeroOrMoreTimes()
                ->andReturn(array('From', 'To', 'Date', 'Subject'));
        $headers->shouldReceive('has')
                ->zeroOrMoreTimes()
                ->with('From')
                ->andReturn(true);
        $headers->shouldReceive('getAll')
                ->zeroOrMoreTimes()
                ->with('From')
                ->andReturn(array($headerFactory->createMailboxHeader('From', 'test@test.test')));
        $headers->shouldReceive('has')
                ->zeroOrMoreTimes()
                ->with('To')
                ->andReturn(true);
        $headers->shouldReceive('getAll')
                ->zeroOrMoreTimes()
                ->with('To')
                ->andReturn(array($headerFactory->createMailboxHeader('To', 'test@test.test')));
        $headers->shouldReceive('has')
                ->zeroOrMoreTimes()
                ->with('Date')
                ->andReturn(true);
        $headers->shouldReceive('getAll')
                ->zeroOrMoreTimes()
                ->with('Date')
                ->andReturn(array($headerFactory->createTextHeader('Date', 'Fri, 11 Mar 2011 20:56:12 +0000 (GMT)')));
        $headers->shouldReceive('has')
                ->zeroOrMoreTimes()
                ->with('Subject')
                ->andReturn(true);
        $headers->shouldReceive('getAll')
                ->zeroOrMoreTimes()
                ->with('Subject')
                ->andReturn(array($headerFactory->createTextHeader('Subject', 'Foo Bar Text Message')));
        $headers->shouldReceive('addTextHeader')
                ->zeroOrMoreTimes()
                ->with('DKIM-Signature', \Mockery::any())
                ->andReturn(true);
        $headers->shouldReceive('getAll')
                ->zeroOrMoreTimes()
                ->with('DKIM-Signature')
                ->andReturn(array($headerFactory->createTextHeader('DKIM-Signature', 'Foo Bar Text Message')));

        return $headers;
    }
}
<?php

/**
 * @todo
 */
class Swift_Signers_OpenDKIMSignerTest extends \SwiftMailerTestCase
{
    public function setUp()
    {
        if (!extension_loaded('opendkim')) {
            $this->markTestSkipped(
                'Need OpenDKIM extension run these tests.'
             );
        }
    }

    public function testBasicSigningHeaderManipulation()
    {
    }

    // Default Signing
    public function testSigningDefaults()
    {
    }

    // SHA256 Signing
    public function testSigning256()
    {
    }

    // Relaxed/Relaxed Hash Signing
    public function testSigningRelaxedRelaxed256()
    {
    }

    // Relaxed/Simple Hash Signing
    public function testSigningRelaxedSimple256()
    {
    }

    // Simple/Relaxed Hash Signing
    public function testSigningSimpleRelaxed256()
    {
    }
}
<?php

class Swift_Signers_SMimeSignerTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @var Swift_StreamFilters_StringReplacementFilterFactory
     */
    protected $replacementFactory;

    protected $samplesDir;

    public function setUp()
    {
        $this->replacementFactory = Swift_DependencyContainer::getInstance()
            ->lookup('transport.replacementfactory');

        $this->samplesDir = str_replace('\\', '/', realpath(__DIR__.'/../../../_samples/')).'/';
    }

    public function testUnSingedMessage()
    {
        $message = Swift_SignedMessage::newInstance('Wonderful Subject')
          ->setFrom(array('john@doe.com' => 'John Doe'))
          ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
          ->setBody('Here is the message itself');

        $this->assertEquals('Here is the message itself', $message->getBody());
    }

    public function testSingedMessage()
    {
        $message = Swift_SignedMessage::newInstance('Wonderful Subject')
          ->setFrom(array('john@doe.com' => 'John Doe'))
          ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
          ->setBody('Here is the message itself');

        $signer = new Swift_Signers_SMimeSigner();
        $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key');
        $message->attachSigner($signer);

        $messageStream = $this->newFilteredStream();
        $message->toByteStream($messageStream);
        $messageStream->commit();

        $entityString = $messageStream->getContent();
        $headers = self::getHeadersOfMessage($entityString);

        if (!($boundary = $this->getBoundary($headers['content-type']))) {
            return false;
        }

        $expectedBody = <<<OEL
This is an S/MIME signed message

--$boundary
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

Here is the message itself
--$boundary
Content-Type: application/(x\-)?pkcs7-signature; name="smime\.p7s"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime\.p7s"

(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})

--$boundary--
OEL;
        $this->assertValidVerify($expectedBody, $messageStream);
        unset($messageStream);
    }

    public function testSingedMessageExtraCerts()
    {
        $message = Swift_SignedMessage::newInstance('Wonderful Subject')
          ->setFrom(array('john@doe.com' => 'John Doe'))
          ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
          ->setBody('Here is the message itself');

        $signer = new Swift_Signers_SMimeSigner();
        $signer->setSignCertificate($this->samplesDir.'smime/sign2.crt', $this->samplesDir.'smime/sign2.key', PKCS7_DETACHED, $this->samplesDir.'smime/intermediate.crt');
        $message->attachSigner($signer);

        $messageStream = $this->newFilteredStream();
        $message->toByteStream($messageStream);
        $messageStream->commit();

        $entityString = $messageStream->getContent();
        $headers = self::getHeadersOfMessage($entityString);

        if (!($boundary = $this->getBoundary($headers['content-type']))) {
            return false;
        }

        $expectedBody = <<<OEL
This is an S/MIME signed message

--$boundary
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

Here is the message itself
--$boundary
Content-Type: application/(x\-)?pkcs7-signature; name="smime\.p7s"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime\.p7s"

(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})

--$boundary--
OEL;
        $this->assertValidVerify($expectedBody, $messageStream);
        unset($messageStream);
    }

    public function testSingedMessageBinary()
    {
        $message = Swift_SignedMessage::newInstance('Wonderful Subject')
          ->setFrom(array('john@doe.com' => 'John Doe'))
          ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
          ->setBody('Here is the message itself');

        $signer = new Swift_Signers_SMimeSigner();
        $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key', PKCS7_BINARY);
        $message->attachSigner($signer);

        $messageStream = $this->newFilteredStream();
        $message->toByteStream($messageStream);
        $messageStream->commit();

        $entityString = $messageStream->getContent();
        $headers = self::getHeadersOfMessage($entityString);

        if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=signed\-data;#', $headers['content-type'])) {
            $this->fail('Content-type does not match.');

            return false;
        }

        $this->assertEquals($headers['content-transfer-encoding'], 'base64');
        $this->assertEquals($headers['content-disposition'], 'attachment; filename="smime.p7m"');

        $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})';

        $messageStreamClean = $this->newFilteredStream();

        $this->assertValidVerify($expectedBody, $messageStream);
        unset($messageStreamClean, $messageStream);
    }

    public function testSingedMessageWithAttachments()
    {
        $message = Swift_SignedMessage::newInstance('Wonderful Subject')
          ->setFrom(array('john@doe.com' => 'John Doe'))
          ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
          ->setBody('Here is the message itself');

        $message->attach(Swift_Attachment::fromPath($this->samplesDir.'/files/textfile.zip'));

        $signer = new Swift_Signers_SMimeSigner();
        $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key');
        $message->attachSigner($signer);

        $messageStream = $this->newFilteredStream();
        $message->toByteStream($messageStream);
        $messageStream->commit();

        $entityString = $messageStream->getContent();
        $headers = self::getHeadersOfMessage($entityString);

        if (!($boundary = $this->getBoundary($headers['content-type']))) {
            return false;
        }

        $expectedBody = <<<OEL
This is an S/MIME signed message

--$boundary
Content-Type: multipart/mixed;
 boundary="([a-z0-9\\'\\(\\)\\+_\\-,\\.\\/:=\\?\\ ]{0,69}[a-z0-9\\'\\(\\)\\+_\\-,\\.\\/:=\\?])"


--\\1
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

Here is the message itself

--\\1
Content-Type: application/zip; name=textfile\\.zip
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=textfile\\.zip

UEsDBAoAAgAAAMi6VjiOTiKwLgAAAC4AAAAMABUAdGV4dGZpbGUudHh0VVQJAAN3vr5Hd76\\+R1V4
BAD1AfUBVGhpcyBpcyBwYXJ0IG9mIGEgU3dpZnQgTWFpbGVyIHY0IHNtb2tlIHRlc3QuClBLAQIX
AwoAAgAAAMi6VjiOTiKwLgAAAC4AAAAMAA0AAAAAAAEAAACkgQAAAAB0ZXh0ZmlsZS50eHRVVAUA
A3e\\+vkdVeAAAUEsFBgAAAAABAAEARwAAAG0AAAAAAA==

--\\1--

--$boundary
Content-Type: application/(x\-)?pkcs7-signature; name="smime\\.p7s"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime\\.p7s"

(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})

--$boundary--
OEL;

        $this->assertValidVerify($expectedBody, $messageStream);
        unset($messageStream);
    }

    public function testEncryptedMessage()
    {
        $message = Swift_SignedMessage::newInstance('Wonderful Subject')
          ->setFrom(array('john@doe.com' => 'John Doe'))
          ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
          ->setBody('Here is the message itself');

        $originalMessage = $this->cleanMessage($message->toString());

        $signer = new Swift_Signers_SMimeSigner();
        $signer->setEncryptCertificate($this->samplesDir.'smime/encrypt.crt');
        $message->attachSigner($signer);

        $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
        $message->toByteStream($messageStream);
        $messageStream->commit();

        $entityString = $messageStream->getContent();
        $headers = self::getHeadersOfMessage($entityString);

        if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=enveloped\-data;#', $headers['content-type'])) {
            $this->fail('Content-type does not match.');

            return false;
        }

        $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})';

        $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();

        if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt.crt', array('file://'.$this->samplesDir.'smime/encrypt.key', 'swift'))) {
            $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string()));
        }

        $this->assertEquals($originalMessage, $decryptedMessageStream->getContent());
        unset($decryptedMessageStream, $messageStream);
    }

    public function testEncryptedMessageWithMultipleCerts()
    {
        $message = Swift_SignedMessage::newInstance('Wonderful Subject')
          ->setFrom(array('john@doe.com' => 'John Doe'))
          ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
          ->setBody('Here is the message itself');

        $originalMessage = $this->cleanMessage($message->toString());

        $signer = new Swift_Signers_SMimeSigner();
        $signer->setEncryptCertificate(array($this->samplesDir.'smime/encrypt.crt', $this->samplesDir.'smime/encrypt2.crt'));
        $message->attachSigner($signer);

        $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
        $message->toByteStream($messageStream);
        $messageStream->commit();

        $entityString = $messageStream->getContent();
        $headers = self::getHeadersOfMessage($entityString);

        if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=enveloped\-data;#', $headers['content-type'])) {
            $this->fail('Content-type does not match.');

            return false;
        }

        $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})';

        $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();

        if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt.crt', array('file://'.$this->samplesDir.'smime/encrypt.key', 'swift'))) {
            $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string()));
        }

        $this->assertEquals($originalMessage, $decryptedMessageStream->getContent());
        unset($decryptedMessageStream);

        $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();

        if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt2.crt', array('file://'.$this->samplesDir.'smime/encrypt2.key', 'swift'))) {
            $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string()));
        }

        $this->assertEquals($originalMessage, $decryptedMessageStream->getContent());
        unset($decryptedMessageStream, $messageStream);
    }

    public function testSignThenEncryptedMessage()
    {
        $message = Swift_SignedMessage::newInstance('Wonderful Subject')
          ->setFrom(array('john@doe.com' => 'John Doe'))
          ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
          ->setBody('Here is the message itself');

        $signer = new Swift_Signers_SMimeSigner();
        $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key');
        $signer->setEncryptCertificate($this->samplesDir.'smime/encrypt.crt');
        $message->attachSigner($signer);

        $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
        $message->toByteStream($messageStream);
        $messageStream->commit();

        $entityString = $messageStream->getContent();
        $headers = self::getHeadersOfMessage($entityString);

        if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=enveloped\-data;#', $headers['content-type'])) {
            $this->fail('Content-type does not match.');

            return false;
        }

        $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})';

        $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();

        if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt.crt', array('file://'.$this->samplesDir.'smime/encrypt.key', 'swift'))) {
            $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string()));
        }

        $entityString = $decryptedMessageStream->getContent();
        $headers = self::getHeadersOfMessage($entityString);

        if (!($boundary = $this->getBoundary($headers['content-type']))) {
            return false;
        }

        $expectedBody = <<<OEL
This is an S/MIME signed message

--$boundary
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable

Here is the message itself
--$boundary
Content-Type: application/(x\-)?pkcs7-signature; name="smime\.p7s"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime\.p7s"

(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})

--$boundary--
OEL;

        if (!$this->assertValidVerify($expectedBody, $decryptedMessageStream)) {
            return false;
        }

        unset($decryptedMessageStream, $messageStream);
    }

    public function testEncryptThenSignMessage()
    {
        $message = Swift_SignedMessage::newInstance('Wonderful Subject')
          ->setFrom(array('john@doe.com' => 'John Doe'))
          ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name'))
          ->setBody('Here is the message itself');

        $originalMessage = $this->cleanMessage($message->toString());

        $signer = Swift_Signers_SMimeSigner::newInstance();
        $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key');
        $signer->setEncryptCertificate($this->samplesDir.'smime/encrypt.crt');
        $signer->setSignThenEncrypt(false);
        $message->attachSigner($signer);

        $messageStream = $this->newFilteredStream();
        $message->toByteStream($messageStream);
        $messageStream->commit();

        $entityString = $messageStream->getContent();
        $headers = self::getHeadersOfMessage($entityString);

        if (!($boundary = $this->getBoundary($headers['content-type']))) {
            return false;
        }

        $expectedBody = <<<OEL
This is an S/MIME signed message

--$boundary
(?P<encrypted_message>MIME-Version: 1\.0
Content-Disposition: attachment; filename="smime\.p7m"
Content-Type: application/(x\-)?pkcs7-mime; smime-type=enveloped-data; name="smime\.p7m"
Content-Transfer-Encoding: base64

(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})


)--$boundary
Content-Type: application/(x\-)?pkcs7-signature; name="smime\.p7s"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime\.p7s"

(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})

--$boundary--
OEL;

        if (!$this->assertValidVerify($expectedBody, $messageStream)) {
            return false;
        }

        $expectedBody = str_replace("\n", "\r\n", $expectedBody);
        if (!preg_match('%'.$expectedBody.'*%m', $entityString, $entities)) {
            $this->fail('Failed regex match.');

            return false;
        }

        $messageStreamClean = new Swift_ByteStream_TemporaryFileByteStream();
        $messageStreamClean->write($entities['encrypted_message']);

        $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream();

        if (!openssl_pkcs7_decrypt($messageStreamClean->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt.crt', array('file://'.$this->samplesDir.'smime/encrypt.key', 'swift'))) {
            $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string()));
        }

        $this->assertEquals($originalMessage, $decryptedMessageStream->getContent());
        unset($messageStreamClean, $messageStream, $decryptedMessageStream);
    }

    protected function assertValidVerify($expected, Swift_ByteStream_TemporaryFileByteStream $messageStream)
    {
        $actual = $messageStream->getContent();

        // File is UNIX encoded so convert them to correct line ending
        $expected = str_replace("\n", "\r\n", $expected);

        $actual = trim(self::getBodyOfMessage($actual));
        if (!$this->assertRegExp('%^'.$expected.'$\s*%m', $actual)) {
            return false;
        }

        $opensslOutput = new Swift_ByteStream_TemporaryFileByteStream();
        $verify = openssl_pkcs7_verify($messageStream->getPath(), null, $opensslOutput->getPath(), array($this->samplesDir.'smime/ca.crt'));

        if (false === $verify) {
            $this->fail('Verification of the message failed.');

            return false;
        } elseif (-1 === $verify) {
            $this->fail(sprintf('Verification of the message failed. Internal error "%s".', openssl_error_string()));

            return false;
        }

        return true;
    }

    protected function getBoundary($contentType)
    {
        if (!preg_match('/boundary=("[^"]+"|(?:[^\s]+|$))/is', $contentType, $contentTypeData)) {
            $this->fail('Failed to find Boundary parameter');

            return false;
        }

        return trim($contentTypeData[1], '"');
    }

    protected function newFilteredStream()
    {
        $messageStream = new Swift_ByteStream_TemporaryFileByteStream();
        $messageStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF');
        $messageStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF');

        return $messageStream;
    }

    protected static function getBodyOfMessage($message)
    {
        return substr($message, strpos($message, "\r\n\r\n"));
    }

    /**
     * Strips of the sender headers and Mime-Version.
     *
     * @param Swift_ByteStream_TemporaryFileByteStream $messageStream
     * @param Swift_ByteStream_TemporaryFileByteStream $inputStream
     */
    protected function cleanMessage($content)
    {
        $newContent = '';

        $headers = self::getHeadersOfMessage($content);
        foreach ($headers as $headerName => $value) {
            if (!in_array($headerName, array('content-type', 'content-transfer-encoding', 'content-disposition'))) {
                continue;
            }

            $headerName = explode('-', $headerName);
            $headerName = array_map('ucfirst', $headerName);
            $headerName = implode('-', $headerName);

            if (strlen($value) > 62) {
                $value = wordwrap($value, 62, "\n ");
            }

            $newContent .= "$headerName: $value\r\n";
        }

        return $newContent."\r\n".ltrim(self::getBodyOfMessage($content));
    }

    /**
     * Returns the headers of the message.
     *
     * Header-names are lowercase.
     *
     * @param string $message
     *
     * @return array
     */
    protected static function getHeadersOfMessage($message)
    {
        $headersPosEnd = strpos($message, "\r\n\r\n");
        $headerData = substr($message, 0, $headersPosEnd);
        $headerLines = explode("\r\n", $headerData);

        if (empty($headerLines)) {
            return array();
        }

        $headers = array();

        foreach ($headerLines as $headerLine) {
            if (ctype_space($headerLines[0]) || false === strpos($headerLine, ':')) {
                $headers[$currentHeaderName] .= ' '.trim($headerLine);
                continue;
            }

            $header = explode(':', $headerLine, 2);
            $currentHeaderName = strtolower($header[0]);
            $headers[$currentHeaderName] = trim($header[1]);
        }

        return $headers;
    }
}
<?php

class Swift_StreamFilters_ByteArrayReplacementFilterTest extends \PHPUnit_Framework_TestCase
{
    public function testBasicReplacementsAreMade()
    {
        $filter = $this->_createFilter(array(0x61, 0x62), array(0x63, 0x64));
        $this->assertEquals(
            array(0x59, 0x60, 0x63, 0x64, 0x65),
            $filter->filter(array(0x59, 0x60, 0x61, 0x62, 0x65))
            );
    }

    public function testShouldBufferReturnsTrueIfPartialMatchAtEndOfBuffer()
    {
        $filter = $this->_createFilter(array(0x61, 0x62), array(0x63, 0x64));
        $this->assertTrue($filter->shouldBuffer(array(0x59, 0x60, 0x61)),
            '%s: Filter should buffer since 0x61 0x62 is the needle and the ending '.
            '0x61 could be from 0x61 0x62'
            );
    }

    public function testFilterCanMakeMultipleReplacements()
    {
        $filter = $this->_createFilter(array(array(0x61), array(0x62)), array(0x63));
        $this->assertEquals(
            array(0x60, 0x63, 0x60, 0x63, 0x60),
            $filter->filter(array(0x60, 0x61, 0x60, 0x62, 0x60))
            );
    }

    public function testMultipleReplacementsCanBeDifferent()
    {
        $filter = $this->_createFilter(array(array(0x61), array(0x62)), array(array(0x63), array(0x64)));
        $this->assertEquals(
            array(0x60, 0x63, 0x60, 0x64, 0x60),
            $filter->filter(array(0x60, 0x61, 0x60, 0x62, 0x60))
            );
    }

    public function testShouldBufferReturnsFalseIfPartialMatchNotAtEndOfString()
    {
        $filter = $this->_createFilter(array(0x0D, 0x0A), array(0x0A));
        $this->assertFalse($filter->shouldBuffer(array(0x61, 0x62, 0x0D, 0x0A, 0x63)),
            '%s: Filter should not buffer since x0Dx0A is the needle and is not at EOF'
            );
    }

    public function testShouldBufferReturnsTrueIfAnyOfMultipleMatchesAtEndOfString()
    {
        $filter = $this->_createFilter(array(array(0x61, 0x62), array(0x63)), array(0x64));
        $this->assertTrue($filter->shouldBuffer(array(0x59, 0x60, 0x61)),
            '%s: Filter should buffer since 0x61 0x62 is a needle and the ending '.
            '0x61 could be from 0x61 0x62'
            );
    }

    public function testConvertingAllLineEndingsToCRLFWhenInputIsLF()
    {
        $filter = $this->_createFilter(
            array(array(0x0D, 0x0A), array(0x0D), array(0x0A)),
            array(array(0x0A), array(0x0A), array(0x0D, 0x0A))
            );

        $this->assertEquals(
            array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63),
            $filter->filter(array(0x60, 0x0A, 0x61, 0x0A, 0x62, 0x0A, 0x63))
            );
    }

    public function testConvertingAllLineEndingsToCRLFWhenInputIsCR()
    {
        $filter = $this->_createFilter(
            array(array(0x0D, 0x0A), array(0x0D), array(0x0A)),
            array(array(0x0A), array(0x0A), array(0x0D, 0x0A))
            );

        $this->assertEquals(
            array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63),
            $filter->filter(array(0x60, 0x0D, 0x61, 0x0D, 0x62, 0x0D, 0x63))
            );
    }

    public function testConvertingAllLineEndingsToCRLFWhenInputIsCRLF()
    {
        $filter = $this->_createFilter(
            array(array(0x0D, 0x0A), array(0x0D), array(0x0A)),
            array(array(0x0A), array(0x0A), array(0x0D, 0x0A))
            );

        $this->assertEquals(
            array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63),
            $filter->filter(array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63))
            );
    }

    public function testConvertingAllLineEndingsToCRLFWhenInputIsLFCR()
    {
        $filter = $this->_createFilter(
            array(array(0x0D, 0x0A), array(0x0D), array(0x0A)),
            array(array(0x0A), array(0x0A), array(0x0D, 0x0A))
            );

        $this->assertEquals(
            array(0x60, 0x0D, 0x0A, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x0D, 0x0A, 0x63),
            $filter->filter(array(0x60, 0x0A, 0x0D, 0x61, 0x0A, 0x0D, 0x62, 0x0A, 0x0D, 0x63))
            );
    }

    public function testConvertingAllLineEndingsToCRLFWhenInputContainsLFLF()
    {
        //Lighthouse Bug #23

        $filter = $this->_createFilter(
            array(array(0x0D, 0x0A), array(0x0D), array(0x0A)),
            array(array(0x0A), array(0x0A), array(0x0D, 0x0A))
            );

        $this->assertEquals(
            array(0x60, 0x0D, 0x0A, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x0D, 0x0A, 0x63),
            $filter->filter(array(0x60, 0x0A, 0x0A, 0x61, 0x0A, 0x0A, 0x62, 0x0A, 0x0A, 0x63))
            );
    }

    // -- Creation methods

    private function _createFilter($search, $replace)
    {
        return new Swift_StreamFilters_ByteArrayReplacementFilter($search, $replace);
    }
}
<?php

class Swift_StreamFilters_StringReplacementFilterFactoryTest extends \PHPUnit_Framework_TestCase
{
    public function testInstancesOfStringReplacementFilterAreCreated()
    {
        $factory = $this->_createFactory();
        $this->assertInstanceof(
            'Swift_StreamFilters_StringReplacementFilter',
            $factory->createFilter('a', 'b')
        );
    }

    public function testSameInstancesAreCached()
    {
        $factory = $this->_createFactory();
        $filter1 = $factory->createFilter('a', 'b');
        $filter2 = $factory->createFilter('a', 'b');
        $this->assertSame($filter1, $filter2, '%s: Instances should be cached');
    }

    public function testDifferingInstancesAreNotCached()
    {
        $factory = $this->_createFactory();
        $filter1 = $factory->createFilter('a', 'b');
        $filter2 = $factory->createFilter('a', 'c');
        $this->assertNotEquals($filter1, $filter2,
            '%s: Differing instances should not be cached'
            );
    }

    // -- Creation methods

    private function _createFactory()
    {
        return new Swift_StreamFilters_StringReplacementFilterFactory();
    }
}
<?php

class Swift_StreamFilters_StringReplacementFilterTest extends \PHPUnit_Framework_TestCase
{
    public function testBasicReplacementsAreMade()
    {
        $filter = $this->_createFilter('foo', 'bar');
        $this->assertEquals('XbarYbarZ', $filter->filter('XfooYfooZ'));
    }

    public function testShouldBufferReturnsTrueIfPartialMatchAtEndOfBuffer()
    {
        $filter = $this->_createFilter('foo', 'bar');
        $this->assertTrue($filter->shouldBuffer('XfooYf'),
            '%s: Filter should buffer since "foo" is the needle and the ending '.
            '"f" could be from "foo"'
            );
    }

    public function testFilterCanMakeMultipleReplacements()
    {
        $filter = $this->_createFilter(array('a', 'b'), 'foo');
        $this->assertEquals('XfooYfooZ', $filter->filter('XaYbZ'));
    }

    public function testMultipleReplacementsCanBeDifferent()
    {
        $filter = $this->_createFilter(array('a', 'b'), array('foo', 'zip'));
        $this->assertEquals('XfooYzipZ', $filter->filter('XaYbZ'));
    }

    public function testShouldBufferReturnsFalseIfPartialMatchNotAtEndOfString()
    {
        $filter = $this->_createFilter("\r\n", "\n");
        $this->assertFalse($filter->shouldBuffer("foo\r\nbar"),
            '%s: Filter should not buffer since x0Dx0A is the needle and is not at EOF'
            );
    }

    public function testShouldBufferReturnsTrueIfAnyOfMultipleMatchesAtEndOfString()
    {
        $filter = $this->_createFilter(array('foo', 'zip'), 'bar');
        $this->assertTrue($filter->shouldBuffer('XfooYzi'),
            '%s: Filter should buffer since "zip" is a needle and the ending '.
            '"zi" could be from "zip"'
            );
    }

    // -- Creation methods

    private function _createFilter($search, $replace)
    {
        return new Swift_StreamFilters_StringReplacementFilter($search, $replace);
    }
}
<?php

require_once __DIR__.'/AbstractSmtpTest.php';

abstract class Swift_Transport_AbstractSmtpEventSupportTest extends Swift_Transport_AbstractSmtpTest
{
    public function testRegisterPluginLoadsPluginInEventDispatcher()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $listener = $this->getMockery('Swift_Events_EventListener');
        $smtp = $this->_getTransport($buf, $dispatcher);
        $dispatcher->shouldReceive('bindEventListener')
                   ->once()
                   ->with($listener);

        $smtp->registerPlugin($listener);
    }

    public function testSendingDispatchesBeforeSendEvent()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $message = $this->_createMessage();
        $smtp = $this->_getTransport($buf, $dispatcher);
        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();

        $message->shouldReceive('getFrom')
                ->zeroOrMoreTimes()
                ->andReturn(array('chris@swiftmailer.org' => null));
        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('mark@swiftmailer.org' => 'Mark'));
        $dispatcher->shouldReceive('createSendEvent')
                   ->once()
                   ->andReturn($evt);
        $dispatcher->shouldReceive('dispatchEvent')
                   ->once()
                   ->with($evt, 'beforeSendPerformed');
        $dispatcher->shouldReceive('dispatchEvent')
                   ->zeroOrMoreTimes();
        $evt->shouldReceive('bubbleCancelled')
            ->zeroOrMoreTimes()
            ->andReturn(false);

        $this->_finishBuffer($buf);
        $smtp->start();
        $this->assertEquals(1, $smtp->send($message));
    }

    public function testSendingDispatchesSendEvent()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $message = $this->_createMessage();
        $smtp = $this->_getTransport($buf, $dispatcher);
        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();

        $message->shouldReceive('getFrom')
                ->zeroOrMoreTimes()
                ->andReturn(array('chris@swiftmailer.org' => null));
        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('mark@swiftmailer.org' => 'Mark'));
        $dispatcher->shouldReceive('createSendEvent')
                   ->once()
                   ->andReturn($evt);
        $dispatcher->shouldReceive('dispatchEvent')
                   ->once()
                   ->with($evt, 'sendPerformed');
        $dispatcher->shouldReceive('dispatchEvent')
                   ->zeroOrMoreTimes();
        $evt->shouldReceive('bubbleCancelled')
            ->zeroOrMoreTimes()
            ->andReturn(false);

        $this->_finishBuffer($buf);
        $smtp->start();
        $this->assertEquals(1, $smtp->send($message));
    }

    public function testSendEventCapturesFailures()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
        $smtp = $this->_getTransport($buf, $dispatcher);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->zeroOrMoreTimes()
                ->andReturn(array('chris@swiftmailer.org' => null));
        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('mark@swiftmailer.org' => 'Mark'));
        $buf->shouldReceive('write')
            ->once()
            ->with("MAIL FROM:<chris@swiftmailer.org>\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<mark@swiftmailer.org>\r\n")
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn("500 Not now\r\n");
        $dispatcher->shouldReceive('createSendEvent')
                   ->zeroOrMoreTimes()
                   ->with($smtp, \Mockery::any())
                   ->andReturn($evt);
        $dispatcher->shouldReceive('dispatchEvent')
                   ->once()
                   ->with($evt, 'sendPerformed');
        $dispatcher->shouldReceive('dispatchEvent')
                   ->zeroOrMoreTimes();
        $evt->shouldReceive('bubbleCancelled')
            ->zeroOrMoreTimes()
            ->andReturn(false);
        $evt->shouldReceive('setFailedRecipients')
            ->once()
            ->with(array('mark@swiftmailer.org'));

        $this->_finishBuffer($buf);
        $smtp->start();
        $this->assertEquals(0, $smtp->send($message));
    }

    public function testSendEventHasResultFailedIfAllFailures()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
        $smtp = $this->_getTransport($buf, $dispatcher);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->zeroOrMoreTimes()
                ->andReturn(array('chris@swiftmailer.org' => null));
        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('mark@swiftmailer.org' => 'Mark'));
        $buf->shouldReceive('write')
            ->once()
            ->with("MAIL FROM:<chris@swiftmailer.org>\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<mark@swiftmailer.org>\r\n")
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn("500 Not now\r\n");
        $dispatcher->shouldReceive('createSendEvent')
                   ->zeroOrMoreTimes()
                   ->with($smtp, \Mockery::any())
                   ->andReturn($evt);
        $dispatcher->shouldReceive('dispatchEvent')
                   ->once()
                   ->with($evt, 'sendPerformed');
        $dispatcher->shouldReceive('dispatchEvent')
                   ->zeroOrMoreTimes();
        $evt->shouldReceive('bubbleCancelled')
            ->zeroOrMoreTimes()
            ->andReturn(false);
        $evt->shouldReceive('setResult')
            ->once()
            ->with(Swift_Events_SendEvent::RESULT_FAILED);

        $this->_finishBuffer($buf);
        $smtp->start();
        $this->assertEquals(0, $smtp->send($message));
    }

    public function testSendEventHasResultTentativeIfSomeFailures()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
        $smtp = $this->_getTransport($buf, $dispatcher);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->zeroOrMoreTimes()
                ->andReturn(array('chris@swiftmailer.org' => null));
        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array(
                    'mark@swiftmailer.org' => 'Mark',
                    'chris@site.tld' => 'Chris',
                ));
        $buf->shouldReceive('write')
            ->once()
            ->with("MAIL FROM:<chris@swiftmailer.org>\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<mark@swiftmailer.org>\r\n")
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn("500 Not now\r\n");
        $dispatcher->shouldReceive('createSendEvent')
                   ->zeroOrMoreTimes()
                   ->with($smtp, \Mockery::any())
                   ->andReturn($evt);
        $dispatcher->shouldReceive('dispatchEvent')
                   ->once()
                   ->with($evt, 'sendPerformed');
        $dispatcher->shouldReceive('dispatchEvent')
                   ->zeroOrMoreTimes();
        $evt->shouldReceive('bubbleCancelled')
            ->zeroOrMoreTimes()
            ->andReturn(false);
        $evt->shouldReceive('setResult')
            ->once()
            ->with(Swift_Events_SendEvent::RESULT_TENTATIVE);

        $this->_finishBuffer($buf);
        $smtp->start();
        $this->assertEquals(1, $smtp->send($message));
    }

    public function testSendEventHasResultSuccessIfNoFailures()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
        $smtp = $this->_getTransport($buf, $dispatcher);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->zeroOrMoreTimes()
                ->andReturn(array('chris@swiftmailer.org' => null));
        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array(
                    'mark@swiftmailer.org' => 'Mark',
                    'chris@site.tld' => 'Chris',
                ));
        $dispatcher->shouldReceive('createSendEvent')
                   ->zeroOrMoreTimes()
                   ->with($smtp, \Mockery::any())
                   ->andReturn($evt);
        $dispatcher->shouldReceive('dispatchEvent')
                   ->once()
                   ->with($evt, 'sendPerformed');
        $dispatcher->shouldReceive('dispatchEvent')
                   ->zeroOrMoreTimes();
        $evt->shouldReceive('bubbleCancelled')
            ->zeroOrMoreTimes()
            ->andReturn(false);
        $evt->shouldReceive('setResult')
            ->once()
            ->with(Swift_Events_SendEvent::RESULT_SUCCESS);

        $this->_finishBuffer($buf);
        $smtp->start();
        $this->assertEquals(2, $smtp->send($message));
    }

    public function testCancellingEventBubbleBeforeSendStopsEvent()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing();
        $smtp = $this->_getTransport($buf, $dispatcher);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->zeroOrMoreTimes()
                ->andReturn(array('chris@swiftmailer.org' => null));
        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('mark@swiftmailer.org' => 'Mark'));
        $dispatcher->shouldReceive('createSendEvent')
                   ->zeroOrMoreTimes()
                   ->with($smtp, \Mockery::any())
                   ->andReturn($evt);
        $dispatcher->shouldReceive('dispatchEvent')
                   ->once()
                   ->with($evt, 'beforeSendPerformed');
        $dispatcher->shouldReceive('dispatchEvent')
                   ->zeroOrMoreTimes();
        $evt->shouldReceive('bubbleCancelled')
            ->atLeast()->once()
            ->andReturn(true);

        $this->_finishBuffer($buf);
        $smtp->start();
        $this->assertEquals(0, $smtp->send($message));
    }

    public function testStartingTransportDispatchesTransportChangeEvent()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $evt = $this->getMockery('Swift_Events_TransportChangeEvent');
        $smtp = $this->_getTransport($buf, $dispatcher);

        $dispatcher->shouldReceive('createTransportChangeEvent')
                   ->atLeast()->once()
                   ->with($smtp)
                   ->andReturn($evt);
        $dispatcher->shouldReceive('dispatchEvent')
                   ->once()
                   ->with($evt, 'transportStarted');
        $dispatcher->shouldReceive('dispatchEvent')
                   ->zeroOrMoreTimes();
        $evt->shouldReceive('bubbleCancelled')
            ->atLeast()->once()
            ->andReturn(false);

        $this->_finishBuffer($buf);
        $smtp->start();
    }

    public function testStartingTransportDispatchesBeforeTransportChangeEvent()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $evt = $this->getMockery('Swift_Events_TransportChangeEvent');
        $smtp = $this->_getTransport($buf, $dispatcher);

        $dispatcher->shouldReceive('createTransportChangeEvent')
                   ->atLeast()->once()
                   ->with($smtp)
                   ->andReturn($evt);
        $dispatcher->shouldReceive('dispatchEvent')
                   ->once()
                   ->with($evt, 'beforeTransportStarted');
        $dispatcher->shouldReceive('dispatchEvent')
                   ->zeroOrMoreTimes();
        $evt->shouldReceive('bubbleCancelled')
            ->atLeast()->once()
            ->andReturn(false);

        $this->_finishBuffer($buf);
        $smtp->start();
    }

    public function testCancellingBubbleBeforeTransportStartStopsEvent()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $evt = $this->getMockery('Swift_Events_TransportChangeEvent');
        $smtp = $this->_getTransport($buf, $dispatcher);

        $dispatcher->shouldReceive('createTransportChangeEvent')
                   ->atLeast()->once()
                   ->with($smtp)
                   ->andReturn($evt);
        $dispatcher->shouldReceive('dispatchEvent')
                   ->once()
                   ->with($evt, 'beforeTransportStarted');
        $dispatcher->shouldReceive('dispatchEvent')
                   ->zeroOrMoreTimes();
        $evt->shouldReceive('bubbleCancelled')
            ->atLeast()->once()
            ->andReturn(true);

        $this->_finishBuffer($buf);
        $smtp->start();

        $this->assertFalse($smtp->isStarted(),
            '%s: Transport should not be started since event bubble was cancelled'
        );
    }

    public function testStoppingTransportDispatchesTransportChangeEvent()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $evt = $this->getMockery('Swift_Events_TransportChangeEvent')->shouldIgnoreMissing();
        $smtp = $this->_getTransport($buf, $dispatcher);

        $dispatcher->shouldReceive('createTransportChangeEvent')
                   ->atLeast()->once()
                   ->with($smtp)
                   ->andReturn($evt);
        $dispatcher->shouldReceive('dispatchEvent')
                   ->once()
                   ->with($evt, 'transportStopped');
        $dispatcher->shouldReceive('dispatchEvent')
                   ->zeroOrMoreTimes();

        $this->_finishBuffer($buf);
        $smtp->start();
        $smtp->stop();
    }

    public function testStoppingTransportDispatchesBeforeTransportChangeEvent()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $evt = $this->getMockery('Swift_Events_TransportChangeEvent')->shouldIgnoreMissing();
        $smtp = $this->_getTransport($buf, $dispatcher);

        $dispatcher->shouldReceive('createTransportChangeEvent')
                   ->atLeast()->once()
                   ->with($smtp)
                   ->andReturn($evt);
        $dispatcher->shouldReceive('dispatchEvent')
                   ->once()
                   ->with($evt, 'beforeTransportStopped');
        $dispatcher->shouldReceive('dispatchEvent')
                   ->zeroOrMoreTimes();

        $this->_finishBuffer($buf);
        $smtp->start();
        $smtp->stop();
    }

    public function testCancellingBubbleBeforeTransportStoppedStopsEvent()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $evt = $this->getMockery('Swift_Events_TransportChangeEvent');
        $smtp = $this->_getTransport($buf, $dispatcher);

        $hasRun = false;
        $dispatcher->shouldReceive('createTransportChangeEvent')
                   ->atLeast()->once()
                   ->with($smtp)
                   ->andReturn($evt);
        $dispatcher->shouldReceive('dispatchEvent')
                   ->once()
                   ->with($evt, 'beforeTransportStopped')
                   ->andReturnUsing(function () use (&$hasRun) {
                       $hasRun = true;
                   });
        $dispatcher->shouldReceive('dispatchEvent')
                   ->zeroOrMoreTimes();
        $evt->shouldReceive('bubbleCancelled')
            ->zeroOrMoreTimes()
            ->andReturnUsing(function () use (&$hasRun) {
                return $hasRun;
            });

        $this->_finishBuffer($buf);
        $smtp->start();
        $smtp->stop();

        $this->assertTrue($smtp->isStarted(),
            '%s: Transport should not be stopped since event bubble was cancelled'
        );
    }

    public function testResponseEventsAreGenerated()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $evt = $this->getMockery('Swift_Events_ResponseEvent');
        $smtp = $this->_getTransport($buf, $dispatcher);

        $dispatcher->shouldReceive('createResponseEvent')
                   ->atLeast()->once()
                   ->with($smtp, \Mockery::any(), \Mockery::any())
                   ->andReturn($evt);
        $dispatcher->shouldReceive('dispatchEvent')
                   ->atLeast()->once()
                   ->with($evt, 'responseReceived');

        $this->_finishBuffer($buf);
        $smtp->start();
    }

    public function testCommandEventsAreGenerated()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $evt = $this->getMockery('Swift_Events_CommandEvent');
        $smtp = $this->_getTransport($buf, $dispatcher);

        $dispatcher->shouldReceive('createCommandEvent')
                   ->once()
                   ->with($smtp, \Mockery::any(), \Mockery::any())
                   ->andReturn($evt);
        $dispatcher->shouldReceive('dispatchEvent')
                   ->once()
                   ->with($evt, 'commandSent');

        $this->_finishBuffer($buf);
        $smtp->start();
    }

    public function testExceptionsCauseExceptionEvents()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $evt = $this->getMockery('Swift_Events_TransportExceptionEvent');
        $smtp = $this->_getTransport($buf, $dispatcher);

        $buf->shouldReceive('readLine')
            ->atLeast()->once()
            ->andReturn("503 I'm sleepy, go away!\r\n");
        $dispatcher->shouldReceive('createTransportExceptionEvent')
                   ->zeroOrMoreTimes()
                   ->with($smtp, \Mockery::any())
                   ->andReturn($evt);
        $dispatcher->shouldReceive('dispatchEvent')
                   ->once()
                   ->with($evt, 'exceptionThrown');
        $evt->shouldReceive('bubbleCancelled')
            ->atLeast()->once()
            ->andReturn(false);

        try {
            $smtp->start();
            $this->fail('TransportException should be thrown on invalid response');
        } catch (Swift_TransportException $e) {
        }
    }

    public function testExceptionBubblesCanBeCancelled()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher(false);
        $evt = $this->getMockery('Swift_Events_TransportExceptionEvent');
        $smtp = $this->_getTransport($buf, $dispatcher);

        $buf->shouldReceive('readLine')
            ->atLeast()->once()
            ->andReturn("503 I'm sleepy, go away!\r\n");
        $dispatcher->shouldReceive('createTransportExceptionEvent')
                   ->twice()
                   ->with($smtp, \Mockery::any())
                   ->andReturn($evt);
        $dispatcher->shouldReceive('dispatchEvent')
                   ->twice()
                   ->with($evt, 'exceptionThrown');
        $evt->shouldReceive('bubbleCancelled')
            ->atLeast()->once()
            ->andReturn(true);

        $this->_finishBuffer($buf);
        $smtp->start();
    }

    // -- Creation Methods

    protected function _createEventDispatcher($stub = true)
    {
        return $this->getMockery('Swift_Events_EventDispatcher')->shouldIgnoreMissing();
    }
}
<?php

abstract class Swift_Transport_AbstractSmtpTest extends \SwiftMailerTestCase
{
    /** Abstract test method */
    abstract protected function _getTransport($buf);

    public function testStartAccepts220ServiceGreeting()
    {
        /* -- RFC 2821, 4.2.

     Greeting = "220 " Domain [ SP text ] CRLF

     -- RFC 2822, 4.3.2.

     CONNECTION ESTABLISHMENT
         S: 220
         E: 554
        */

        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $buf->shouldReceive('initialize')
            ->once();
        $buf->shouldReceive('readLine')
            ->once()
            ->with(0)
            ->andReturn("220 some.server.tld bleh\r\n");

        $this->_finishBuffer($buf);
        try {
            $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started');
            $smtp->start();
            $this->assertTrue($smtp->isStarted(), '%s: start() should have started connection');
        } catch (Exception $e) {
            $this->fail('220 is a valid SMTP greeting and should be accepted');
        }
    }

    public function testBadGreetingCausesException()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $buf->shouldReceive('initialize')
            ->once();
        $buf->shouldReceive('readLine')
            ->once()
            ->with(0)
            ->andReturn("554 I'm busy\r\n");
        $this->_finishBuffer($buf);
        try {
            $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started');
            $smtp->start();
            $this->fail('554 greeting indicates an error and should cause an exception');
        } catch (Exception $e) {
            $this->assertFalse($smtp->isStarted(), '%s: start() should have failed');
        }
    }

    public function testStartSendsHeloToInitiate()
    {
        /* -- RFC 2821, 3.2.

            3.2 Client Initiation

         Once the server has sent the welcoming message and the client has
         received it, the client normally sends the EHLO command to the
         server, indicating the client's identity.  In addition to opening the
         session, use of EHLO indicates that the client is able to process
         service extensions and requests that the server provide a list of the
         extensions it supports.  Older SMTP systems which are unable to
         support service extensions and contemporary clients which do not
         require service extensions in the mail session being initiated, MAY
         use HELO instead of EHLO.  Servers MUST NOT return the extended
         EHLO-style response to a HELO command.  For a particular connection
         attempt, if the server returns a "command not recognized" response to
         EHLO, the client SHOULD be able to fall back and send HELO.

         In the EHLO command the host sending the command identifies itself;
         the command may be interpreted as saying "Hello, I am <domain>" (and,
         in the case of EHLO, "and I support service extension requests").

       -- RFC 2281, 4.1.1.1.

       ehlo            = "EHLO" SP Domain CRLF
       helo            = "HELO" SP Domain CRLF

       -- RFC 2821, 4.3.2.

       EHLO or HELO
           S: 250
           E: 504, 550

     */

        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);

        $buf->shouldReceive('initialize')
            ->once();
        $buf->shouldReceive('readLine')
            ->once()
            ->with(0)
            ->andReturn("220 some.server.tld bleh\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with('~^HELO .*?\r\n$~D')
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('250 ServerName'."\r\n");

        $this->_finishBuffer($buf);
        try {
            $smtp->start();
        } catch (Exception $e) {
            $this->fail('Starting SMTP should send HELO and accept 250 response');
        }
    }

    public function testInvalidHeloResponseCausesException()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);

        $buf->shouldReceive('initialize')
            ->once();
        $buf->shouldReceive('readLine')
            ->once()
            ->with(0)
            ->andReturn("220 some.server.tld bleh\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with('~^HELO .*?\r\n$~D')
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('504 WTF'."\r\n");

        $this->_finishBuffer($buf);
        try {
            $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started');
            $smtp->start();
            $this->fail('Non 250 HELO response should raise Exception');
        } catch (Exception $e) {
            $this->assertFalse($smtp->isStarted(), '%s: SMTP start() should have failed');
        }
    }

    public function testDomainNameIsPlacedInHelo()
    {
        /* -- RFC 2821, 4.1.4.

       The SMTP client MUST, if possible, ensure that the domain parameter
       to the EHLO command is a valid principal host name (not a CNAME or MX
       name) for its host.  If this is not possible (e.g., when the client's
       address is dynamically assigned and the client does not have an
       obvious name), an address literal SHOULD be substituted for the
       domain name and supplemental information provided that will assist in
       identifying the client.
        */

        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);

        $buf->shouldReceive('initialize')
            ->once();
        $buf->shouldReceive('readLine')
            ->once()
            ->with(0)
            ->andReturn("220 some.server.tld bleh\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("HELO mydomain.com\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('250 ServerName'."\r\n");

        $this->_finishBuffer($buf);
        $smtp->setLocalDomain('mydomain.com');
        $smtp->start();
    }

    public function testSuccessfulMailCommand()
    {
        /* -- RFC 2821, 3.3.

        There are three steps to SMTP mail transactions.  The transaction
        starts with a MAIL command which gives the sender identification.

        .....

        The first step in the procedure is the MAIL command.

            MAIL FROM:<reverse-path> [SP <mail-parameters> ] <CRLF>

        -- RFC 2821, 4.1.1.2.

        Syntax:

            "MAIL FROM:" ("<>" / Reverse-Path)
                       [SP Mail-parameters] CRLF
        -- RFC 2821, 4.1.2.

        Reverse-path = Path
            Forward-path = Path
            Path = "<" [ A-d-l ":" ] Mailbox ">"
            A-d-l = At-domain *( "," A-d-l )
                        ; Note that this form, the so-called "source route",
                        ; MUST BE accepted, SHOULD NOT be generated, and SHOULD be
                        ; ignored.
            At-domain = "@" domain

        -- RFC 2821, 4.3.2.

        MAIL
            S: 250
            E: 552, 451, 452, 550, 553, 503
        */

        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();
        $message->shouldReceive('getFrom')
                ->once()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getTo')
                ->once()
                ->andReturn(array('foo@bar' => null));
        $buf->shouldReceive('initialize')
            ->once();
        $buf->shouldReceive('write')
            ->once()
            ->with("MAIL FROM:<me@domain.com>\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250 OK\r\n");

        $this->_finishBuffer($buf);
        try {
            $smtp->start();
            $smtp->send($message);
        } catch (Exception $e) {
            $this->fail('MAIL FROM should accept a 250 response');
        }
    }

    public function testInvalidResponseCodeFromMailCausesException()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->once()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getTo')
                ->once()
                ->andReturn(array('foo@bar' => null));
        $buf->shouldReceive('write')
            ->once()
            ->with("MAIL FROM:<me@domain.com>\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('553 Bad'."\r\n");

        $this->_finishBuffer($buf);
        try {
            $smtp->start();
            $smtp->send($message);
            $this->fail('MAIL FROM should accept a 250 response');
        } catch (Exception $e) {
        }
    }

    public function testSenderIsPreferredOverFrom()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->once()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getSender')
                ->once()
                ->andReturn(array('another@domain.com' => 'Someone'));
        $message->shouldReceive('getTo')
                ->once()
                ->andReturn(array('foo@bar' => null));
        $buf->shouldReceive('write')
            ->once()
            ->with("MAIL FROM:<another@domain.com>\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('250 OK'."\r\n");

        $this->_finishBuffer($buf);
        $smtp->start();
        $smtp->send($message);
    }

    public function testReturnPathIsPreferredOverSender()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->once()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getSender')
                ->once()
                ->andReturn(array('another@domain.com' => 'Someone'));
        $message->shouldReceive('getReturnPath')
                ->once()
                ->andReturn('more@domain.com');
        $message->shouldReceive('getTo')
                ->once()
                ->andReturn(array('foo@bar' => null));
        $buf->shouldReceive('write')
            ->once()
            ->with("MAIL FROM:<more@domain.com>\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('250 OK'."\r\n");

        $this->_finishBuffer($buf);
        $smtp->start();
        $smtp->send($message);
    }

    public function testSuccessfulRcptCommandWith250Response()
    {
        /* -- RFC 2821, 3.3.

     The second step in the procedure is the RCPT command.

            RCPT TO:<forward-path> [ SP <rcpt-parameters> ] <CRLF>

     The first or only argument to this command includes a forward-path
     (normally a mailbox and domain, always surrounded by "<" and ">"
     brackets) identifying one recipient.  If accepted, the SMTP server
     returns a 250 OK reply and stores the forward-path.  If the recipient
     is known not to be a deliverable address, the SMTP server returns a
     550 reply, typically with a string such as "no such user - " and the
     mailbox name (other circumstances and reply codes are possible).
     This step of the procedure can be repeated any number of times.

        -- RFC 2821, 4.1.1.3.

        This command is used to identify an individual recipient of the mail
        data; multiple recipients are specified by multiple use of this
        command.  The argument field contains a forward-path and may contain
        optional parameters.

        The forward-path normally consists of the required destination
        mailbox.  Sending systems SHOULD not generate the optional list of
        hosts known as a source route.

        .......

        "RCPT TO:" ("<Postmaster@" domain ">" / "<Postmaster>" / Forward-Path)
                                        [SP Rcpt-parameters] CRLF

        -- RFC 2821, 4.2.2.

            250 Requested mail action okay, completed
            251 User not local; will forward to <forward-path>
         (See section 3.4)
            252 Cannot VRFY user, but will accept message and attempt
                    delivery

        -- RFC 2821, 4.3.2.

        RCPT
            S: 250, 251 (but see section 3.4 for discussion of 251 and 551)
            E: 550, 551, 552, 553, 450, 451, 452, 503, 550
        */

        //We'll treat 252 as accepted since it isn't really a failure

        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->once()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getTo')
                ->once()
                ->andReturn(array('foo@bar' => null));
        $buf->shouldReceive('write')
            ->once()
            ->with("MAIL FROM:<me@domain.com>\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('250 OK'."\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<foo@bar>\r\n")
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn('250 OK'."\r\n");

        $this->_finishBuffer($buf);
        try {
            $smtp->start();
            $smtp->send($message);
        } catch (Exception $e) {
            $this->fail('RCPT TO should accept a 250 response');
        }
    }

    public function testMailFromCommandIsOnlySentOncePerMessage()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->once()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getTo')
                ->once()
                ->andReturn(array('foo@bar' => null));
        $buf->shouldReceive('write')
            ->once()
            ->with("MAIL FROM:<me@domain.com>\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('250 OK'."\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<foo@bar>\r\n")
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn('250 OK'."\r\n");
        $buf->shouldReceive('write')
            ->never()
            ->with("MAIL FROM:<me@domain.com>\r\n");

        $this->_finishBuffer($buf);
        $smtp->start();
        $smtp->send($message);
    }

    public function testMultipleRecipientsSendsMultipleRcpt()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->once()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getTo')
                ->once()
                ->andReturn(array(
                    'foo@bar' => null,
                    'zip@button' => 'Zip Button',
                    'test@domain' => 'Test user',
                ));
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<foo@bar>\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('250 OK'."\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<zip@button>\r\n")
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn('250 OK'."\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<test@domain>\r\n")
            ->andReturn(3);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(3)
            ->andReturn('250 OK'."\r\n");

        $this->_finishBuffer($buf);
        $smtp->start();
        $smtp->send($message);
    }

    public function testCcRecipientsSendsMultipleRcpt()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->once()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getTo')
                ->once()
                ->andReturn(array('foo@bar' => null));
        $message->shouldReceive('getCc')
                ->once()
                ->andReturn(array(
                    'zip@button' => 'Zip Button',
                    'test@domain' => 'Test user',
                ));
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<foo@bar>\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('250 OK'."\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<zip@button>\r\n")
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn('250 OK'."\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<test@domain>\r\n")
            ->andReturn(3);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(3)
            ->andReturn('250 OK'."\r\n");

        $this->_finishBuffer($buf);
        $smtp->start();
        $smtp->send($message);
    }

    public function testSendReturnsNumberOfSuccessfulRecipients()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->once()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getTo')
                ->once()
                ->andReturn(array('foo@bar' => null));
        $message->shouldReceive('getCc')
                ->once()
                ->andReturn(array(
                    'zip@button' => 'Zip Button',
                    'test@domain' => 'Test user',
                ));
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<foo@bar>\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('250 OK'."\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<zip@button>\r\n")
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn('501 Nobody here'."\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<test@domain>\r\n")
            ->andReturn(3);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(3)
            ->andReturn('250 OK'."\r\n");

        $this->_finishBuffer($buf);
        $smtp->start();
        $this->assertEquals(2, $smtp->send($message),
            '%s: 1 of 3 recipients failed so 2 should be returned'
            );
    }

    public function testRsetIsSentIfNoSuccessfulRecipients()
    {
        /* --RFC 2821, 4.1.1.5.

        This command specifies that the current mail transaction will be
        aborted.  Any stored sender, recipients, and mail data MUST be
        discarded, and all buffers and state tables cleared.  The receiver
        MUST send a "250 OK" reply to a RSET command with no arguments.  A
        reset command may be issued by the client at any time.

        -- RFC 2821, 4.3.2.

        RSET
            S: 250
        */

        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->once()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getTo')
                ->once()
                ->andReturn(array('foo@bar' => null));
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<foo@bar>\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('503 Bad'."\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("RSET\r\n")
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn('250 OK'."\r\n");

        $this->_finishBuffer($buf);
        $smtp->start();
        $this->assertEquals(0, $smtp->send($message),
            '%s: 1 of 1 recipients failed so 0 should be returned'
            );
    }

    public function testSuccessfulDataCommand()
    {
        /* -- RFC 2821, 3.3.

        The third step in the procedure is the DATA command (or some
        alternative specified in a service extension).

                    DATA <CRLF>

        If accepted, the SMTP server returns a 354 Intermediate reply and
        considers all succeeding lines up to but not including the end of
        mail data indicator to be the message text.

        -- RFC 2821, 4.1.1.4.

        The receiver normally sends a 354 response to DATA, and then treats
        the lines (strings ending in <CRLF> sequences, as described in
        section 2.3.7) following the command as mail data from the sender.
        This command causes the mail data to be appended to the mail data
        buffer.  The mail data may contain any of the 128 ASCII character
        codes, although experience has indicated that use of control
        characters other than SP, HT, CR, and LF may cause problems and
        SHOULD be avoided when possible.

        -- RFC 2821, 4.3.2.

        DATA
            I: 354 -> data -> S: 250
                                                E: 552, 554, 451, 452
            E: 451, 554, 503
        */

        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->once()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getTo')
                ->once()
                ->andReturn(array('foo@bar' => null));
        $buf->shouldReceive('write')
            ->once()
            ->with("DATA\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('354 Go ahead'."\r\n");

        $this->_finishBuffer($buf);
        try {
            $smtp->start();
            $smtp->send($message);
        } catch (Exception $e) {
            $this->fail('354 is the expected response to DATA');
        }
    }

    public function testBadDataResponseCausesException()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->once()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getTo')
                ->once()
                ->andReturn(array('foo@bar' => null));
        $buf->shouldReceive('write')
            ->once()
            ->with("DATA\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('451 Bad'."\r\n");

        $this->_finishBuffer($buf);
        try {
            $smtp->start();
            $smtp->send($message);
            $this->fail('354 is the expected response to DATA (not observed)');
        } catch (Exception $e) {
        }
    }

    public function testMessageIsStreamedToBufferForData()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->once()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getTo')
                ->once()
                ->andReturn(array('foo@bar' => null));
        $buf->shouldReceive('write')
            ->once()
            ->with("DATA\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('354 OK'."\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("\r\n.\r\n")
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn('250 OK'."\r\n");

        $this->_finishBuffer($buf);
        $smtp->start();
        $smtp->send($message);
    }

    public function testBadResponseAfterDataTransmissionCausesException()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->once()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getTo')
                ->once()
                ->andReturn(array('foo@bar' => null));
        $buf->shouldReceive('write')
            ->once()
            ->with("DATA\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('354 OK'."\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("\r\n.\r\n")
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn('554 Error'."\r\n");

        $this->_finishBuffer($buf);
        try {
            $smtp->start();
            $smtp->send($message);
            $this->fail('250 is the expected response after a DATA transmission (not observed)');
        } catch (Exception $e) {
        }
    }

    public function testBccRecipientsAreRemovedFromHeaders()
    {
        /* -- RFC 2821, 7.2.

     Addresses that do not appear in the message headers may appear in the
     RCPT commands to an SMTP server for a number of reasons.  The two
     most common involve the use of a mailing address as a "list exploder"
     (a single address that resolves into multiple addresses) and the
     appearance of "blind copies".  Especially when more than one RCPT
     command is present, and in order to avoid defeating some of the
     purpose of these mechanisms, SMTP clients and servers SHOULD NOT copy
     the full set of RCPT command arguments into the headers, either as
     part of trace headers or as informational or private-extension
     headers.  Since this rule is often violated in practice, and cannot
     be enforced, sending SMTP systems that are aware of "bcc" use MAY
     find it helpful to send each blind copy as a separate message
     transaction containing only a single RCPT command.
     */

        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();
        $message->shouldReceive('getFrom')
                ->zeroOrMoreTimes()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('foo@bar' => null));
        $message->shouldReceive('getBcc')
                ->zeroOrMoreTimes()
                ->andReturn(array(
                    'zip@button' => 'Zip Button',
                    'test@domain' => 'Test user',
                ));
        $message->shouldReceive('setBcc')
                ->once()
                ->with(array());
        $message->shouldReceive('setBcc')
                ->zeroOrMoreTimes();

        $this->_finishBuffer($buf);
        $smtp->start();
        $smtp->send($message);
    }

    public function testEachBccRecipientIsSentASeparateMessage()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->zeroOrMoreTimes()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('foo@bar' => null));
        $message->shouldReceive('getBcc')
                ->zeroOrMoreTimes()
                ->andReturn(array(
                    'zip@button' => 'Zip Button',
                    'test@domain' => 'Test user',
                ));
        $message->shouldReceive('setBcc')
                ->atLeast()->once()
                ->with(array());
        $message->shouldReceive('setBcc')
                ->once()
                ->with(array('zip@button' => 'Zip Button'));
        $message->shouldReceive('setBcc')
                ->once()
                ->with(array('test@domain' => 'Test user'));
        $message->shouldReceive('setBcc')
                ->atLeast()->once()
                ->with(array(
                    'zip@button' => 'Zip Button',
                    'test@domain' => 'Test user',
                ));

        $buf->shouldReceive('write')->once()->with("MAIL FROM:<me@domain.com>\r\n")->andReturn(1);
        $buf->shouldReceive('readLine')->once()->with(1)->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')->once()->with("RCPT TO:<foo@bar>\r\n")->andReturn(2);
        $buf->shouldReceive('readLine')->once()->with(2)->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')->once()->with("DATA\r\n")->andReturn(3);
        $buf->shouldReceive('readLine')->once()->with(3)->andReturn("354 OK\r\n");
        $buf->shouldReceive('write')->once()->with("\r\n.\r\n")->andReturn(4);
        $buf->shouldReceive('readLine')->once()->with(4)->andReturn("250 OK\r\n");

        $buf->shouldReceive('write')->once()->with("MAIL FROM:<me@domain.com>\r\n")->andReturn(5);
        $buf->shouldReceive('readLine')->once()->with(5)->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')->once()->with("RCPT TO:<zip@button>\r\n")->andReturn(6);
        $buf->shouldReceive('readLine')->once()->with(6)->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')->once()->with("DATA\r\n")->andReturn(7);
        $buf->shouldReceive('readLine')->once()->with(7)->andReturn("354 OK\r\n");
        $buf->shouldReceive('write')->once()->with("\r\n.\r\n")->andReturn(8);
        $buf->shouldReceive('readLine')->once()->with(8)->andReturn("250 OK\r\n");

        $buf->shouldReceive('write')->once()->with("MAIL FROM:<me@domain.com>\r\n")->andReturn(9);
        $buf->shouldReceive('readLine')->once()->with(9)->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')->once()->with("RCPT TO:<test@domain>\r\n")->andReturn(10);
        $buf->shouldReceive('readLine')->once()->with(10)->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')->once()->with("DATA\r\n")->andReturn(11);
        $buf->shouldReceive('readLine')->once()->with(11)->andReturn("354 OK\r\n");
        $buf->shouldReceive('write')->once()->with("\r\n.\r\n")->andReturn(12);
        $buf->shouldReceive('readLine')->once()->with(12)->andReturn("250 OK\r\n");

        $this->_finishBuffer($buf);
        $smtp->start();
        $this->assertEquals(3, $smtp->send($message));
    }

    public function testMessageStateIsRestoredOnFailure()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->zeroOrMoreTimes()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('foo@bar' => null));
        $message->shouldReceive('getBcc')
                ->zeroOrMoreTimes()
                ->andReturn(array(
                    'zip@button' => 'Zip Button',
                    'test@domain' => 'Test user',
                ));
        $message->shouldReceive('setBcc')
                ->once()
                ->with(array());
        $message->shouldReceive('setBcc')
                ->once()
                ->with(array(
                    'zip@button' => 'Zip Button',
                    'test@domain' => 'Test user',
                ));
        $buf->shouldReceive('write')
            ->once()
            ->with("MAIL FROM:<me@domain.com>\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<foo@bar>\r\n")
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("DATA\r\n")
            ->andReturn(3);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(3)
            ->andReturn("451 No\r\n");

        $this->_finishBuffer($buf);

        $smtp->start();
        try {
            $smtp->send($message);
            $this->fail('A bad response was given so exception is expected');
        } catch (Exception $e) {
        }
    }

    public function testStopSendsQuitCommand()
    {
        /* -- RFC 2821, 4.1.1.10.

        This command specifies that the receiver MUST send an OK reply, and
        then close the transmission channel.

        The receiver MUST NOT intentionally close the transmission channel
        until it receives and replies to a QUIT command (even if there was an
        error).  The sender MUST NOT intentionally close the transmission
        channel until it sends a QUIT command and SHOULD wait until it
        receives the reply (even if there was an error response to a previous
        command).  If the connection is closed prematurely due to violations
        of the above or system or network failure, the server MUST cancel any
        pending transaction, but not undo any previously completed
        transaction, and generally MUST act as if the command or transaction
        in progress had received a temporary error (i.e., a 4yz response).

        The QUIT command may be issued at any time.

        Syntax:
            "QUIT" CRLF
        */

        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();
        $buf->shouldReceive('initialize')
            ->once();
        $buf->shouldReceive('write')
            ->once()
            ->with("QUIT\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("221 Bye\r\n");
        $buf->shouldReceive('terminate')
            ->once();

        $this->_finishBuffer($buf);

        $this->assertFalse($smtp->isStarted());
        $smtp->start();
        $this->assertTrue($smtp->isStarted());
        $smtp->stop();
        $this->assertFalse($smtp->isStarted());
    }

    public function testBufferCanBeFetched()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $ref = $smtp->getBuffer();
        $this->assertEquals($buf, $ref);
    }

    public function testBufferCanBeWrittenToUsingExecuteCommand()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();
        $buf->shouldReceive('write')
            ->zeroOrMoreTimes()
            ->with("FOO\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->zeroOrMoreTimes()
            ->with(1)
            ->andReturn("250 OK\r\n");

        $res = $smtp->executeCommand("FOO\r\n");
        $this->assertEquals("250 OK\r\n", $res);
    }

    public function testResponseCodesAreValidated()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();
        $buf->shouldReceive('write')
            ->zeroOrMoreTimes()
            ->with("FOO\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->zeroOrMoreTimes()
            ->with(1)
            ->andReturn("551 Not ok\r\n");

        try {
            $smtp->executeCommand("FOO\r\n", array(250, 251));
            $this->fail('A 250 or 251 response was needed but 551 was returned.');
        } catch (Exception $e) {
        }
    }

    public function testFailedRecipientsCanBeCollectedByReference()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->zeroOrMoreTimes()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('foo@bar' => null));
        $message->shouldReceive('getBcc')
                ->zeroOrMoreTimes()
                ->andReturn(array(
                    'zip@button' => 'Zip Button',
                    'test@domain' => 'Test user',
                ));
        $message->shouldReceive('setBcc')
                ->atLeast()->once()
                ->with(array());
        $message->shouldReceive('setBcc')
                ->once()
                ->with(array('zip@button' => 'Zip Button'));
        $message->shouldReceive('setBcc')
                ->once()
                ->with(array('test@domain' => 'Test user'));
        $message->shouldReceive('setBcc')
                ->atLeast()->once()
                ->with(array(
                    'zip@button' => 'Zip Button',
                    'test@domain' => 'Test user',
                ));

        $buf->shouldReceive('write')->once()->with("MAIL FROM:<me@domain.com>\r\n")->andReturn(1);
        $buf->shouldReceive('readLine')->once()->with(1)->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')->once()->with("RCPT TO:<foo@bar>\r\n")->andReturn(2);
        $buf->shouldReceive('readLine')->once()->with(2)->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')->once()->with("DATA\r\n")->andReturn(3);
        $buf->shouldReceive('readLine')->once()->with(3)->andReturn("354 OK\r\n");
        $buf->shouldReceive('write')->once()->with("\r\n.\r\n")->andReturn(4);
        $buf->shouldReceive('readLine')->once()->with(4)->andReturn("250 OK\r\n");

        $buf->shouldReceive('write')->once()->with("MAIL FROM:<me@domain.com>\r\n")->andReturn(5);
        $buf->shouldReceive('readLine')->once()->with(5)->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')->once()->with("RCPT TO:<zip@button>\r\n")->andReturn(6);
        $buf->shouldReceive('readLine')->once()->with(6)->andReturn("500 Bad\r\n");
        $buf->shouldReceive('write')->once()->with("RSET\r\n")->andReturn(7);
        $buf->shouldReceive('readLine')->once()->with(7)->andReturn("250 OK\r\n");

        $buf->shouldReceive('write')->once()->with("MAIL FROM:<me@domain.com>\r\n")->andReturn(9);
        $buf->shouldReceive('readLine')->once()->with(9)->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')->once()->with("RCPT TO:<test@domain>\r\n")->andReturn(10);
        $buf->shouldReceive('readLine')->once()->with(10)->andReturn("500 Bad\r\n");
        $buf->shouldReceive('write')->once()->with("RSET\r\n")->andReturn(11);
        $buf->shouldReceive('readLine')->once()->with(11)->andReturn("250 OK\r\n");

        $this->_finishBuffer($buf);
        $smtp->start();
        $this->assertEquals(1, $smtp->send($message, $failures));
        $this->assertEquals(array('zip@button', 'test@domain'), $failures,
            '%s: Failures should be caught in an array'
            );
    }

    public function testSendingRegeneratesMessageId()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $message = $this->_createMessage();
        $message->shouldReceive('getFrom')
                ->zeroOrMoreTimes()
                ->andReturn(array('me@domain.com' => 'Me'));
        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('foo@bar' => null));
        $message->shouldReceive('generateId')
                ->once();

        $this->_finishBuffer($buf);
        $smtp->start();
        $smtp->send($message);
    }

    protected function _getBuffer()
    {
        return $this->getMockery('Swift_Transport_IoBuffer')->shouldIgnoreMissing();
    }

    protected function _createMessage()
    {
        return $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing();
    }

    protected function _finishBuffer($buf)
    {
        $buf->shouldReceive('readLine')
            ->zeroOrMoreTimes()
            ->with(0)
            ->andReturn('220 server.com foo'."\r\n");
        $buf->shouldReceive('write')
            ->zeroOrMoreTimes()
            ->with('~^(EH|HE)LO .*?\r\n$~D')
            ->andReturn($x = uniqid());
        $buf->shouldReceive('readLine')
            ->zeroOrMoreTimes()
            ->with($x)
            ->andReturn('250 ServerName'."\r\n");
        $buf->shouldReceive('write')
            ->zeroOrMoreTimes()
            ->with('~^MAIL FROM:<.*?>\r\n$~D')
            ->andReturn($x = uniqid());
        $buf->shouldReceive('readLine')
            ->zeroOrMoreTimes()
            ->with($x)
            ->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')
            ->zeroOrMoreTimes()
            ->with('~^RCPT TO:<.*?>\r\n$~D')
            ->andReturn($x = uniqid());
        $buf->shouldReceive('readLine')
            ->zeroOrMoreTimes()
            ->with($x)
            ->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')
            ->zeroOrMoreTimes()
            ->with("DATA\r\n")
            ->andReturn($x = uniqid());
        $buf->shouldReceive('readLine')
            ->zeroOrMoreTimes()
            ->with($x)
            ->andReturn("354 OK\r\n");
        $buf->shouldReceive('write')
            ->zeroOrMoreTimes()
            ->with("\r\n.\r\n")
            ->andReturn($x = uniqid());
        $buf->shouldReceive('readLine')
            ->zeroOrMoreTimes()
            ->with($x)
            ->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')
            ->zeroOrMoreTimes()
            ->with("RSET\r\n")
            ->andReturn($x = uniqid());
        $buf->shouldReceive('readLine')
            ->zeroOrMoreTimes()
            ->with($x)
            ->andReturn("250 OK\r\n");

        $buf->shouldReceive('write')
            ->zeroOrMoreTimes()
            ->andReturn(false);
        $buf->shouldReceive('readLine')
            ->zeroOrMoreTimes()
            ->andReturn(false);
    }
}
<?php

class Swift_Transport_Esmtp_Auth_CramMd5AuthenticatorTest extends \SwiftMailerTestCase
{
    private $_agent;

    public function setUp()
    {
        $this->_agent = $this->getMockery('Swift_Transport_SmtpAgent')->shouldIgnoreMissing();
    }

    public function testKeywordIsCramMd5()
    {
        /* -- RFC 2195, 2.
        The authentication type associated with CRAM is "CRAM-MD5".
        */

        $cram = $this->_getAuthenticator();
        $this->assertEquals('CRAM-MD5', $cram->getAuthKeyword());
    }

    public function testSuccessfulAuthentication()
    {
        $cram = $this->_getAuthenticator();

        $this->_agent->shouldReceive('executeCommand')
             ->once()
             ->with("AUTH CRAM-MD5\r\n", array(334))
             ->andReturn('334 '.base64_encode('<foo@bar>')."\r\n");
        $this->_agent->shouldReceive('executeCommand')
             ->once()
             ->with(\Mockery::any(), array(235));

        $this->assertTrue($cram->authenticate($this->_agent, 'jack', 'pass'),
            '%s: The buffer accepted all commands authentication should succeed'
            );
    }

    public function testAuthenticationFailureSendRsetAndReturnFalse()
    {
        $cram = $this->_getAuthenticator();

        $this->_agent->shouldReceive('executeCommand')
             ->once()
             ->with("AUTH CRAM-MD5\r\n", array(334))
             ->andReturn('334 '.base64_encode('<foo@bar>')."\r\n");
        $this->_agent->shouldReceive('executeCommand')
             ->once()
             ->with(\Mockery::any(), array(235))
             ->andThrow(new Swift_TransportException(''));
        $this->_agent->shouldReceive('executeCommand')
             ->once()
             ->with("RSET\r\n", array(250));

        $this->assertFalse($cram->authenticate($this->_agent, 'jack', 'pass'),
            '%s: Authentication fails, so RSET should be sent'
            );
    }

    // -- Private helpers

    private function _getAuthenticator()
    {
        return new Swift_Transport_Esmtp_Auth_CramMd5Authenticator();
    }
}
<?php

class Swift_Transport_Esmtp_Auth_LoginAuthenticatorTest extends \SwiftMailerTestCase
{
    private $_agent;

    public function setUp()
    {
        $this->_agent = $this->getMockery('Swift_Transport_SmtpAgent')->shouldIgnoreMissing();
    }

    public function testKeywordIsLogin()
    {
        $login = $this->_getAuthenticator();
        $this->assertEquals('LOGIN', $login->getAuthKeyword());
    }

    public function testSuccessfulAuthentication()
    {
        $login = $this->_getAuthenticator();

        $this->_agent->shouldReceive('executeCommand')
             ->once()
             ->with("AUTH LOGIN\r\n", array(334));
        $this->_agent->shouldReceive('executeCommand')
             ->once()
             ->with(base64_encode('jack')."\r\n", array(334));
        $this->_agent->shouldReceive('executeCommand')
             ->once()
             ->with(base64_encode('pass')."\r\n", array(235));

        $this->assertTrue($login->authenticate($this->_agent, 'jack', 'pass'),
            '%s: The buffer accepted all commands authentication should succeed'
            );
    }

    public function testAuthenticationFailureSendRsetAndReturnFalse()
    {
        $login = $this->_getAuthenticator();

        $this->_agent->shouldReceive('executeCommand')
             ->once()
             ->with("AUTH LOGIN\r\n", array(334));
        $this->_agent->shouldReceive('executeCommand')
             ->once()
             ->with(base64_encode('jack')."\r\n", array(334));
        $this->_agent->shouldReceive('executeCommand')
             ->once()
             ->with(base64_encode('pass')."\r\n", array(235))
             ->andThrow(new Swift_TransportException(''));
        $this->_agent->shouldReceive('executeCommand')
             ->once()
             ->with("RSET\r\n", array(250));

        $this->assertFalse($login->authenticate($this->_agent, 'jack', 'pass'),
            '%s: Authentication fails, so RSET should be sent'
            );
    }

    // -- Private helpers

    private function _getAuthenticator()
    {
        return new Swift_Transport_Esmtp_Auth_LoginAuthenticator();
    }
}
<?php

class Swift_Transport_Esmtp_Auth_NTLMAuthenticatorTest extends \SwiftMailerTestCase
{
    private $_message1 = '4e544c4d535350000100000007020000';
    private $_message2 = '4e544c4d53535000020000000c000c003000000035828980514246973ea892c10000000000000000460046003c00000054004500530054004e00540002000c0054004500530054004e00540001000c004d0045004d0042004500520003001e006d0065006d006200650072002e0074006500730074002e0063006f006d0000000000';
    private $_message3 = '4e544c4d5353500003000000180018006000000076007600780000000c000c0040000000080008004c0000000c000c0054000000000000009a0000000102000054004500530054004e00540074006500730074004d0045004d00420045005200bf2e015119f6bdb3f6fdb768aa12d478f5ce3d2401c8f6e9caa4da8f25d5e840974ed8976d3ada46010100000000000030fa7e3c677bc301f5ce3d2401c8f6e90000000002000c0054004500530054004e00540001000c004d0045004d0042004500520003001e006d0065006d006200650072002e0074006500730074002e0063006f006d000000000000000000';

    public function setUp()
    {
        if (!function_exists('openssl_encrypt') || !function_exists('openssl_random_pseudo_bytes') || !function_exists('bcmul') || !function_exists('iconv')) {
            $this->markTestSkipped(
                'One of the required functions is not available.'
             );
        }
    }

    public function testKeywordIsNtlm()
    {
        $login = $this->_getAuthenticator();
        $this->assertEquals('NTLM', $login->getAuthKeyword());
    }

    public function testMessage1Generator()
    {
        $login = $this->_getAuthenticator();
        $message1 = $this->_invokePrivateMethod('createMessage1', $login);

        $this->assertEquals($this->_message1, bin2hex($message1),
            '%s: We send the smallest ntlm message which should never fail.'
        );
    }

    public function testLMv1Generator()
    {
        $password = 'test1234';
        $challenge = 'b019d38bad875c9d';
        $lmv1 = '1879f60127f8a877022132ec221bcbf3ca016a9f76095606';

        $login = $this->_getAuthenticator();
        $lmv1Result = $this->_invokePrivateMethod('createLMPassword', $login, array($password, $this->hex2bin($challenge)));

        $this->assertEquals($lmv1, bin2hex($lmv1Result),
            '%s: The keys should be the same cause we use the same values to generate them.'
        );
    }

    public function testLMv2Generator()
    {
        $username = 'user';
        $password = 'SecREt01';
        $domain = 'DOMAIN';
        $challenge = '0123456789abcdef';
        $lmv2 = 'd6e6152ea25d03b7c6ba6629c2d6aaf0ffffff0011223344';

        $login = $this->_getAuthenticator();
        $lmv2Result = $this->_invokePrivateMethod('createLMv2Password', $login, array($password, $username, $domain, $this->hex2bin($challenge), $this->hex2bin('ffffff0011223344')));

        $this->assertEquals($lmv2, bin2hex($lmv2Result),
            '%s: The keys should be the same cause we use the same values to generate them.'
        );
    }

    public function testMessage3v1Generator()
    {
        $username = 'test';
        $domain = 'TESTNT';
        $workstation = 'MEMBER';
        $lmResponse = '1879f60127f8a877022132ec221bcbf3ca016a9f76095606';
        $ntlmResponse = 'e6285df3287c5d194f84df1a94817c7282d09754b6f9e02a';
        $message3T = '4e544c4d5353500003000000180018006000000018001800780000000c000c0040000000080008004c0000000c000c0054000000000000009a0000000102000054004500530054004e00540074006500730074004d0045004d004200450052001879f60127f8a877022132ec221bcbf3ca016a9f76095606e6285df3287c5d194f84df1a94817c7282d09754b6f9e02a';

        $login = $this->_getAuthenticator();
        $message3 = $this->_invokePrivateMethod('createMessage3', $login, array($domain, $username, $workstation, $this->hex2bin($lmResponse), $this->hex2bin($ntlmResponse)));

        $this->assertEquals($message3T, bin2hex($message3),
            '%s: We send the same information as the example is created with so this should be the same'
        );
    }

    public function testMessage3v2Generator()
    {
        $username = 'test';
        $domain = 'TESTNT';
        $workstation = 'MEMBER';
        $lmResponse = 'bf2e015119f6bdb3f6fdb768aa12d478f5ce3d2401c8f6e9';
        $ntlmResponse = 'caa4da8f25d5e840974ed8976d3ada46010100000000000030fa7e3c677bc301f5ce3d2401c8f6e90000000002000c0054004500530054004e00540001000c004d0045004d0042004500520003001e006d0065006d006200650072002e0074006500730074002e0063006f006d000000000000000000';

        $login = $this->_getAuthenticator();
        $message3 = $this->_invokePrivateMethod('createMessage3', $login, array($domain, $username, $workstation, $this->hex2bin($lmResponse), $this->hex2bin($ntlmResponse)));

        $this->assertEquals($this->_message3, bin2hex($message3),
            '%s: We send the same information as the example is created with so this should be the same'
        );
    }

    public function testGetDomainAndUsername()
    {
        $username = "DOMAIN\user";

        $login = $this->_getAuthenticator();
        list($domain, $user) = $this->_invokePrivateMethod('getDomainAndUsername', $login, array($username));

        $this->assertEquals('DOMAIN', $domain,
            '%s: the fetched domain did not match'
        );
        $this->assertEquals('user', $user,
            '%s: the fetched user did not match'
        );
    }

    public function testGetDomainAndUsernameWithExtension()
    {
        $username = "domain.com\user";

        $login = $this->_getAuthenticator();
        list($domain, $user) = $this->_invokePrivateMethod('getDomainAndUsername', $login, array($username));

        $this->assertEquals('domain.com', $domain,
            '%s: the fetched domain did not match'
        );
        $this->assertEquals('user', $user,
            '%s: the fetched user did not match'
        );
    }

    public function testGetDomainAndUsernameWithAtSymbol()
    {
        $username = 'user@DOMAIN';

        $login = $this->_getAuthenticator();
        list($domain, $user) = $this->_invokePrivateMethod('getDomainAndUsername', $login, array($username));

        $this->assertEquals('DOMAIN', $domain,
            '%s: the fetched domain did not match'
        );
        $this->assertEquals('user', $user,
            '%s: the fetched user did not match'
        );
    }

    public function testGetDomainAndUsernameWithAtSymbolAndExtension()
    {
        $username = 'user@domain.com';

        $login = $this->_getAuthenticator();
        list($domain, $user) = $this->_invokePrivateMethod('getDomainAndUsername', $login, array($username));

        $this->assertEquals('domain.com', $domain,
            '%s: the fetched domain did not match'
        );
        $this->assertEquals('user', $user,
            '%s: the fetched user did not match'
        );
    }

    public function testSuccessfulAuthentication()
    {
        $domain = 'TESTNT';
        $username = 'test';
        $secret = 'test1234';

        $ntlm = $this->_getAuthenticator();
        $agent = $this->_getAgent();
        $agent->shouldReceive('executeCommand')
              ->once()
              ->with('AUTH NTLM '.base64_encode(
                        $this->_invokePrivateMethod('createMessage1', $ntlm)
                    )."\r\n", array(334))
              ->andReturn('334 '.base64_encode($this->hex2bin('4e544c4d53535000020000000c000c003000000035828980514246973ea892c10000000000000000460046003c00000054004500530054004e00540002000c0054004500530054004e00540001000c004d0045004d0042004500520003001e006d0065006d006200650072002e0074006500730074002e0063006f006d0000000000')));
        $agent->shouldReceive('executeCommand')
              ->once()
              ->with(base64_encode(
                        $this->_invokePrivateMethod('createMessage3', $ntlm, array($domain, $username, $this->hex2bin('4d0045004d00420045005200'), $this->hex2bin('bf2e015119f6bdb3f6fdb768aa12d478f5ce3d2401c8f6e9'), $this->hex2bin('caa4da8f25d5e840974ed8976d3ada46010100000000000030fa7e3c677bc301f5ce3d2401c8f6e90000000002000c0054004500530054004e00540001000c004d0045004d0042004500520003001e006d0065006d006200650072002e0074006500730074002e0063006f006d000000000000000000'))
                    ))."\r\n", array(235));

        $this->assertTrue($ntlm->authenticate($agent, $username.'@'.$domain, $secret, $this->hex2bin('30fa7e3c677bc301'), $this->hex2bin('f5ce3d2401c8f6e9')),
            '%s: The buffer accepted all commands authentication should succeed'
        );
    }

    public function testAuthenticationFailureSendRsetAndReturnFalse()
    {
        $domain = 'TESTNT';
        $username = 'test';
        $secret = 'test1234';

        $ntlm = $this->_getAuthenticator();
        $agent = $this->_getAgent();
        $agent->shouldReceive('executeCommand')
              ->once()
              ->with('AUTH NTLM '.base64_encode(
                        $this->_invokePrivateMethod('createMessage1', $ntlm)
                    )."\r\n", array(334))
              ->andThrow(new Swift_TransportException(''));
        $agent->shouldReceive('executeCommand')
              ->once()
              ->with("RSET\r\n", array(250));

        $this->assertFalse($ntlm->authenticate($agent, $username.'@'.$domain, $secret, $this->hex2bin('30fa7e3c677bc301'), $this->hex2bin('f5ce3d2401c8f6e9')),
            '%s: Authentication fails, so RSET should be sent'
        );
    }

    // -- Private helpers
    private function _getAuthenticator()
    {
        return new Swift_Transport_Esmtp_Auth_NTLMAuthenticator();
    }

    private function _getAgent()
    {
        return $this->getMockery('Swift_Transport_SmtpAgent')->shouldIgnoreMissing();
    }

    private function _invokePrivateMethod($method, $instance, array $args = array())
    {
        $methodC = new ReflectionMethod($instance, trim($method));
        $methodC->setAccessible(true);

        return $methodC->invokeArgs($instance, $args);
    }

    /**
     * Hex2bin replacement for < PHP 5.4.
     *
     * @param string $hex
     *
     * @return string Binary
     */
    protected function hex2bin($hex)
    {
        return function_exists('hex2bin') ? hex2bin($hex) : pack('H*', $hex);
    }
}
<?php

class Swift_Transport_Esmtp_Auth_PlainAuthenticatorTest extends \SwiftMailerTestCase
{
    private $_agent;

    public function setUp()
    {
        $this->_agent = $this->getMockery('Swift_Transport_SmtpAgent')->shouldIgnoreMissing();
    }

    public function testKeywordIsPlain()
    {
        /* -- RFC 4616, 1.
        The name associated with this mechanism is "PLAIN".
        */

        $login = $this->_getAuthenticator();
        $this->assertEquals('PLAIN', $login->getAuthKeyword());
    }

    public function testSuccessfulAuthentication()
    {
        /* -- RFC 4616, 2.
        The client presents the authorization identity (identity to act as),
        followed by a NUL (U+0000) character, followed by the authentication
        identity (identity whose password will be used), followed by a NUL
        (U+0000) character, followed by the clear-text password.
        */

        $plain = $this->_getAuthenticator();

        $this->_agent->shouldReceive('executeCommand')
             ->once()
             ->with('AUTH PLAIN '.base64_encode(
                        'jack'.chr(0).'jack'.chr(0).'pass'
                    )."\r\n", array(235));

        $this->assertTrue($plain->authenticate($this->_agent, 'jack', 'pass'),
            '%s: The buffer accepted all commands authentication should succeed'
            );
    }

    public function testAuthenticationFailureSendRsetAndReturnFalse()
    {
        $plain = $this->_getAuthenticator();

        $this->_agent->shouldReceive('executeCommand')
             ->once()
             ->with('AUTH PLAIN '.base64_encode(
                        'jack'.chr(0).'jack'.chr(0).'pass'
                    )."\r\n", array(235))
             ->andThrow(new Swift_TransportException(''));
        $this->_agent->shouldReceive('executeCommand')
             ->once()
             ->with("RSET\r\n", array(250));

        $this->assertFalse($plain->authenticate($this->_agent, 'jack', 'pass'),
            '%s: Authentication fails, so RSET should be sent'
            );
    }

    // -- Private helpers

    private function _getAuthenticator()
    {
        return new Swift_Transport_Esmtp_Auth_PlainAuthenticator();
    }
}
<?php

class Swift_Transport_Esmtp_AuthHandlerTest extends \SwiftMailerTestCase
{
    private $_agent;

    public function setUp()
    {
        $this->_agent = $this->getMockery('Swift_Transport_SmtpAgent')->shouldIgnoreMissing();
    }

    public function testKeywordIsAuth()
    {
        $auth = $this->_createHandler(array());
        $this->assertEquals('AUTH', $auth->getHandledKeyword());
    }

    public function testUsernameCanBeSetAndFetched()
    {
        $auth = $this->_createHandler(array());
        $auth->setUsername('jack');
        $this->assertEquals('jack', $auth->getUsername());
    }

    public function testPasswordCanBeSetAndFetched()
    {
        $auth = $this->_createHandler(array());
        $auth->setPassword('pass');
        $this->assertEquals('pass', $auth->getPassword());
    }

    public function testAuthModeCanBeSetAndFetched()
    {
        $auth = $this->_createHandler(array());
        $auth->setAuthMode('PLAIN');
        $this->assertEquals('PLAIN', $auth->getAuthMode());
    }

    public function testMixinMethods()
    {
        $auth = $this->_createHandler(array());
        $mixins = $auth->exposeMixinMethods();
        $this->assertTrue(in_array('getUsername', $mixins),
            '%s: getUsername() should be accessible via mixin'
            );
        $this->assertTrue(in_array('setUsername', $mixins),
            '%s: setUsername() should be accessible via mixin'
            );
        $this->assertTrue(in_array('getPassword', $mixins),
            '%s: getPassword() should be accessible via mixin'
            );
        $this->assertTrue(in_array('setPassword', $mixins),
            '%s: setPassword() should be accessible via mixin'
            );
        $this->assertTrue(in_array('setAuthMode', $mixins),
            '%s: setAuthMode() should be accessible via mixin'
            );
        $this->assertTrue(in_array('getAuthMode', $mixins),
            '%s: getAuthMode() should be accessible via mixin'
            );
    }

    public function testAuthenticatorsAreCalledAccordingToParamsAfterEhlo()
    {
        $a1 = $this->_createMockAuthenticator('PLAIN');
        $a2 = $this->_createMockAuthenticator('LOGIN');

        $a1->shouldReceive('authenticate')
           ->never()
           ->with($this->_agent, 'jack', 'pass');
        $a2->shouldReceive('authenticate')
           ->once()
           ->with($this->_agent, 'jack', 'pass')
           ->andReturn(true);

        $auth = $this->_createHandler(array($a1, $a2));
        $auth->setUsername('jack');
        $auth->setPassword('pass');

        $auth->setKeywordParams(array('CRAM-MD5', 'LOGIN'));
        $auth->afterEhlo($this->_agent);
    }

    public function testAuthenticatorsAreNotUsedIfNoUsernameSet()
    {
        $a1 = $this->_createMockAuthenticator('PLAIN');
        $a2 = $this->_createMockAuthenticator('LOGIN');

        $a1->shouldReceive('authenticate')
           ->never()
           ->with($this->_agent, 'jack', 'pass');
        $a2->shouldReceive('authenticate')
           ->never()
           ->with($this->_agent, 'jack', 'pass')
           ->andReturn(true);

        $auth = $this->_createHandler(array($a1, $a2));

        $auth->setKeywordParams(array('CRAM-MD5', 'LOGIN'));
        $auth->afterEhlo($this->_agent);
    }

    public function testSeveralAuthenticatorsAreTriedIfNeeded()
    {
        $a1 = $this->_createMockAuthenticator('PLAIN');
        $a2 = $this->_createMockAuthenticator('LOGIN');

        $a1->shouldReceive('authenticate')
           ->once()
           ->with($this->_agent, 'jack', 'pass')
           ->andReturn(false);
        $a2->shouldReceive('authenticate')
           ->once()
           ->with($this->_agent, 'jack', 'pass')
           ->andReturn(true);

        $auth = $this->_createHandler(array($a1, $a2));
        $auth->setUsername('jack');
        $auth->setPassword('pass');

        $auth->setKeywordParams(array('PLAIN', 'LOGIN'));
        $auth->afterEhlo($this->_agent);
    }

    public function testFirstAuthenticatorToPassBreaksChain()
    {
        $a1 = $this->_createMockAuthenticator('PLAIN');
        $a2 = $this->_createMockAuthenticator('LOGIN');
        $a3 = $this->_createMockAuthenticator('CRAM-MD5');

        $a1->shouldReceive('authenticate')
           ->once()
           ->with($this->_agent, 'jack', 'pass')
           ->andReturn(false);
        $a2->shouldReceive('authenticate')
           ->once()
           ->with($this->_agent, 'jack', 'pass')
           ->andReturn(true);
        $a3->shouldReceive('authenticate')
           ->never()
           ->with($this->_agent, 'jack', 'pass');

        $auth = $this->_createHandler(array($a1, $a2));
        $auth->setUsername('jack');
        $auth->setPassword('pass');

        $auth->setKeywordParams(array('PLAIN', 'LOGIN', 'CRAM-MD5'));
        $auth->afterEhlo($this->_agent);
    }

    // -- Private helpers

    private function _createHandler($authenticators)
    {
        return new Swift_Transport_Esmtp_AuthHandler($authenticators);
    }

    private function _createMockAuthenticator($type)
    {
        $authenticator = $this->getMockery('Swift_Transport_Esmtp_Authenticator')->shouldIgnoreMissing();
        $authenticator->shouldReceive('getAuthKeyword')
                      ->zeroOrMoreTimes()
                      ->andReturn($type);

        return $authenticator;
    }
}
<?php

require_once dirname(__DIR__).'/EsmtpTransportTest.php';

interface Swift_Transport_EsmtpHandlerMixin extends Swift_Transport_EsmtpHandler
{
    public function setUsername($user);
    public function setPassword($pass);
}

class Swift_Transport_EsmtpTransport_ExtensionSupportTest extends Swift_Transport_EsmtpTransportTest
{
    public function testExtensionHandlersAreSortedAsNeeded()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
        $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();

        $ext1->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('AUTH');
        $ext1->shouldReceive('getPriorityOver')
             ->zeroOrMoreTimes()
             ->with('STARTTLS')
             ->andReturn(1);
        $ext2->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('STARTTLS');
        $ext2->shouldReceive('getPriorityOver')
             ->zeroOrMoreTimes()
             ->with('AUTH')
             ->andReturn(-1);
        $this->_finishBuffer($buf);

        $smtp->setExtensionHandlers(array($ext1, $ext2));
        $this->assertEquals(array($ext2, $ext1), $smtp->getExtensionHandlers());
    }

    public function testHandlersAreNotifiedOfParams()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
        $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();

        $buf->shouldReceive('readLine')
            ->once()
            ->with(0)
            ->andReturn("220 server.com foo\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with('~^EHLO .*?\r\n$~D')
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250-ServerName.tld\r\n");
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250-AUTH PLAIN LOGIN\r\n");
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250 SIZE=123456\r\n");

        $ext1->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('AUTH');
        $ext1->shouldReceive('setKeywordParams')
             ->once()
             ->with(array('PLAIN', 'LOGIN'));
        $ext2->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('SIZE');
        $ext2->shouldReceive('setKeywordParams')
             ->zeroOrMoreTimes()
             ->with(array('123456'));
        $this->_finishBuffer($buf);

        $smtp->setExtensionHandlers(array($ext1, $ext2));
        $smtp->start();
    }

    public function testSupportedExtensionHandlersAreRunAfterEhlo()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
        $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
        $ext3 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();

        $buf->shouldReceive('readLine')
            ->once()
            ->with(0)
            ->andReturn("220 server.com foo\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with('~^EHLO .*?\r\n$~D')
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250-ServerName.tld\r\n");
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250-AUTH PLAIN LOGIN\r\n");
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250 SIZE=123456\r\n");

        $ext1->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('AUTH');
        $ext1->shouldReceive('afterEhlo')
             ->once()
             ->with($smtp);
        $ext2->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('SIZE');
        $ext2->shouldReceive('afterEhlo')
             ->zeroOrMoreTimes()
             ->with($smtp);
        $ext3->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('STARTTLS');
        $ext3->shouldReceive('afterEhlo')
             ->never()
             ->with($smtp);
        $this->_finishBuffer($buf);

        $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3));
        $smtp->start();
    }

    public function testExtensionsCanModifyMailFromParams()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher();
        $smtp = new Swift_Transport_EsmtpTransport($buf, array(), $dispatcher);
        $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
        $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
        $ext3 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->zeroOrMoreTimes()
                ->andReturn(array('me@domain' => 'Me'));
        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('foo@bar' => null));

        $buf->shouldReceive('readLine')
            ->once()
            ->with(0)
            ->andReturn("220 server.com foo\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with('~^EHLO .*?\r\n$~D')
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250-ServerName.tld\r\n");
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250-AUTH PLAIN LOGIN\r\n");
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250 SIZE=123456\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("MAIL FROM:<me@domain> FOO ZIP\r\n")
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<foo@bar>\r\n")
            ->andReturn(3);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(3)
            ->andReturn("250 OK\r\n");
        $this->_finishBuffer($buf);

        $ext1->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('AUTH');
        $ext1->shouldReceive('getMailParams')
             ->once()
             ->andReturn('FOO');
        $ext1->shouldReceive('getPriorityOver')
             ->zeroOrMoreTimes()
             ->with('AUTH')
             ->andReturn(-1);
        $ext2->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('SIZE');
        $ext2->shouldReceive('getMailParams')
             ->once()
             ->andReturn('ZIP');
        $ext2->shouldReceive('getPriorityOver')
             ->zeroOrMoreTimes()
             ->with('AUTH')
             ->andReturn(1);
        $ext3->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('STARTTLS');
        $ext3->shouldReceive('getMailParams')
             ->never();

        $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3));
        $smtp->start();
        $smtp->send($message);
    }

    public function testExtensionsCanModifyRcptParams()
    {
        $buf = $this->_getBuffer();
        $dispatcher = $this->_createEventDispatcher();
        $smtp = new Swift_Transport_EsmtpTransport($buf, array(), $dispatcher);
        $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
        $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
        $ext3 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
        $message = $this->_createMessage();

        $message->shouldReceive('getFrom')
                ->zeroOrMoreTimes()
                ->andReturn(array('me@domain' => 'Me'));
        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('foo@bar' => null));

        $buf->shouldReceive('readLine')
            ->once()
            ->with(0)
            ->andReturn("220 server.com foo\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with('~^EHLO .+?\r\n$~D')
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250-ServerName.tld\r\n");
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250-AUTH PLAIN LOGIN\r\n");
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250 SIZE=123456\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("MAIL FROM:<me@domain>\r\n")
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn("250 OK\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("RCPT TO:<foo@bar> FOO ZIP\r\n")
            ->andReturn(3);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(3)
            ->andReturn("250 OK\r\n");
        $this->_finishBuffer($buf);

        $ext1->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('AUTH');
        $ext1->shouldReceive('getRcptParams')
             ->once()
             ->andReturn('FOO');
        $ext1->shouldReceive('getPriorityOver')
             ->zeroOrMoreTimes()
             ->with('AUTH')
             ->andReturn(-1);
        $ext2->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('SIZE');
        $ext2->shouldReceive('getRcptParams')
             ->once()
             ->andReturn('ZIP');
        $ext2->shouldReceive('getPriorityOver')
             ->zeroOrMoreTimes()
             ->with('AUTH')
             ->andReturn(1);
        $ext3->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('STARTTLS');
        $ext3->shouldReceive('getRcptParams')
             ->never();

        $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3));
        $smtp->start();
        $smtp->send($message);
    }

    public function testExtensionsAreNotifiedOnCommand()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
        $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
        $ext3 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();

        $buf->shouldReceive('readLine')
            ->once()
            ->with(0)
            ->andReturn("220 server.com foo\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with('~^EHLO .+?\r\n$~D')
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250-ServerName.tld\r\n");
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250-AUTH PLAIN LOGIN\r\n");
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250 SIZE=123456\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("FOO\r\n")
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn("250 Cool\r\n");
        $this->_finishBuffer($buf);

        $ext1->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('AUTH');
        $ext1->shouldReceive('onCommand')
             ->once()
             ->with($smtp, "FOO\r\n", array(250, 251), \Mockery::any(), \Mockery::any());
        $ext2->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('SIZE');
        $ext2->shouldReceive('onCommand')
             ->once()
             ->with($smtp, "FOO\r\n", array(250, 251), \Mockery::any(), \Mockery::any());
        $ext3->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('STARTTLS');
        $ext3->shouldReceive('onCommand')
             ->never()
             ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any());

        $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3));
        $smtp->start();
        $smtp->executeCommand("FOO\r\n", array(250, 251));
    }

    public function testChainOfCommandAlgorithmWhenNotifyingExtensions()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
        $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();
        $ext3 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();

        $buf->shouldReceive('readLine')
            ->once()
            ->with(0)
            ->andReturn("220 server.com foo\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with('~^EHLO .+?\r\n$~D')
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250-ServerName.tld\r\n");
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250-AUTH PLAIN LOGIN\r\n");
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn("250 SIZE=123456\r\n");
        $buf->shouldReceive('write')
            ->never()
            ->with("FOO\r\n");
        $this->_finishBuffer($buf);

        $ext1->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('AUTH');
        $ext1->shouldReceive('onCommand')
             ->once()
             ->with($smtp, "FOO\r\n", array(250, 251), \Mockery::any(), \Mockery::any())
             ->andReturnUsing(function ($a, $b, $c, $d, &$e) {
                 $e = true;

                 return '250 ok';
             });
        $ext2->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('SIZE');
        $ext2->shouldReceive('onCommand')
             ->never()
             ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any());

        $ext3->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('STARTTLS');
        $ext3->shouldReceive('onCommand')
             ->never()
             ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any());

        $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3));
        $smtp->start();
        $smtp->executeCommand("FOO\r\n", array(250, 251));
    }

    public function testExtensionsCanExposeMixinMethods()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $ext1 = $this->getMockery('Swift_Transport_EsmtpHandlerMixin')->shouldIgnoreMissing();
        $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();

        $ext1->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('AUTH');
        $ext1->shouldReceive('exposeMixinMethods')
             ->zeroOrMoreTimes()
             ->andReturn(array('setUsername', 'setPassword'));
        $ext1->shouldReceive('setUsername')
             ->once()
             ->with('mick');
        $ext1->shouldReceive('setPassword')
             ->once()
             ->with('pass');
        $ext2->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('STARTTLS');
        $this->_finishBuffer($buf);

        $smtp->setExtensionHandlers(array($ext1, $ext2));
        $smtp->setUsername('mick');
        $smtp->setPassword('pass');
    }

    public function testMixinMethodsBeginningWithSetAndNullReturnAreFluid()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $ext1 = $this->getMockery('Swift_Transport_EsmtpHandlerMixin')->shouldIgnoreMissing();
        $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();

        $ext1->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('AUTH');
        $ext1->shouldReceive('exposeMixinMethods')
             ->zeroOrMoreTimes()
             ->andReturn(array('setUsername', 'setPassword'));
        $ext1->shouldReceive('setUsername')
             ->once()
             ->with('mick')
             ->andReturn(null);
        $ext1->shouldReceive('setPassword')
             ->once()
             ->with('pass')
             ->andReturn(null);
        $ext2->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('STARTTLS');
        $this->_finishBuffer($buf);

        $smtp->setExtensionHandlers(array($ext1, $ext2));
        $ret = $smtp->setUsername('mick');
        $this->assertEquals($smtp, $ret);
        $ret = $smtp->setPassword('pass');
        $this->assertEquals($smtp, $ret);
    }

    public function testMixinSetterWhichReturnValuesAreNotFluid()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $ext1 = $this->getMockery('Swift_Transport_EsmtpHandlerMixin')->shouldIgnoreMissing();
        $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing();

        $ext1->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('AUTH');
        $ext1->shouldReceive('exposeMixinMethods')
             ->zeroOrMoreTimes()
             ->andReturn(array('setUsername', 'setPassword'));
        $ext1->shouldReceive('setUsername')
             ->once()
             ->with('mick')
             ->andReturn('x');
        $ext1->shouldReceive('setPassword')
             ->once()
             ->with('pass')
             ->andReturn('x');
        $ext2->shouldReceive('getHandledKeyword')
             ->zeroOrMoreTimes()
             ->andReturn('STARTTLS');
        $this->_finishBuffer($buf);

        $smtp->setExtensionHandlers(array($ext1, $ext2));
        $this->assertEquals('x', $smtp->setUsername('mick'));
        $this->assertEquals('x', $smtp->setPassword('pass'));
    }
}
<?php

class Swift_Transport_EsmtpTransportTest extends Swift_Transport_AbstractSmtpEventSupportTest
{
    protected function _getTransport($buf, $dispatcher = null)
    {
        if (!$dispatcher) {
            $dispatcher = $this->_createEventDispatcher();
        }

        return new Swift_Transport_EsmtpTransport($buf, array(), $dispatcher);
    }

    public function testHostCanBeSetAndFetched()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $smtp->setHost('foo');
        $this->assertEquals('foo', $smtp->getHost(), '%s: Host should be returned');
    }

    public function testPortCanBeSetAndFetched()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $smtp->setPort(25);
        $this->assertEquals(25, $smtp->getPort(), '%s: Port should be returned');
    }

    public function testTimeoutCanBeSetAndFetched()
    {
        $buf = $this->_getBuffer();
        $buf->shouldReceive('setParam')
            ->once()
            ->with('timeout', 10);

        $smtp = $this->_getTransport($buf);
        $smtp->setTimeout(10);
        $this->assertEquals(10, $smtp->getTimeout(), '%s: Timeout should be returned');
    }

    public function testEncryptionCanBeSetAndFetched()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $smtp->setEncryption('tls');
        $this->assertEquals('tls', $smtp->getEncryption(), '%s: Crypto should be returned');
    }

    public function testStartSendsHeloToInitiate()
    {
        //Overridden for EHLO instead
    }

    public function testStartSendsEhloToInitiate()
    {
        /* -- RFC 2821, 3.2.

            3.2 Client Initiation

         Once the server has sent the welcoming message and the client has
         received it, the client normally sends the EHLO command to the
         server, indicating the client's identity.  In addition to opening the
         session, use of EHLO indicates that the client is able to process
         service extensions and requests that the server provide a list of the
         extensions it supports.  Older SMTP systems which are unable to
         support service extensions and contemporary clients which do not
         require service extensions in the mail session being initiated, MAY
         use HELO instead of EHLO.  Servers MUST NOT return the extended
         EHLO-style response to a HELO command.  For a particular connection
         attempt, if the server returns a "command not recognized" response to
         EHLO, the client SHOULD be able to fall back and send HELO.

         In the EHLO command the host sending the command identifies itself;
         the command may be interpreted as saying "Hello, I am <domain>" (and,
         in the case of EHLO, "and I support service extension requests").

       -- RFC 2281, 4.1.1.1.

       ehlo            = "EHLO" SP Domain CRLF
       helo            = "HELO" SP Domain CRLF

       -- RFC 2821, 4.3.2.

       EHLO or HELO
           S: 250
           E: 504, 550

     */

        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);

        $buf->shouldReceive('initialize')
            ->once();
        $buf->shouldReceive('readLine')
            ->once()
            ->with(0)
            ->andReturn("220 some.server.tld bleh\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with('~^EHLO .+?\r\n$~D')
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('250 ServerName'."\r\n");

        $this->_finishBuffer($buf);
        try {
            $smtp->start();
        } catch (Exception $e) {
            $this->fail('Starting Esmtp should send EHLO and accept 250 response');
        }
    }

    public function testHeloIsUsedAsFallback()
    {
        /* -- RFC 2821, 4.1.4.

       If the EHLO command is not acceptable to the SMTP server, 501, 500,
       or 502 failure replies MUST be returned as appropriate.  The SMTP
       server MUST stay in the same state after transmitting these replies
       that it was in before the EHLO was received.
        */

        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);

        $buf->shouldReceive('initialize')
            ->once();
        $buf->shouldReceive('readLine')
            ->once()
            ->with(0)
            ->andReturn("220 some.server.tld bleh\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with('~^EHLO .+?\r\n$~D')
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('501 WTF'."\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with('~^HELO .+?\r\n$~D')
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn('250 HELO'."\r\n");

        $this->_finishBuffer($buf);
        try {
            $smtp->start();
        } catch (Exception $e) {
            $this->fail(
                'Starting Esmtp should fallback to HELO if needed and accept 250 response'
                );
        }
    }

    public function testInvalidHeloResponseCausesException()
    {
        //Overridden to first try EHLO
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);

        $buf->shouldReceive('initialize')
            ->once();
        $buf->shouldReceive('readLine')
            ->once()
            ->with(0)
            ->andReturn("220 some.server.tld bleh\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with('~^EHLO .+?\r\n$~D')
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('501 WTF'."\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with('~^HELO .+?\r\n$~D')
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn('504 WTF'."\r\n");
        $this->_finishBuffer($buf);

        try {
            $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started');
            $smtp->start();
            $this->fail('Non 250 HELO response should raise Exception');
        } catch (Exception $e) {
            $this->assertFalse($smtp->isStarted(), '%s: SMTP start() should have failed');
        }
    }

    public function testDomainNameIsPlacedInEhlo()
    {
        /* -- RFC 2821, 4.1.4.

       The SMTP client MUST, if possible, ensure that the domain parameter
       to the EHLO command is a valid principal host name (not a CNAME or MX
       name) for its host.  If this is not possible (e.g., when the client's
       address is dynamically assigned and the client does not have an
       obvious name), an address literal SHOULD be substituted for the
       domain name and supplemental information provided that will assist in
       identifying the client.
        */

        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $buf->shouldReceive('initialize')
            ->once();
        $buf->shouldReceive('readLine')
            ->once()
            ->with(0)
            ->andReturn("220 some.server.tld bleh\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("EHLO mydomain.com\r\n")
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('250 ServerName'."\r\n");

        $this->_finishBuffer($buf);
        $smtp->setLocalDomain('mydomain.com');
        $smtp->start();
    }

    public function testDomainNameIsPlacedInHelo()
    {
        //Overridden to include ESMTP
        /* -- RFC 2821, 4.1.4.

       The SMTP client MUST, if possible, ensure that the domain parameter
       to the EHLO command is a valid principal host name (not a CNAME or MX
       name) for its host.  If this is not possible (e.g., when the client's
       address is dynamically assigned and the client does not have an
       obvious name), an address literal SHOULD be substituted for the
       domain name and supplemental information provided that will assist in
       identifying the client.
        */

        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $buf->shouldReceive('initialize')
            ->once();
        $buf->shouldReceive('readLine')
            ->once()
            ->with(0)
            ->andReturn("220 some.server.tld bleh\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with('~^EHLO .+?\r\n$~D')
            ->andReturn(1);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(1)
            ->andReturn('501 WTF'."\r\n");
        $buf->shouldReceive('write')
            ->once()
            ->with("HELO mydomain.com\r\n")
            ->andReturn(2);
        $buf->shouldReceive('readLine')
            ->once()
            ->with(2)
            ->andReturn('250 ServerName'."\r\n");

        $this->_finishBuffer($buf);
        $smtp->setLocalDomain('mydomain.com');
        $smtp->start();
    }

    public function testFluidInterface()
    {
        $buf = $this->_getBuffer();
        $smtp = $this->_getTransport($buf);
        $buf->shouldReceive('setParam')
            ->once()
            ->with('timeout', 30);

        $ref = $smtp
            ->setHost('foo')
            ->setPort(25)
            ->setEncryption('tls')
            ->setTimeout(30)
            ;
        $this->assertEquals($ref, $smtp);
    }
}
<?php

class Swift_Transport_FailoverTransportTest extends \SwiftMailerTestCase
{
    public function testFirstTransportIsUsed()
    {
        $message1 = $this->getMockery('Swift_Mime_Message');
        $message2 = $this->getMockery('Swift_Mime_Message');
        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');
        $connectionState = false;

        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState) {
               return $connectionState;
           });
        $t1->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState) {
               if (!$connectionState) {
                   $connectionState = true;
               }
           });
        $t1->shouldReceive('send')
           ->twice()
           ->with(\Mockery::anyOf($message1, $message2), \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState) {
               if ($connectionState) {
                   return 1;
               }
           });
        $t2->shouldReceive('start')->never();
        $t2->shouldReceive('send')->never();

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->start();
        $this->assertEquals(1, $transport->send($message1));
        $this->assertEquals(1, $transport->send($message2));
    }

    public function testMessageCanBeTriedOnNextTransportIfExceptionThrown()
    {
        $e = new Swift_TransportException('b0rken');

        $message = $this->getMockery('Swift_Mime_Message');
        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');
        $connectionState1 = false;
        $connectionState2 = false;

        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState1) {
               return $connectionState1;
           });
        $t1->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState1) {
               if (!$connectionState1) {
                   $connectionState1 = true;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState1, $e) {
               if ($connectionState1) {
                   throw $e;
               }
           });

        $t2->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState2) {
               return $connectionState2;
           });
        $t2->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState2) {
               if (!$connectionState2) {
                   $connectionState2 = true;
               }
           });
        $t2->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState2, $e) {
               if ($connectionState2) {
                   return 1;
               }
           });

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->start();
        $this->assertEquals(1, $transport->send($message));
    }

    public function testZeroIsReturnedIfTransportReturnsZero()
    {
        $message = $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing();
        $t1 = $this->getMockery('Swift_Transport')->shouldIgnoreMissing();

        $connectionState = false;
        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState) {
               return $connectionState;
           });
        $t1->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState) {
               if (!$connectionState) {
                   $connectionState = true;
               }
           });
        $testCase = $this;
        $t1->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState, $testCase) {
               if (!$connectionState) {
                   $testCase->fail();
               }

               return 0;
           });

        $transport = $this->_getTransport(array($t1));
        $transport->start();
        $this->assertEquals(0, $transport->send($message));
    }

    public function testTransportsWhichThrowExceptionsAreNotRetried()
    {
        $e = new Swift_TransportException('maur b0rken');

        $message1 = $this->getMockery('Swift_Mime_Message');
        $message2 = $this->getMockery('Swift_Mime_Message');
        $message3 = $this->getMockery('Swift_Mime_Message');
        $message4 = $this->getMockery('Swift_Mime_Message');
        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');
        $connectionState1 = false;
        $connectionState2 = false;

        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState1) {
               return $connectionState1;
           });
        $t1->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState1) {
               if (!$connectionState1) {
                   $connectionState1 = true;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message1, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState1, $e) {
               if ($connectionState1) {
                   throw $e;
               }
           });
        $t1->shouldReceive('send')
           ->never()
           ->with($message2, \Mockery::any());
        $t1->shouldReceive('send')
           ->never()
           ->with($message3, \Mockery::any());
        $t1->shouldReceive('send')
           ->never()
           ->with($message4, \Mockery::any());

        $t2->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState2) {
               return $connectionState2;
           });
        $t2->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState2) {
               if (!$connectionState2) {
                   $connectionState2 = true;
               }
           });
        $t2->shouldReceive('send')
           ->times(4)
           ->with(\Mockery::anyOf($message1, $message2, $message3, $message4), \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState2, $e) {
               if ($connectionState2) {
                   return 1;
               }
           });

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->start();
        $this->assertEquals(1, $transport->send($message1));
        $this->assertEquals(1, $transport->send($message2));
        $this->assertEquals(1, $transport->send($message3));
        $this->assertEquals(1, $transport->send($message4));
    }

    public function testExceptionIsThrownIfAllTransportsDie()
    {
        $e = new Swift_TransportException('b0rken');

        $message = $this->getMockery('Swift_Mime_Message');
        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');
        $connectionState1 = false;
        $connectionState2 = false;

        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState1) {
               return $connectionState1;
           });
        $t1->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState1) {
               if (!$connectionState1) {
                   $connectionState1 = true;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState1, $e) {
               if ($connectionState1) {
                   throw $e;
               }
           });

        $t2->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState2) {
               return $connectionState2;
           });
        $t2->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState2) {
               if (!$connectionState2) {
                   $connectionState2 = true;
               }
           });
        $t2->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState2, $e) {
               if ($connectionState2) {
                   throw $e;
               }
           });

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->start();
        try {
            $transport->send($message);
            $this->fail('All transports failed so Exception should be thrown');
        } catch (Exception $e) {
        }
    }

    public function testStoppingTransportStopsAllDelegates()
    {
        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');

        $connectionState1 = true;
        $connectionState2 = true;

        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState1) {
               return $connectionState1;
           });
        $t1->shouldReceive('stop')
           ->once()
           ->andReturnUsing(function () use (&$connectionState1) {
               if ($connectionState1) {
                   $connectionState1 = false;
               }
           });

        $t2->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState2) {
               return $connectionState2;
           });
        $t2->shouldReceive('stop')
           ->once()
           ->andReturnUsing(function () use (&$connectionState2) {
               if ($connectionState2) {
                   $connectionState2 = false;
               }
           });

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->start();
        $transport->stop();
    }

    public function testTransportShowsAsNotStartedIfAllDelegatesDead()
    {
        $e = new Swift_TransportException('b0rken');

        $message = $this->getMockery('Swift_Mime_Message');
        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');

        $connectionState1 = false;
        $connectionState2 = false;

        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState1) {
               return $connectionState1;
           });
        $t1->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState1) {
               if (!$connectionState1) {
                   $connectionState1 = true;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState1, $e) {
               if ($connectionState1) {
                   $connectionState1 = false;
                   throw $e;
               }
           });

        $t2->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState2) {
               return $connectionState2;
           });
        $t2->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState2) {
               if (!$connectionState2) {
                   $connectionState2 = true;
               }
           });
        $t2->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState2, $e) {
               if ($connectionState2) {
                   $connectionState2 = false;
                   throw $e;
               }
           });

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->start();
        $this->assertTrue($transport->isStarted());
        try {
            $transport->send($message);
            $this->fail('All transports failed so Exception should be thrown');
        } catch (Exception $e) {
            $this->assertFalse($transport->isStarted());
        }
    }

    public function testRestartingTransportRestartsDeadDelegates()
    {
        $e = new Swift_TransportException('b0rken');

        $message1 = $this->getMockery('Swift_Mime_Message');
        $message2 = $this->getMockery('Swift_Mime_Message');
        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');

        $connectionState1 = false;
        $connectionState2 = false;

        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState1) {
               return $connectionState1;
           });
        $t1->shouldReceive('start')
           ->twice()
           ->andReturnUsing(function () use (&$connectionState1) {
               if (!$connectionState1) {
                   $connectionState1 = true;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message1, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState1, $e) {
               if ($connectionState1) {
                   $connectionState1 = false;
                   throw $e;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message2, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState1) {
               if ($connectionState1) {
                   return 10;
               }
           });

        $t2->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState2) {
               return $connectionState2;
           });
        $t2->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState2) {
               if (!$connectionState2) {
                   $connectionState2 = true;
               }
           });
        $t2->shouldReceive('send')
           ->once()
           ->with($message1, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState2, $e) {
               if ($connectionState2) {
                   $connectionState2 = false;
                   throw $e;
               }
           });
        $t2->shouldReceive('send')
           ->never()
           ->with($message2, \Mockery::any());

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->start();
        $this->assertTrue($transport->isStarted());
        try {
            $transport->send($message1);
            $this->fail('All transports failed so Exception should be thrown');
        } catch (Exception $e) {
            $this->assertFalse($transport->isStarted());
        }
        //Restart and re-try
        $transport->start();
        $this->assertTrue($transport->isStarted());
        $this->assertEquals(10, $transport->send($message2));
    }

    public function testFailureReferenceIsPassedToDelegates()
    {
        $failures = array();

        $message = $this->getMockery('Swift_Mime_Message');
        $t1 = $this->getMockery('Swift_Transport');

        $connectionState = false;

        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use ($connectionState) {
               return $connectionState;
           });
        $t1->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use ($connectionState) {
               if (!$connectionState) {
                   $connectionState = true;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message, $failures)
           ->andReturnUsing(function () use ($connectionState) {
               if ($connectionState) {
                   return 1;
               }
           });

        $transport = $this->_getTransport(array($t1));
        $transport->start();
        $transport->send($message, $failures);
    }

    public function testRegisterPluginDelegatesToLoadedTransports()
    {
        $plugin = $this->_createPlugin();

        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');
        $t1->shouldReceive('registerPlugin')
           ->once()
           ->with($plugin);
        $t2->shouldReceive('registerPlugin')
           ->once()
           ->with($plugin);

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->registerPlugin($plugin);
    }

    // -- Private helpers

    private function _getTransport(array $transports)
    {
        $transport = new Swift_Transport_FailoverTransport();
        $transport->setTransports($transports);

        return $transport;
    }

    private function _createPlugin()
    {
        return $this->getMockery('Swift_Events_EventListener');
    }
}
<?php

class Swift_Transport_LoadBalancedTransportTest extends \SwiftMailerTestCase
{
    public function testEachTransportIsUsedInTurn()
    {
        $message1 = $this->getMockery('Swift_Mime_Message');
        $message2 = $this->getMockery('Swift_Mime_Message');
        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');
        $connectionState1 = false;
        $connectionState2 = false;

        $testCase = $this;
        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState1) {
               return $connectionState1;
           });
        $t1->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState1) {
               if (!$connectionState1) {
                   $connectionState1 = true;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message1, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState1, $testCase) {
               if ($connectionState1) {
                   return 1;
               }
               $testCase->fail();
           });
        $t1->shouldReceive('send')
           ->never()
           ->with($message2, \Mockery::any());

        $t2->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState2) {
               return $connectionState2;
           });
        $t2->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState2) {
               if (!$connectionState2) {
                   $connectionState2 = true;
               }
           });
        $t2->shouldReceive('send')
           ->once()
           ->with($message2, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState2, $testCase) {
               if ($connectionState2) {
                   return 1;
               }
               $testCase->fail();
           });
        $t2->shouldReceive('send')
           ->never()
           ->with($message1, \Mockery::any());

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->start();
        $this->assertEquals(1, $transport->send($message1));
        $this->assertEquals(1, $transport->send($message2));
    }

    public function testTransportsAreReusedInRotatingFashion()
    {
        $message1 = $this->getMockery('Swift_Mime_Message');
        $message2 = $this->getMockery('Swift_Mime_Message');
        $message3 = $this->getMockery('Swift_Mime_Message');
        $message4 = $this->getMockery('Swift_Mime_Message');
        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');
        $connectionState1 = false;
        $connectionState2 = false;

        $testCase = $this;
        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState1) {
               return $connectionState1;
           });
        $t1->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState1) {
               if (!$connectionState1) {
                   $connectionState1 = true;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message1, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState1, $testCase) {
               if ($connectionState1) {
                   return 1;
               }
               $testCase->fail();
           });
        $t1->shouldReceive('send')
           ->never()
           ->with($message2, \Mockery::any());
        $t1->shouldReceive('send')
           ->once()
           ->with($message3, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState1, $testCase) {
               if ($connectionState1) {
                   return 1;
               }
               $testCase->fail();
           });
        $t1->shouldReceive('send')
           ->never()
           ->with($message4, \Mockery::any());

        $t2->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState2) {
               return $connectionState2;
           });
        $t2->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState2) {
               if (!$connectionState2) {
                   $connectionState2 = true;
               }
           });
        $t2->shouldReceive('send')
           ->once()
           ->with($message2, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState2, $testCase) {
               if ($connectionState2) {
                   return 1;
               }
               $testCase->fail();
           });
        $t2->shouldReceive('send')
           ->never()
           ->with($message1, \Mockery::any());
        $t2->shouldReceive('send')
           ->once()
           ->with($message4, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState2, $testCase) {
               if ($connectionState2) {
                   return 1;
               }
               $testCase->fail();
           });
        $t2->shouldReceive('send')
           ->never()
           ->with($message3, \Mockery::any());

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->start();

        $this->assertEquals(1, $transport->send($message1));
        $this->assertEquals(1, $transport->send($message2));
        $this->assertEquals(1, $transport->send($message3));
        $this->assertEquals(1, $transport->send($message4));
    }

    public function testMessageCanBeTriedOnNextTransportIfExceptionThrown()
    {
        $e = new Swift_TransportException('b0rken');

        $message = $this->getMockery('Swift_Mime_Message');
        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');
        $connectionState1 = false;
        $connectionState2 = false;

        $testCase = $this;
        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState1) {
               return $connectionState1;
           });
        $t1->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState1) {
               if (!$connectionState1) {
                   $connectionState1 = true;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState1, $e, $testCase) {
               if ($connectionState1) {
                   throw $e;
               }
               $testCase->fail();
           });

        $t2->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState2) {
               return $connectionState2;
           });
        $t2->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState2) {
               if (!$connectionState2) {
                   $connectionState2 = true;
               }
           });
        $t2->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState2, $testCase) {
               if ($connectionState2) {
                   return 1;
               }
               $testCase->fail();
           });

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->start();
        $this->assertEquals(1, $transport->send($message));
    }

    public function testMessageIsTriedOnNextTransportIfZeroReturned()
    {
        $message = $this->getMockery('Swift_Mime_Message');
        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');
        $connectionState1 = false;
        $connectionState2 = false;

        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState1) {
               return $connectionState1;
           });
        $t1->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState1) {
               if (!$connectionState1) {
                   $connectionState1 = true;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState1) {
               if ($connectionState1) {
                   return 0;
               }

               return 1;
           });

        $t2->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState2) {
               return $connectionState2;
           });
        $t2->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState2) {
               if (!$connectionState2) {
                   $connectionState2 = true;
               }
           });
        $t2->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState2) {
               if ($connectionState2) {
                   return 1;
               }

               return 0;
           });

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->start();
        $this->assertEquals(1, $transport->send($message));
    }

    public function testZeroIsReturnedIfAllTransportsReturnZero()
    {
        $message = $this->getMockery('Swift_Mime_Message');
        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');
        $connectionState1 = false;
        $connectionState2 = false;

        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState1) {
               return $connectionState1;
           });
        $t1->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState1) {
               if (!$connectionState1) {
                   $connectionState1 = true;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState1) {
               if ($connectionState1) {
                   return 0;
               }

               return 1;
           });

        $t2->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState2) {
               return $connectionState2;
           });
        $t2->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState2) {
               if (!$connectionState2) {
                   $connectionState2 = true;
               }
           });
        $t2->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState2) {
               if ($connectionState2) {
                   return 0;
               }

               return 1;
           });

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->start();
        $this->assertEquals(0, $transport->send($message));
    }

    public function testTransportsWhichThrowExceptionsAreNotRetried()
    {
        $e = new Swift_TransportException('maur b0rken');

        $message1 = $this->getMockery('Swift_Mime_Message');
        $message2 = $this->getMockery('Swift_Mime_Message');
        $message3 = $this->getMockery('Swift_Mime_Message');
        $message4 = $this->getMockery('Swift_Mime_Message');
        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');
        $connectionState1 = false;
        $connectionState2 = false;

        $testCase = $this;
        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState1) {
               return $connectionState1;
           });
        $t1->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState1) {
               if (!$connectionState1) {
                   $connectionState1 = true;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message1, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState1, $e, $testCase) {
               if ($connectionState1) {
                   throw $e;
               }
               $testCase->fail();
           });
        $t1->shouldReceive('send')
           ->never()
           ->with($message2, \Mockery::any());
        $t1->shouldReceive('send')
           ->never()
           ->with($message3, \Mockery::any());
        $t1->shouldReceive('send')
           ->never()
           ->with($message4, \Mockery::any());

        $t2->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState2) {
               return $connectionState2;
           });
        $t2->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState2) {
               if (!$connectionState2) {
                   $connectionState2 = true;
               }
           });
        $t2->shouldReceive('send')
           ->times(4)
           ->with(\Mockery::anyOf($message1, $message3, $message3, $message4), \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState2, $testCase) {
               if ($connectionState2) {
                   return 1;
               }
               $testCase->fail();
           });

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->start();
        $this->assertEquals(1, $transport->send($message1));
        $this->assertEquals(1, $transport->send($message2));
        $this->assertEquals(1, $transport->send($message3));
        $this->assertEquals(1, $transport->send($message4));
    }

    public function testExceptionIsThrownIfAllTransportsDie()
    {
        $e = new Swift_TransportException('b0rken');

        $message = $this->getMockery('Swift_Mime_Message');
        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');
        $connectionState1 = false;
        $connectionState2 = false;

        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState1) {
               return $connectionState1;
           });
        $t1->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState1) {
               if (!$connectionState1) {
                   $connectionState1 = true;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState1, $e) {
               if ($connectionState1) {
                   throw $e;
               }
           });

        $t2->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState2) {
               return $connectionState2;
           });
        $t2->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState2) {
               if (!$connectionState2) {
                   $connectionState2 = true;
               }
           });
        $t2->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState2, $e) {
               if ($connectionState2) {
                   throw $e;
               }
           });

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->start();
        try {
            $transport->send($message);
            $this->fail('All transports failed so Exception should be thrown');
        } catch (Exception $e) {
        }
    }

    public function testStoppingTransportStopsAllDelegates()
    {
        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');
        $connectionState1 = true;
        $connectionState2 = true;

        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState1) {
               return $connectionState1;
           });
        $t1->shouldReceive('stop')
           ->once()
           ->andReturnUsing(function () use (&$connectionState1) {
               if ($connectionState1) {
                   $connectionState1 = false;
               }
           });

        $t2->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState2) {
               return $connectionState2;
           });
        $t2->shouldReceive('stop')
           ->once()
           ->andReturnUsing(function () use (&$connectionState2) {
               if ($connectionState2) {
                   $connectionState2 = false;
               }
           });

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->start();
        $transport->stop();
    }

    public function testTransportShowsAsNotStartedIfAllDelegatesDead()
    {
        $e = new Swift_TransportException('b0rken');

        $message = $this->getMockery('Swift_Mime_Message');
        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');
        $connectionState1 = false;
        $connectionState2 = false;

        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState1) {
               return $connectionState1;
           });
        $t1->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState1) {
               if (!$connectionState1) {
                   $connectionState1 = true;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState1, $e) {
               if ($connectionState1) {
                   throw $e;
               }
           });

        $t2->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState2) {
               return $connectionState2;
           });
        $t2->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState2) {
               if (!$connectionState2) {
                   $connectionState2 = true;
               }
           });
        $t2->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState2, $e) {
               if ($connectionState2) {
                   throw $e;
               }
           });

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->start();
        $this->assertTrue($transport->isStarted());
        try {
            $transport->send($message);
            $this->fail('All transports failed so Exception should be thrown');
        } catch (Exception $e) {
            $this->assertFalse($transport->isStarted());
        }
    }

    public function testRestartingTransportRestartsDeadDelegates()
    {
        $e = new Swift_TransportException('b0rken');

        $message1 = $this->getMockery('Swift_Mime_Message');
        $message2 = $this->getMockery('Swift_Mime_Message');
        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');
        $connectionState1 = false;
        $connectionState2 = false;

        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState1) {
               return $connectionState1;
           });
        $t1->shouldReceive('start')
           ->twice()
           ->andReturnUsing(function () use (&$connectionState1) {
               if (!$connectionState1) {
                   $connectionState1 = true;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message1, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState1, $e) {
               if ($connectionState1) {
                   $connectionState1 = false;
                   throw $e;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message2, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState1, $e) {
               if ($connectionState1) {
                   return 10;
               }
           });

        $t2->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState2) {
               return $connectionState2;
           });
        $t2->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState2) {
               if (!$connectionState2) {
                   $connectionState2 = true;
               }
           });
        $t2->shouldReceive('send')
           ->once()
           ->with($message1, \Mockery::any())
           ->andReturnUsing(function () use (&$connectionState2, $e) {
               if ($connectionState2) {
                   throw $e;
               }
           });
        $t2->shouldReceive('send')
           ->never()
           ->with($message2, \Mockery::any());

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->start();
        $this->assertTrue($transport->isStarted());
        try {
            $transport->send($message1);
            $this->fail('All transports failed so Exception should be thrown');
        } catch (Exception $e) {
            $this->assertFalse($transport->isStarted());
        }
        //Restart and re-try
        $transport->start();
        $this->assertTrue($transport->isStarted());
        $this->assertEquals(10, $transport->send($message2));
    }

    public function testFailureReferenceIsPassedToDelegates()
    {
        $failures = array();
        $testCase = $this;

        $message = $this->getMockery('Swift_Mime_Message');
        $t1 = $this->getMockery('Swift_Transport');
        $connectionState = false;

        $t1->shouldReceive('isStarted')
           ->zeroOrMoreTimes()
           ->andReturnUsing(function () use (&$connectionState) {
               return $connectionState;
           });
        $t1->shouldReceive('start')
           ->once()
           ->andReturnUsing(function () use (&$connectionState) {
               if (!$connectionState) {
                   $connectionState = true;
               }
           });
        $t1->shouldReceive('send')
           ->once()
           ->with($message, \Mockery::on(function (&$var) use (&$failures, $testCase) {
               return $testCase->varsAreReferences($var, $failures);
           }))
           ->andReturnUsing(function () use (&$connectionState) {
               if ($connectionState) {
                   return 1;
               }
           });

        $transport = $this->_getTransport(array($t1));
        $transport->start();
        $transport->send($message, $failures);
    }

    public function testRegisterPluginDelegatesToLoadedTransports()
    {
        $plugin = $this->_createPlugin();

        $t1 = $this->getMockery('Swift_Transport');
        $t2 = $this->getMockery('Swift_Transport');

        $t1->shouldReceive('registerPlugin')
           ->once()
           ->with($plugin);
        $t2->shouldReceive('registerPlugin')
           ->once()
           ->with($plugin);

        $transport = $this->_getTransport(array($t1, $t2));
        $transport->registerPlugin($plugin);
    }

    /**
     * Adapted from Yay_Matchers_ReferenceMatcher.
     */
    public function varsAreReferences(&$ref1, &$ref2)
    {
        if (is_object($ref2)) {
            return ($ref1 === $ref2);
        }
        if ($ref1 !== $ref2) {
            return false;
        }

        $copy = $ref2;
        $randomString = uniqid('yay');
        $ref2 = $randomString;
        $isRef = ($ref1 === $ref2);
        $ref2 = $copy;

        return $isRef;
    }

    // -- Private helpers

    private function _getTransport(array $transports)
    {
        $transport = new Swift_Transport_LoadBalancedTransport();
        $transport->setTransports($transports);

        return $transport;
    }

    private function _createPlugin()
    {
        return $this->getMockery('Swift_Events_EventListener');
    }
}
<?php

/**
 * @group legacy
 */
class Swift_Transport_MailTransportTest extends \SwiftMailerTestCase
{
    public function testTransportInvokesMailOncePerMessage()
    {
        $invoker = $this->_createInvoker();
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);

        $headers = $this->_createHeaders();
        $message = $this->_createMessageWithRecipient($headers);

        $invoker->shouldReceive('mail')
                ->once();

        $transport->send($message);
    }

    public function testTransportUsesToFieldBodyInSending()
    {
        $invoker = $this->_createInvoker();
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);

        $to = $this->_createHeader();
        $headers = $this->_createHeaders(array(
            'To' => $to,
        ));
        $message = $this->_createMessageWithRecipient($headers);

        $to->shouldReceive('getFieldBody')
           ->zeroOrMoreTimes()
           ->andReturn('Foo <foo@bar>');
        $invoker->shouldReceive('mail')
                ->once()
                ->with('Foo <foo@bar>', \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any());

        $transport->send($message);
    }

    public function testTransportUsesSubjectFieldBodyInSending()
    {
        $invoker = $this->_createInvoker();
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);

        $subj = $this->_createHeader();
        $headers = $this->_createHeaders(array(
            'Subject' => $subj,
        ));
        $message = $this->_createMessageWithRecipient($headers);

        $subj->shouldReceive('getFieldBody')
             ->zeroOrMoreTimes()
             ->andReturn('Thing');
        $invoker->shouldReceive('mail')
                ->once()
                ->with(\Mockery::any(), 'Thing', \Mockery::any(), \Mockery::any(), \Mockery::any());

        $transport->send($message);
    }

    public function testTransportUsesBodyOfMessage()
    {
        $invoker = $this->_createInvoker();
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);

        $headers = $this->_createHeaders();
        $message = $this->_createMessageWithRecipient($headers);

        $message->shouldReceive('toString')
             ->zeroOrMoreTimes()
             ->andReturn(
                "To: Foo <foo@bar>\r\n".
                "\r\n".
                'This body'
             );
        $invoker->shouldReceive('mail')
                ->once()
                ->with(\Mockery::any(), \Mockery::any(), 'This body', \Mockery::any(), \Mockery::any());

        $transport->send($message);
    }

    public function testTransportSettingUsingReturnPathForExtraParams()
    {
        $invoker = $this->_createInvoker();
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);

        $headers = $this->_createHeaders();
        $message = $this->_createMessageWithRecipient($headers);

        $message->shouldReceive('getReturnPath')
             ->zeroOrMoreTimes()
             ->andReturn(
                'foo@bar'
             );
        $invoker->shouldReceive('mail')
                ->once()
                ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), '-ffoo@bar');

        $transport->send($message);
    }

    public function testTransportSettingEmptyExtraParams()
    {
        $invoker = $this->_createInvoker();
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);

        $headers = $this->_createHeaders();
        $message = $this->_createMessageWithRecipient($headers);

        $message->shouldReceive('getReturnPath')
            ->zeroOrMoreTimes()
            ->andReturn(null);
        $message->shouldReceive('getSender')
            ->zeroOrMoreTimes()
            ->andReturn(null);
        $message->shouldReceive('getFrom')
            ->zeroOrMoreTimes()
            ->andReturn(null);
        $invoker->shouldReceive('mail')
            ->once()
            ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), null);

        $transport->send($message);
    }

    public function testTransportSettingSettingExtraParamsWithF()
    {
        $invoker = $this->_createInvoker();
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);
        $transport->setExtraParams('-x\'foo\' -f%s');

        $headers = $this->_createHeaders();
        $message = $this->_createMessageWithRecipient($headers);

        $message->shouldReceive('getReturnPath')
            ->zeroOrMoreTimes()
            ->andReturn(
                    'foo@bar'
                );
        $message->shouldReceive('getSender')
            ->zeroOrMoreTimes()
            ->andReturn(null);
        $message->shouldReceive('getFrom')
            ->zeroOrMoreTimes()
            ->andReturn(null);
        $invoker->shouldReceive('mail')
            ->once()
            ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), '-x\'foo\' -ffoo@bar');

        $transport->send($message);
    }

    public function testTransportSettingSettingExtraParamsWithoutF()
    {
        $invoker = $this->_createInvoker();
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);
        $transport->setExtraParams('-x\'foo\'');

        $headers = $this->_createHeaders();
        $message = $this->_createMessageWithRecipient($headers);

        $message->shouldReceive('getReturnPath')
            ->zeroOrMoreTimes()
            ->andReturn(
                'foo@bar'
            );
        $message->shouldReceive('getSender')
            ->zeroOrMoreTimes()
            ->andReturn(null);
        $message->shouldReceive('getFrom')
            ->zeroOrMoreTimes()
            ->andReturn(null);
        $invoker->shouldReceive('mail')
            ->once()
            ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), '-x\'foo\'');

        $transport->send($message);
    }

    public function testTransportSettingInvalidFromEmail()
    {
        $invoker = $this->_createInvoker();
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);

        $headers = $this->_createHeaders();
        $message = $this->_createMessageWithRecipient($headers);

        $message->shouldReceive('getReturnPath')
            ->zeroOrMoreTimes()
            ->andReturn(
                '"attacker\" -oQ/tmp/ -X/var/www/cache/phpcode.php "@email.com'
            );
        $message->shouldReceive('getSender')
            ->zeroOrMoreTimes()
            ->andReturn(null);
        $message->shouldReceive('getFrom')
            ->zeroOrMoreTimes()
            ->andReturn(null);
        $invoker->shouldReceive('mail')
            ->once()
            ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), null);

        $transport->send($message);
    }

    public function testTransportUsesHeadersFromMessage()
    {
        $invoker = $this->_createInvoker();
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);

        $headers = $this->_createHeaders();
        $message = $this->_createMessageWithRecipient($headers);

        $message->shouldReceive('toString')
            ->zeroOrMoreTimes()
            ->andReturn(
                "Subject: Stuff\r\n".
                "\r\n".
                'This body'
            );
        $invoker->shouldReceive('mail')
            ->once()
            ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), 'Subject: Stuff'.PHP_EOL, \Mockery::any());

        $transport->send($message);
    }

    public function testTransportReturnsCountOfAllRecipientsIfInvokerReturnsTrue()
    {
        $invoker = $this->_createInvoker();
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);

        $headers = $this->_createHeaders();
        $message = $this->_createMessage($headers);

        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('foo@bar' => null, 'zip@button' => null));
        $message->shouldReceive('getCc')
                ->zeroOrMoreTimes()
                ->andReturn(array('test@test' => null));
        $invoker->shouldReceive('mail')
                ->once()
                ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any())
                ->andReturn(true);

        $this->assertEquals(3, $transport->send($message));
    }

    public function testTransportReturnsZeroIfInvokerReturnsFalse()
    {
        $invoker = $this->_createInvoker();
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);

        $headers = $this->_createHeaders();
        $message = $this->_createMessage($headers);

        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('foo@bar' => null, 'zip@button' => null));
        $message->shouldReceive('getCc')
                ->zeroOrMoreTimes()
                ->andReturn(array('test@test' => null));
        $invoker->shouldReceive('mail')
                ->once()
                ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any())
                ->andReturn(false);

        $this->assertEquals(0, $transport->send($message));
    }

    public function testToHeaderIsRemovedFromHeaderSetDuringSending()
    {
        $invoker = $this->_createInvoker();
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);

        $to = $this->_createHeader();
        $headers = $this->_createHeaders(array(
            'To' => $to,
        ));
        $message = $this->_createMessageWithRecipient($headers);

        $headers->shouldReceive('remove')
                ->once()
                ->with('To');
        $headers->shouldReceive('remove')
                ->zeroOrMoreTimes();
        $invoker->shouldReceive('mail')
                ->once()
                ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any());

        $transport->send($message);
    }

    public function testSubjectHeaderIsRemovedFromHeaderSetDuringSending()
    {
        $invoker = $this->_createInvoker();
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);

        $subject = $this->_createHeader();
        $headers = $this->_createHeaders(array(
            'Subject' => $subject,
        ));
        $message = $this->_createMessageWithRecipient($headers);

        $headers->shouldReceive('remove')
                ->once()
                ->with('Subject');
        $headers->shouldReceive('remove')
                ->zeroOrMoreTimes();
        $invoker->shouldReceive('mail')
                ->once()
                ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any());

        $transport->send($message);
    }

    public function testToHeaderIsPutBackAfterSending()
    {
        $invoker = $this->_createInvoker();
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);

        $to = $this->_createHeader();
        $headers = $this->_createHeaders(array(
            'To' => $to,
        ));
        $message = $this->_createMessageWithRecipient($headers);

        $headers->shouldReceive('set')
                ->once()
                ->with($to);
        $headers->shouldReceive('set')
                ->zeroOrMoreTimes();
        $invoker->shouldReceive('mail')
                ->once()
                ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any());

        $transport->send($message);
    }

    public function testSubjectHeaderIsPutBackAfterSending()
    {
        $invoker = $this->_createInvoker();
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);

        $subject = $this->_createHeader();
        $headers = $this->_createHeaders(array(
            'Subject' => $subject,
        ));
        $message = $this->_createMessageWithRecipient($headers);

        $headers->shouldReceive('set')
                ->once()
                ->with($subject);
        $headers->shouldReceive('set')
                ->zeroOrMoreTimes();
        $invoker->shouldReceive('mail')
                ->once()
                ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any());

        $transport->send($message);
    }

    public function testMessageHeadersOnlyHavePHPEolsDuringSending()
    {
        $invoker = $this->_createInvoker();
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);

        $subject = $this->_createHeader();
        $subject->shouldReceive('getFieldBody')->andReturn("Foo\r\nBar");

        $headers = $this->_createHeaders(array(
            'Subject' => $subject,
        ));
        $message = $this->_createMessageWithRecipient($headers);
        $message->shouldReceive('toString')
            ->zeroOrMoreTimes()
            ->andReturn(
                "From: Foo\r\n<foo@bar>\r\n".
                "\r\n".
                "This\r\n".
                'body'
            );

        if ("\r\n" != PHP_EOL) {
            $expectedHeaders = "From: Foo\n<foo@bar>\n";
            $expectedSubject = "Foo\nBar";
            $expectedBody = "This\nbody";
        } else {
            $expectedHeaders = "From: Foo\r\n<foo@bar>\r\n";
            $expectedSubject = "Foo\r\nBar";
            $expectedBody = "This\r\nbody";
        }

        $invoker->shouldReceive('mail')
            ->once()
            ->with(\Mockery::any(), $expectedSubject, $expectedBody, $expectedHeaders, \Mockery::any());

        $transport->send($message);
    }

    /**
     * @expectedException Swift_TransportException
     * @expectedExceptionMessage Cannot send message without a recipient
     */
    public function testExceptionWhenNoRecipients()
    {
        $invoker = $this->_createInvoker();
        $invoker->shouldReceive('mail');
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);

        $headers = $this->_createHeaders();
        $message = $this->_createMessage($headers);

        $transport->send($message);
    }

    public function noExceptionWhenRecipientsExistProvider()
    {
        return array(
            array('To'),
            array('Cc'),
            array('Bcc'),
        );
    }

    /**
     * @dataProvider noExceptionWhenRecipientsExistProvider
     *
     * @param string $header
     */
    public function testNoExceptionWhenRecipientsExist($header)
    {
        $invoker = $this->_createInvoker();
        $invoker->shouldReceive('mail');
        $dispatcher = $this->_createEventDispatcher();
        $transport = $this->_createTransport($invoker, $dispatcher);

        $headers = $this->_createHeaders();
        $message = $this->_createMessage($headers);
        $message->shouldReceive(sprintf('get%s', $header))->andReturn(array('foo@bar' => 'Foo'));

        $transport->send($message);
    }

    // -- Creation Methods

    private function _createTransport($invoker, $dispatcher)
    {
        return new Swift_Transport_MailTransport($invoker, $dispatcher);
    }

    private function _createEventDispatcher()
    {
        return $this->getMockery('Swift_Events_EventDispatcher')->shouldIgnoreMissing();
    }

    private function _createInvoker()
    {
        return $this->getMockery('Swift_Transport_MailInvoker');
    }

    private function _createMessage($headers)
    {
        $message = $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing();
        $message->shouldReceive('getHeaders')
                ->zeroOrMoreTimes()
                ->andReturn($headers);

        return $message;
    }

    private function _createMessageWithRecipient($headers, $recipient = array('foo@bar' => 'Foo'))
    {
        $message = $this->_createMessage($headers);
        $message->shouldReceive('getTo')->andReturn($recipient);

        return $message;
    }

    private function _createHeaders($headers = array())
    {
        $set = $this->getMockery('Swift_Mime_HeaderSet')->shouldIgnoreMissing();

        if (count($headers) > 0) {
            foreach ($headers as $name => $header) {
                $set->shouldReceive('get')
                    ->zeroOrMoreTimes()
                    ->with($name)
                    ->andReturn($header);
                $set->shouldReceive('has')
                    ->zeroOrMoreTimes()
                    ->with($name)
                    ->andReturn(true);
            }
        }

        $header = $this->_createHeader();
        $set->shouldReceive('get')
            ->zeroOrMoreTimes()
            ->andReturn($header);
        $set->shouldReceive('has')
            ->zeroOrMoreTimes()
            ->andReturn(true);

        return $set;
    }

    private function _createHeader()
    {
        return $this->getMockery('Swift_Mime_Header')->shouldIgnoreMissing();
    }
}
<?php

class Swift_Transport_SendmailTransportTest extends Swift_Transport_AbstractSmtpEventSupportTest
{
    protected function _getTransport($buf, $dispatcher = null, $command = '/usr/sbin/sendmail -bs')
    {
        if (!$dispatcher) {
            $dispatcher = $this->_createEventDispatcher();
        }
        $transport = new Swift_Transport_SendmailTransport($buf, $dispatcher);
        $transport->setCommand($command);

        return $transport;
    }

    protected function _getSendmail($buf, $dispatcher = null)
    {
        if (!$dispatcher) {
            $dispatcher = $this->_createEventDispatcher();
        }
        $sendmail = new Swift_Transport_SendmailTransport($buf, $dispatcher);

        return $sendmail;
    }

    public function testCommandCanBeSetAndFetched()
    {
        $buf = $this->_getBuffer();
        $sendmail = $this->_getSendmail($buf);

        $sendmail->setCommand('/usr/sbin/sendmail -bs');
        $this->assertEquals('/usr/sbin/sendmail -bs', $sendmail->getCommand());
        $sendmail->setCommand('/usr/sbin/sendmail -oi -t');
        $this->assertEquals('/usr/sbin/sendmail -oi -t', $sendmail->getCommand());
    }

    public function testSendingMessageIn_t_ModeUsesSimplePipe()
    {
        $buf = $this->_getBuffer();
        $sendmail = $this->_getSendmail($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('foo@bar' => 'Foobar', 'zip@button' => 'Zippy'));
        $message->shouldReceive('toByteStream')
                ->once()
                ->with($buf);
        $buf->shouldReceive('initialize')
            ->once();
        $buf->shouldReceive('terminate')
            ->once();
        $buf->shouldReceive('setWriteTranslations')
            ->once()
            ->with(array("\r\n" => "\n", "\n." => "\n.."));
        $buf->shouldReceive('setWriteTranslations')
            ->once()
            ->with(array());

        $sendmail->setCommand('/usr/sbin/sendmail -t');
        $this->assertEquals(2, $sendmail->send($message));
    }

    public function testSendingIn_t_ModeWith_i_FlagDoesntEscapeDot()
    {
        $buf = $this->_getBuffer();
        $sendmail = $this->_getSendmail($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('foo@bar' => 'Foobar', 'zip@button' => 'Zippy'));
        $message->shouldReceive('toByteStream')
                ->once()
                ->with($buf);
        $buf->shouldReceive('initialize')
            ->once();
        $buf->shouldReceive('terminate')
            ->once();
        $buf->shouldReceive('setWriteTranslations')
            ->once()
            ->with(array("\r\n" => "\n"));
        $buf->shouldReceive('setWriteTranslations')
            ->once()
            ->with(array());

        $sendmail->setCommand('/usr/sbin/sendmail -i -t');
        $this->assertEquals(2, $sendmail->send($message));
    }

    public function testSendingInTModeWith_oi_FlagDoesntEscapeDot()
    {
        $buf = $this->_getBuffer();
        $sendmail = $this->_getSendmail($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('foo@bar' => 'Foobar', 'zip@button' => 'Zippy'));
        $message->shouldReceive('toByteStream')
                ->once()
                ->with($buf);
        $buf->shouldReceive('initialize')
            ->once();
        $buf->shouldReceive('terminate')
            ->once();
        $buf->shouldReceive('setWriteTranslations')
            ->once()
            ->with(array("\r\n" => "\n"));
        $buf->shouldReceive('setWriteTranslations')
            ->once()
            ->with(array());

        $sendmail->setCommand('/usr/sbin/sendmail -oi -t');
        $this->assertEquals(2, $sendmail->send($message));
    }

    public function testSendingMessageRegeneratesId()
    {
        $buf = $this->_getBuffer();
        $sendmail = $this->_getSendmail($buf);
        $message = $this->_createMessage();

        $message->shouldReceive('getTo')
                ->zeroOrMoreTimes()
                ->andReturn(array('foo@bar' => 'Foobar', 'zip@button' => 'Zippy'));
        $message->shouldReceive('generateId');
        $buf->shouldReceive('initialize')
            ->once();
        $buf->shouldReceive('terminate')
            ->once();
        $buf->shouldReceive('setWriteTranslations')
            ->once()
            ->with(array("\r\n" => "\n", "\n." => "\n.."));
        $buf->shouldReceive('setWriteTranslations')
            ->once()
            ->with(array());

        $sendmail->setCommand('/usr/sbin/sendmail -t');
        $this->assertEquals(2, $sendmail->send($message));
    }

    public function testFluidInterface()
    {
        $buf = $this->_getBuffer();
        $sendmail = $this->_getTransport($buf);

        $ref = $sendmail->setCommand('/foo');
        $this->assertEquals($ref, $sendmail);
    }
}
<?php

class Swift_Transport_StreamBufferTest extends \PHPUnit_Framework_TestCase
{
    public function testSettingWriteTranslationsCreatesFilters()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->once())
                ->method('createFilter')
                ->with('a', 'b')
                ->will($this->returnCallback(array($this, '_createFilter')));

        $buffer = $this->_createBuffer($factory);
        $buffer->setWriteTranslations(array('a' => 'b'));
    }

    public function testOverridingTranslationsOnlyAddsNeededFilters()
    {
        $factory = $this->_createFactory();
        $factory->expects($this->exactly(2))
                ->method('createFilter')
                ->will($this->returnCallback(array($this, '_createFilter')));

        $buffer = $this->_createBuffer($factory);
        $buffer->setWriteTranslations(array('a' => 'b'));
        $buffer->setWriteTranslations(array('x' => 'y', 'a' => 'b'));
    }

    // -- Creation methods

    private function _createBuffer($replacementFactory)
    {
        return new Swift_Transport_StreamBuffer($replacementFactory);
    }

    private function _createFactory()
    {
        return $this->getMockBuilder('Swift_ReplacementFilterFactory')->getMock();
    }

    public function _createFilter()
    {
        return $this->getMockBuilder('Swift_StreamFilter')->getMock();
    }
}
<?php

return [

    // From address as a single address, or in this format: From Name <from@example.com>
	'from' => 'From Name <from@example.com>',

	// Supported drivers: smtp, mailgun
	'driver' => 'smtp',

	'mailgun' => [
		'domain' => 'mailgun.example.com',
		'secret' => 'key-123456789'
	],

    'smtp' => [
        'server' => 'smtp.gmail.com',
        'port' => 465, // Common: 25, 465, 587
        'username' => 'example@gmail.com',
        'password' => 'passwordhere',
        'ssl' => true
    ]

];
<?php namespace Mreschke\Email;

class DefineOptions
{
	/**
	 * Class handler
	 * @param  array $argv
	 * @return array
	 */
	public function handle($argv)
	{
		// All available options (- or --)
		// See http://php.net/manual/en/function.getopt.php
		$allOptions = [
			'short' => [
				'v', // -v version
				'h', // -h help
			],
			'long' => [
				'from:',
				'to:',
				'cc:',
				'bcc:',
				'subject:',
				'text:',
				'html:', 'body:',
				'file:',
				'driver:',
				'version',
				'help'
			]
		];

		// Parse options
		$options = getopt(implode($allOptions['short']), $allOptions['long']);

		// Legacy converter
		if (empty($options)) {
			if (count($argv) >= 4) {
				$options = [
					'to' => $argv[1],
					'subject' => $argv[2],
					'html' => $argv[3],
				];
				if (isset($argv[4])) $options['file'] = $argv[4];
			}
		}

		// Body is an alias for HTML
		if (isset($options['body'])) {
			$options['html'] = $options['body'];
			unset($options['body']);
		}

		// Convert any comma separation strings into arrays
		$this->toArray($options, 'to');
		$this->toArray($options, 'cc');
		$this->toArray($options, 'bcc');
		$this->toArray($options, 'file');

		// Return options
		return $options;
	}

	/**
	 * Convert any comma separated strings into arrays
	 * @param  array &$options
	 * @param  string $key
	 * @return void
	 */
	protected function toArray(&$options, $key)
	{
		if (isset($options[$key])) {
			if (is_string($options[$key]) && strpos($options[$key], ',') !== false) {
				$options[$key]= explode(',', $options[$key]);
			}
		}
	}

}
<?php

function dump()
{
    array_map(function ($items) {
        var_dump($items);
    }, func_get_args());
}

function dd()
{
    array_map(function ($items) {
        var_dump($items);
    }, func_get_args());
    exit();
}<?php namespace Mreschke\Email;

class LoadConfig
{
	/**
	 * Class handler
	 * @return array
	 */
	public function handle()
	{
		// Define config file
		$userConfig = $_SERVER['HOME']."/.config/mreschke/email";
		$systemConfig = '/etc/mreschke/email';
		$configFile = 'config.php';

		if (!file_exists("$userConfig/$configFile") && !file_exists("$systemConfig/$configFile")) {
			echo "Config not found, new installation.  Creating $userConfig/$configFile\n";
			echo "You can create a system level config at $systemConfig/$configFile\n";
			echo "User level config overrides system level config\n";
			passthru("mkdir -p $userConfig");
			file_put_contents("$userConfig/$configFile", file_get_contents(__DIR__.'/DefaultConfig.php', TRUE));
		}

		// Require config file (find user or system level configs)
		if (file_exists("$userConfig/$configFile")) {
			$config = require "$userConfig/$configFile";
		} elseif (file_exists("$systemConfig/$configFile")) {
			$config = require "$systemConfig/$configFile";
		}
		return $config;
	}
}
<?php namespace Mreschke\Email;

class SendEmail
{

	/**
	 * Class handler
	 * @param  array $options
	 * @param  array $config
	 * @return mixed
	 */
	public function handle($options, $config)
	{
		//
	}

}
<?php namespace Mreschke\Email;

use Mailgun\Mailgun;
use Http\Adapter\Guzzle6\Client as Guzzle;

class SendMailgunEmail extends SendEmail
{
	/**
	 * Class handler
	 * @param  array $options
	 * @param  array $config
	 * @return mixed
	 */
	public function handle($options, $config)
	{
		// Call parent handler
		parent::handle($options, $config);

		// Config options
		$driver = 'mailgun';
		$from = $config['from'];
		$domain = $config[$driver]['domain'];
		$secret = $config[$driver]['secret'];

		// Instantiate Mailgun
		$client = new Guzzle();
		$mailgun = new Mailgun($secret, $client);

		// Convert command line options to mailgun options
		$mailgunOptions = [];
		if (isset($from)) $mailgunOptions['from'] = $from;
		if (isset($options['from'])) $mailgunOptions['from'] = $options['from'];
		if (isset($options['subject'])) $mailgunOptions['subject'] = $options['subject'];
		if (isset($options['to'])) $mailgunOptions['to'] = $options['to'];
		if (isset($options['cc'])) $mailgunOptions['cc'] = $options['cc'];
		if (isset($options['bcc'])) $mailgunOptions['bcc'] = $options['bcc'];
		if (isset($options['text'])) $mailgunOptions['text'] = $options['text'];
		if (isset($options['html'])) $mailgunOptions['html'] = $options['html'];

		// Mailgun will not send with empty body...lets change that
		if (isset($mailgunOptions['text']) && $mailgunOptions['text'] == "") $mailgunOptions['text'] = " ";
		if (isset($mailgunOptions['html']) && $mailgunOptions['html'] == "") $mailgunOptions['html'] = " ";

		#var_dump($mailgunOptions);
		#exit();

		// Add any file attachments
		$files = [];
		if (isset($options['file'])) $files['attachment'] = $options['file'];

		// Send with mailgun
		$response = $mailgun->sendMessage($domain, $mailgunOptions, $files);
		$status = $response->http_response_body->message;

		// If there is a mailgun error, it will throw error and die before it returns true
		return true;
	}
}
<?php namespace Mreschke\Email;

use Swift_Mailer;
use Swift_Message;
use Mailgun\Mailgun;
use Swift_Attachment;
use Swift_SmtpTransport;
use Http\Adapter\Guzzle6\Client as Guzzle;

class SendSmtpEmail extends SendEmail
{
    /**
     * Class handler
     * @param  array $options
     * @param  array $config
     * @return mixed
     */
    public function handle($options, $config)
    {
        // Call parent handler
        parent::handle($options, $config);

        // Config options
        $driver = 'smtp';
        $server = $config[$driver]['server'];
        $port = $config[$driver]['port'];
        $username = $config[$driver]['username'];
        $password = $config[$driver]['password'];
        $ssl = $config[$driver]['ssl'];

        $from = isset($options['from']) ? $options['from'] : $config['from'];
        if (preg_match('/</', $from)) {
            // From format: Matthew Reschke <mail@example.com>
            list($fromName, $fromAddress) = explode('<', $from);
            $fromName = trim($fromName);
            $fromAddress = trim($fromAddress);
            $fromAddress = preg_replace('/>/', '', $fromAddress);
        }
        $subject = isset($options['subject']) ? $options['subject'] : "";
        $body = isset($options['html']) ? $options['html'] : (isset($options['text']) ? $options['text'] : 'x');
        $to = isset($options['to']) ? $options['to'] : null;
        $cc = isset($options['cc']) ? $options['cc'] : null;
        $bcc = isset($options['bcc']) ? $options['bcc'] : null;
        


        // Create the Transport
        $ssl = $ssl == true ? 'ssl' : null;
        $transport = Swift_SmtpTransport::newInstance($server, $port, $ssl)->setUsername($username)->setPassword($password);

        // Create the Mailer using your created Transport
        $mailer = Swift_Mailer::newInstance($transport);

        // Create message
        $message = Swift_Message::newInstance($options['subject']);
        $message->setTo($to);
        if (isset($fromName)) {
            $message->setFrom([$fromAddress => $fromName]);
        } else {
            $message->setFrom($from);
        }
        $message->setBody($body);
        
        if ($cc) $message->setCc($cc);
        if ($bcc) $message->setBcc($bcc);
        if (isset($options['file'])) {
            foreach ($options['file'] as $file) {
                $message->attach(Swift_Attachment::fromPath($file));
            }
        }

        // Send the message
        if (!$mailer->send($message, $failures)) {
            echo "Failures:";
            print_r($failures);
            return false;
        } else {
            // If there is a swift error, it will throw error and die before it returns true
            return true;
        }
    }
}
<?php namespace Mreschke\Email;

class ShowUsage
{
	/**
	 * Class handler
	 * @return string
	 */
	public function handle()
	{

return "Email console command
Copyright (C) 2016 mReschke.com
This program may be freely redistributed under the terms of the MIT license.

Send email from the command line using PHP's smtp, mailgun, mandril or other drivers.

Options:
 --to             Add recipients (one or more --to or , separated string)
 --cc             Add cc recipients (one or more --cc or , separated string)
 --bcc            Add bcc recipients (one or more --bcc or , separated string)
 --subject        Email subject
 --text           Email body (as text not html)
 --html, --body   Email body (as html)
 --file           Add attachment (one or more --file or , separated string)
 --from           Override from address defined in config file
 --driver         Override driver defined in config file
 -v, --version    Show version
 -h, --help       Show help and usage

If no --text, --html or --body is define, it will assume STDIN

Examples:
 email --to=me@mail.com --subject='Hi' --body='Sup'
 email --to=me@mail.com --subject='Hi' --text=\"$(cat /tmp/file.txt)\"
 cat /tmp/file | email --to=me@mail.com --subject='Hi'
 uname -a | email --to=me@mail.com --subject='Hi'
 email --to=me@mail.com --bcc=you@mail.com --subject='Hi' --body='There' --file=/tmp/file.txt
 email --to=me@mail.com,you@mail.com --subject='Hi' --body='There'
 email --to=me@mail.com --to=you@mail.com --subject='Hi' --body='There'
 email --to=me@mail.com --subject='Hi' --body='There' --from='Me <me@me.com>'
 email --to=me@mail.com --subject='Hi' --body='There' --driver=smtp

There is also a shorthand notation without using any -- (last file argument is optional)
 email me@mail.com 'Subject' 'Body Here'
 email me@mail.com,email2@mail.com 'Subject' 'Body Here'
 email me@mail.com 'Subject' 'Body Here' /tmp/file.txt
 email me@mail.com 'Subject' 'Body Here' /tmp/file.txt,/tmp/file2.txt

";
	}
}
<?php namespace Mreschke\Email;

// Require composer autoloader
require __DIR__.'/../vendor/autoload.php';

// Load helpers
require __DIR__.'/../src/Helpers.php';

// Define and load config file
$config = (new LoadConfig())->handle();

// Get supplied command line options and arguments
$options = (new DefineOptions())->handle($argv);

// Handle help and usage
if (empty($options) || isset($options['h']) || isset($options['help'])) {
	exit((new ShowUsage())->handle());
}

// Check if body from STDIN
if (!isset($options['text']) && !isset($options['html'])) {
	$stdin = '';
	$fh = fopen('php://stdin', 'r');
	if($fh) {
		while ($line = fgets( $fh )) {
			$stdin .= $line;
		}
		fclose($fh);
	}
	$options['text'] = $stdin;
	unset($options['html']);
}

// Send email through defined mail driver
$driver = isset($options['driver']) ? ucfirst($options['driver']) : ucfirst($config['driver']);
$class = "Mreschke\Email\Send${driver}Email";
$success = (new $class())->handle($options, $config);
if ($success) {
	echo "Email queued successfully!";
}
echo "\n";
/qZKCϏNJǙ   GBMB