Various...

This commit is contained in:
andre.peters
2017-12-09 13:17:15 +01:00
parent 5f5b6652a1
commit adc23d86f9
70 changed files with 6008 additions and 1381 deletions

View File

@@ -7,4 +7,5 @@ $baseDir = dirname($vendorDir);
return array(
'RobThree\\Auth\\' => array($vendorDir . '/robthree/twofactorauth/lib'),
'PhpMimeMailParser\\' => array($vendorDir . '/php-mime-mail-parser/php-mime-mail-parser/src'),
);

View File

@@ -11,6 +11,10 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
array (
'RobThree\\Auth\\' => 14,
),
'P' =>
array (
'PhpMimeMailParser\\' => 18,
),
);
public static $prefixDirsPsr4 = array (
@@ -18,6 +22,10 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
array (
0 => __DIR__ . '/..' . '/robthree/twofactorauth/lib',
),
'PhpMimeMailParser\\' =>
array (
0 => __DIR__ . '/..' . '/php-mime-mail-parser/php-mime-mail-parser/src',
),
);
public static $classMap = array (

View File

@@ -1,4 +1,57 @@
[
{
"name": "robthree/twofactorauth",
"version": "1.6",
"version_normalized": "1.6.0.0",
"source": {
"type": "git",
"url": "https://github.com/RobThree/TwoFactorAuth.git",
"reference": "5093ab230cd8f1296d792afb6a49545f37e7fd5a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/5093ab230cd8f1296d792afb6a49545f37e7fd5a",
"reference": "5093ab230cd8f1296d792afb6a49545f37e7fd5a",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "@stable"
},
"time": "2017-02-17T15:24:54+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"RobThree\\Auth\\": "lib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Rob Janssen",
"homepage": "http://robiii.me",
"role": "Developer"
}
],
"description": "Two Factor Authentication",
"homepage": "https://github.com/RobThree/TwoFactorAuth",
"keywords": [
"Authentication",
"MFA",
"Multi Factor Authentication",
"Two Factor Authentication",
"authenticator",
"authy",
"php",
"tfa"
]
},
{
"name": "yubico/u2flib-server",
"version": "1.0.1",
@@ -36,72 +89,19 @@
"description": "Library for U2F implementation",
"homepage": "https://developers.yubico.com/php-u2flib-server"
},
{
"name": "robthree/twofactorauth",
"version": "1.6.1",
"version_normalized": "1.6.1.0",
"source": {
"type": "git",
"url": "https://github.com/RobThree/TwoFactorAuth.git",
"reference": "a77e7d822343bb88112baef808839cfae7bc5abb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/RobThree/TwoFactorAuth/zipball/a77e7d822343bb88112baef808839cfae7bc5abb",
"reference": "a77e7d822343bb88112baef808839cfae7bc5abb",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "@stable"
},
"time": "2017-11-06T17:55:56+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"RobThree\\Auth\\": "lib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Rob Janssen",
"homepage": "http://robiii.me",
"role": "Developer"
}
],
"description": "Two Factor Authentication",
"homepage": "https://github.com/RobThree/TwoFactorAuth",
"keywords": [
"Authentication",
"MFA",
"Multi Factor Authentication",
"Two Factor Authentication",
"authenticator",
"authy",
"php",
"tfa"
]
},
{
"name": "phpmailer/phpmailer",
"version": "v5.2.26",
"version_normalized": "5.2.26.0",
"version": "v5.2.25",
"version_normalized": "5.2.25.0",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "70362997bda4376378be7d92d81e2200550923f7"
"reference": "2baf20b01690fba8cf720c1ebcf9b988eda50915"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/70362997bda4376378be7d92d81e2200550923f7",
"reference": "70362997bda4376378be7d92d81e2200550923f7",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2baf20b01690fba8cf720c1ebcf9b988eda50915",
"reference": "2baf20b01690fba8cf720c1ebcf9b988eda50915",
"shasum": ""
},
"require": {
@@ -131,7 +131,7 @@
"suggest": {
"league/oauth2-google": "Needed for Google XOAUTH2 authentication"
},
"time": "2017-11-04T09:26:05+00:00",
"time": "2017-08-28T11:12:07+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -167,5 +167,87 @@
}
],
"description": "PHPMailer is a full-featured email creation and transfer class for PHP"
},
{
"name": "php-mime-mail-parser/php-mime-mail-parser",
"version": "2.9.3",
"version_normalized": "2.9.3.0",
"source": {
"type": "git",
"url": "https://github.com/php-mime-mail-parser/php-mime-mail-parser.git",
"reference": "c6884c7bc77adbf55979db99841195b232fd30f1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-mime-mail-parser/php-mime-mail-parser/zipball/c6884c7bc77adbf55979db99841195b232fd30f1",
"reference": "c6884c7bc77adbf55979db99841195b232fd30f1",
"shasum": ""
},
"require": {
"ext-mailparse": "*",
"php": "^5.4.0 || ^7.0"
},
"replace": {
"exorus/php-mime-mail-parser": "*",
"messaged/php-mime-mail-parser": "*"
},
"require-dev": {
"phpunit/php-token-stream": "^1.3.0",
"phpunit/phpunit": "^4.0 || ^5.0",
"satooshi/php-coveralls": "0.*",
"squizlabs/php_codesniffer": "2.*"
},
"time": "2017-11-02T05:49:00+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"PhpMimeMailParser\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "bucabay",
"email": "gabe@fijiwebdesign.com",
"homepage": "http://www.fijiwebdesign.com",
"role": "Developer"
},
{
"name": "eXorus",
"email": "exorus.spam@gmail.com",
"homepage": "https://github.com/eXorus/",
"role": "Developer"
},
{
"name": "M.Valinskis",
"email": "M.Valins@gmail.com",
"homepage": "https://code.google.com/p/php-mime-mail-parser",
"role": "Developer"
},
{
"name": "eugene.emmett.wood",
"email": "gene_w@cementhorizon.com",
"homepage": "https://code.google.com/p/php-mime-mail-parser",
"role": "Developer"
},
{
"name": "alknetso",
"email": "alkne@gmail.com",
"homepage": "https://code.google.com/p/php-mime-mail-parser",
"role": "Developer"
}
],
"description": "Fully Tested Mailparse Extension Wrapper for PHP 5.4+",
"homepage": "https://github.com/php-mime-mail-parser/php-mime-mail-parser",
"keywords": [
"MimeMailParser",
"mail",
"mailparse",
"mime"
]
}
]

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Vincent Dauce
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,167 @@
# php-mime-mail-parser
A fully tested mailparse extension wrapper for PHP 5.4+
[![Latest Version](https://img.shields.io/packagist/v/php-mime-mail-parser/php-mime-mail-parser.svg?style=flat-square)](https://github.com/php-mime-mail-parser/php-mime-mail-parser/releases)
[![Total Downloads](https://img.shields.io/packagist/dt/php-mime-mail-parser/php-mime-mail-parser.svg?style=flat-square)](https://packagist.org/packages/php-mime-mail-parser/php-mime-mail-parser)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
## Why?
This extension can be used to...
* Parse and read email from Postfix
* Create webmail
* Store email information such a subject, HTML body, attachments, and etc. into a database
## Is it reliable?
Yes. All known issues have been reproduced, fixed and tested.
We use Travis CI to help ensure code quality. You can see real-time statistics below:
[![Build Status](https://img.shields.io/travis/php-mime-mail-parser/php-mime-mail-parser/master.svg?style=flat-square)](https://travis-ci.org/php-mime-mail-parser/php-mime-mail-parser)
[![Coverage](https://img.shields.io/coveralls/php-mime-mail-parser/php-mime-mail-parser.svg?style=flat-square)](https://coveralls.io/r/php-mime-mail-parser/php-mime-mail-parser)
[![Quality Score](https://img.shields.io/scrutinizer/g/php-mime-mail-parser/php-mime-mail-parser.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-mime-mail-parser/php-mime-mail-parser)
## How do I install it?
The easiest way is via [Composer](https://getcomposer.org/).
To install the latest version of PHP MIME Mail Parser, run the command below:
composer require php-mime-mail-parser/php-mime-mail-parser
## Requirements
The following versions of PHP are supported:
* PHP 5.4
* PHP 5.5
* PHP 5.6
* PHP 7
* HHVM
```
sudo apt install php-cli php-pear php-dev php-mbstring
```
Make sure you have the mailparse extension (http://php.net/manual/en/book.mailparse.php) properly installed. The command line `php -m | grep mailparse` need to return "mailparse" else install it:
* PHP version > 7.0: mailparse
* PHP version < 7.0: mailparse 2.1.6
Follow this steps to install mailparse:
* Compile in the temp folder the extension mailparse or mailparse-2.1.6 (workaround because pecl install doesn't work yet)
```
cd
pecl download mailparse
tar -xvf mailparse-3.0.2.tgz
cd mailparse-3.0.2/
phpize
./configure
sed -i 's/#if\s!HAVE_MBSTRING/#ifndef MBFL_MBFILTER_H/' ./mailparse.c
make
sudo mv modules/mailparse.so /usr/lib/php/20160303/
```
* Add the extension mailparse and activate it
```
echo "extension=mailparse.so" | sudo tee /etc/php/7.1/mods-available/mailparse.ini
sudo phpenmod mailparse
```
On Windows, you need to download mailparse DLL from http://pecl.php.net/package/mailparse and add the line "extension=php_mailparse.dll" to php.ini accordingly.
## How do I use it?
```php
<?php
// Include the library first
require_once __DIR__.'/vendor/autoload.php';
$path = 'path/to/mail.txt';
$Parser = new PhpMimeMailParser\Parser();
// There are four methods available to indicate which mime mail to parse.
// You only need to use one of the following four:
// 1. Specify a file path to the mime mail.
$Parser->setPath($path);
// 2. Specify a php file resource (stream) to the mime mail.
$Parser->setStream(fopen($path, "r"));
// 3. Specify the raw mime mail text.
$Parser->setText(file_get_contents($path));
// 4. Specify a stream to work with mail server
$Parser->setStream(fopen("php://stdin", "r"));
// Once we've indicated where to find the mail, we can parse out the data
$to = $Parser->getHeader('to'); // "test" <test@example.com>, "test2" <test2@example.com>
$addressesTo = $Parser->getAddresses('to'); //Return an array : [[test, test@example.com, false],[test2, test2@example.com, false]]
$from = $Parser->getHeader('from'); // "test" <test@example.com>
$addressesFrom = $Parser->getAddresses('from'); //Return an array : test, test@example.com, false
$subject = $Parser->getHeader('subject');
$text = $Parser->getMessageBody('text');
$html = $Parser->getMessageBody('html');
$htmlEmbedded = $Parser->getMessageBody('htmlEmbedded'); //HTML Body included data
$stringHeaders = $Parser->getHeadersRaw(); // Get all headers as a string, no charset conversion
$arrayHeaders = $Parser->getHeaders(); // Get all headers as an array, with charset conversion
// Pass in a writeable path to save attachments
$attach_dir = '/path/to/save/attachments/'; // Be sure to include the trailing slash
$include_inline = true; // Optional argument to include inline attachments (default: true)
$Parser->saveAttachments($attach_dir [,$include_inline]);
// Get an array of Attachment items from $Parser
$attachments = $Parser->getAttachments([$include_inline]);
// Loop through all the Attachments
if (count($attachments) > 0) {
foreach ($attachments as $attachment) {
echo 'Filename : '.$attachment->getFilename().'<br />'; // logo.jpg
echo 'Filesize : '.filesize($attach_dir.$attachment->getFilename()).'<br />'; // 1000
echo 'Filetype : '.$attachment->getContentType().'<br />'; // image/jpeg
echo 'MIME part string : '.$attachment->getMimePartStr().'<br />'; // (the whole MIME part of the attachment)
}
}
?>
```
Next you need to forward emails to this script above. For that I'm using [Postfix](http://www.postfix.org/) like a mail server, you need to configure /etc/postfix/master.cf
Add this line at the end of the file (specify myhook to send all emails to the script test.php)
```
myhook unix - n n - - pipe
flags=F user=www-data argv=php -c /etc/php5/apache2/php.ini -f /var/www/test.php ${sender} ${size} ${recipient}
```
Edit this line (register myhook)
```
smtp inet n - - - - smtpd
-o content_filter=myhook:dummy
```
The php script must use the fourth method to work with this configuration.
## Can I contribute?
Feel free to contribute!
git clone https://github.com/php-mime-mail-parser/php-mime-mail-parser
cd php-mime-mail-parser
composer install
./vendor/bin/phpunit
If you report an issue, please provide the raw email that triggered it. This helps us reproduce the issue and fix it more quickly.
### License
The php-mime-mail-parser/php-mime-mail-parser is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT)

View File

@@ -0,0 +1,61 @@
{
"name": "php-mime-mail-parser/php-mime-mail-parser",
"type": "library",
"description": "Fully Tested Mailparse Extension Wrapper for PHP 5.4+",
"keywords": ["mime", "mail", "mailparse", "MimeMailParser"],
"homepage": "https://github.com/php-mime-mail-parser/php-mime-mail-parser",
"license": "MIT",
"authors": [
{
"name":"eXorus",
"email":"exorus.spam@gmail.com",
"homepage":"https://github.com/eXorus/",
"role":"Developer"
},
{
"name":"M.Valinskis",
"email":"M.Valins@gmail.com",
"homepage":"https://code.google.com/p/php-mime-mail-parser",
"role":"Developer"
},
{
"name":"eugene.emmett.wood",
"email":"gene_w@cementhorizon.com",
"homepage":"https://code.google.com/p/php-mime-mail-parser",
"role":"Developer"
},
{
"name":"alknetso",
"email":"alkne@gmail.com",
"homepage":"https://code.google.com/p/php-mime-mail-parser",
"role":"Developer"
},
{
"name":"bucabay",
"email":"gabe@fijiwebdesign.com",
"homepage":"http://www.fijiwebdesign.com",
"role":"Developer"
}
],
"repository":{
"type":"git",
"url":"https://github.com/php-mime-mail-parser/php-mime-mail-parser.git"
},
"require": {
"php": "^5.4.0 || ^7.0",
"ext-mailparse": "*"
},
"require-dev": {
"phpunit/phpunit": "^4.0 || ^5.0",
"phpunit/php-token-stream": "^1.3.0",
"satooshi/php-coveralls": "0.*",
"squizlabs/PHP_CodeSniffer": "2.*"
},
"replace": {
"exorus/php-mime-mail-parser": "*",
"messaged/php-mime-mail-parser": "*"
},
"autoload": {
"psr-4": { "PhpMimeMailParser\\": "src/" }
}
}

View File

@@ -0,0 +1,303 @@
<?php
/**
* @link http://php.net/manual/en/mailparse.constants.php
*/
define('MAILPARSE_EXTRACT_OUTPUT', 0);
/**
* @link http://php.net/manual/en/mailparse.constants.php
*/
define('MAILPARSE_EXTRACT_STREAM', 1);
/**
* @link http://php.net/manual/en/mailparse.constants.php
*/
define('MAILPARSE_EXTRACT_RETURN', 2);
/**
* Parses a file. This is the optimal way of parsing a mail file that you have on
* disk.
*
* @link http://php.net/manual/en/functions.mailparse-msg-parse-file.php
*
* @param string $filename Path to the file holding the message. The file is opened
* and streamed through the parser
*
* @return resource Returns a MIME resource representing the structure, or false on error
*/
function mailparse_msg_parse_file($filename)
{
}
/**
* .
*
* @link http://php.net/manual/en/functions.mailparse-msg-get-part.php
*
* @param resource $mimemail A valid MIME resource
* @param string $mimesection
*
* @return resource
*/
function mailparse_msg_get_part($mimemail, $mimesection)
{
}
/**
* .
*
* @link http://php.net/manual/en/functions.mailparse-msg-get-structure.php
*
* @param resource $mimemail A valid MIME resource
*
* @return array
*/
function mailparse_msg_get_structure($mimemail)
{
}
/**
* .
*
* @link http://php.net/manual/en/functions.mailparse-msg-get-part-data.php
*
* @param resource $mimemail A valid MIME resource
*
* @return array
*/
function mailparse_msg_get_part_data($mimemail)
{
}
/**
* .
*
* @link http://php.net/manual/en/functions.mailparse-msg-extract-part.php
*
* @param resource $mimemail A valid MIME resource
* @param string $msgbody
* @param callable $callbackfunc
*
* @return void
*/
function mailparse_msg_extract_part($mimemail, $msgbody, $callbackfunc)
{
}
/**
* Extracts/decodes a message section from the supplied filename.
*
* @link http://php.net/manual/en/functions.mailparse-msg-extract-part-file.php
*
* @param resource $mimemail A valid MIME resource, created with
* mailparse_msg_create
* @param mixed $filename Can be a file name or a valid stream resource
* @param callable $callbackfunc If set, this must be either a valid callback that
* will be passed the extracted section, or null to make this function return the
* extracted section
*
* @return string If $callbackfunc is not null returns true on success
*/
function mailparse_msg_extract_part_file($mimemail, $filename, $callbackfunc = false)
{
}
/**
* .
*
* @link http://php.net/manual/en/functions.mailparse-msg-extract-whole-part-file.php
*
* @param resource $mimemail A valid MIME resource
* @param string $filename
* @param callable $callbackfunc
*
* @return string
*/
function mailparse_msg_extract_whole_part_file($mimemail, $filename, $callbackfunc)
{
}
/**
* Create a MIME mail resource.
*
* @link http://php.net/manual/en/functions.mailparse-msg-create.php
* @return resource Returns a handle that can be used to parse a message
*/
function mailparse_msg_create()
{
}
/**
* Frees a MIME resource.
*
* @link http://php.net/manual/en/functions.mailparse-msg-free.php
*
* @param resource $mimemail A valid MIME resource allocated by
* mailparse_msg_create or mailparse_msg_parse_file
*
* @return bool
*/
function mailparse_msg_free($mimemail)
{
}
/**
* Incrementally parse data into the supplied mime mail resource.
*
* @link http://php.net/manual/en/functions.mailparse-msg-parse.php
*
* @param resource $mimemail A valid MIME resource
* @param string $data
*
* @return bool
*/
function mailparse_msg_parse($mimemail, $data)
{
}
/**
* Parses a RFC 822 compliant recipient list, such as that found in the To: header.
*
* @link http://php.net/manual/en/functions.mailparse-rfc822-parse-addresses.php
*
* @param string $addresses A string containing addresses, like in: Wez Furlong
* wez@example.com, doe@example.com
*
* @return array Returns an array of associative arrays with the following keys for each
* recipient: display The recipient name, for display purpose. If this part is not
* set for a recipient, this key will hold the same value as address. address The
* email address is_group true if the recipient is a newsgroup, false otherwise
*/
function mailparse_rfc822_parse_addresses($addresses)
{
}
/**
* Figures out the best way of encoding the content read from the given file
* pointer.
*
* @link http://php.net/manual/en/functions.mailparse-determine-best-xfer-encoding.php
*
* @param resource $fp A valid file pointer, which must be seek-able
*
* @return string Returns one of the character encodings supported by the mbstring module
*/
function mailparse_determine_best_xfer_encoding($fp)
{
}
/**
* Streams data from the source file pointer, apply $encoding and write to the
* destination file pointer.
*
* @link http://php.net/manual/en/functions.mailparse-stream-encode.php
*
* @param resource $sourcefp A valid file handle. The file is streamed through the
* parser
* @param resource $destfp The destination file handle in which the encoded data
* will be written
* @param string $encoding One of the character encodings supported by the mbstring
* module
*
* @return bool
*/
function mailparse_stream_encode($sourcefp, $destfp, $encoding)
{
}
/**
* Scans the data from the given file pointer and extract each embedded uuencoded
* file into a temporary file.
*
* @link http://php.net/manual/en/functions.mailparse-uudecode-all.php
*
* @param resource $fp A valid file pointer
*
* @return array Returns an array of associative arrays listing filename information.
* filename Path to the temporary file name created origfilename The original
* filename, for uuencoded parts only The first filename entry is the message body.
* The next entries are the decoded uuencoded files
*/
function mailparse_uudecode_all($fp)
{
}
/**
* @return
*/
function mailparse_test()
{
}
class mimemessage
{
/**
* @return
*/
public function mimemessage()
{
}
/**
* @return
*/
public function get_child()
{
}
/**
* @return
*/
public function get_child_count()
{
}
/**
* @return
*/
public function get_parent()
{
}
/**
* @return
*/
public function extract_headers()
{
}
/**
* @return
*/
public function extract_body()
{
}
/**
* @return
*/
public function enum_uue()
{
}
/**
* @return
*/
public function extract_uue()
{
}
/**
* @return
*/
public function remove()
{
}
/**
* @return
*/
public function add_child()
{
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" bootstrap="vendor/autoload.php">
<testsuite name="eXorus PhpMimeMailParser Test Suite">
<directory suffix="Test.php">tests</directory>
</testsuite>
</phpunit>

View File

@@ -0,0 +1,183 @@
<?php
namespace PhpMimeMailParser;
/**
* Attachment of php-mime-mail-parser
*
* Fully Tested Mailparse Extension Wrapper for PHP 5.4+
*
*/
class Attachment
{
/**
* @var string $filename Filename
*/
protected $filename;
/**
* @var string $contentType Mime Type
*/
protected $contentType;
/**
* @var string $content File Content
*/
protected $content;
/**
* @var string $contentDisposition Content-Disposition (attachment or inline)
*/
protected $contentDisposition;
/**
* @var string $contentId Content-ID
*/
protected $contentId;
/**
* @var array $headers An Array of the attachment headers
*/
protected $headers;
/**
* @var resource $stream
*/
protected $stream;
/**
* @var string $mimePartStr
*/
protected $mimePartStr;
/**
* Attachment constructor.
*
* @param string $filename
* @param string $contentType
* @param resource $stream
* @param string $contentDisposition
* @param string $contentId
* @param array $headers
* @param string $mimePartStr
*/
public function __construct(
$filename,
$contentType,
$stream,
$contentDisposition = 'attachment',
$contentId = '',
$headers = [],
$mimePartStr = ''
) {
$this->filename = $filename;
$this->contentType = $contentType;
$this->stream = $stream;
$this->content = null;
$this->contentDisposition = $contentDisposition;
$this->contentId = $contentId;
$this->headers = $headers;
$this->mimePartStr = $mimePartStr;
}
/**
* retrieve the attachment filename
*
* @return string
*/
public function getFilename()
{
return $this->filename;
}
/**
* Retrieve the Attachment Content-Type
*
* @return string
*/
public function getContentType()
{
return $this->contentType;
}
/**
* Retrieve the Attachment Content-Disposition
*
* @return string
*/
public function getContentDisposition()
{
return $this->contentDisposition;
}
/**
* Retrieve the Attachment Content-ID
*
* @return string
*/
public function getContentID()
{
return $this->contentId;
}
/**
* Retrieve the Attachment Headers
*
* @return array
*/
public function getHeaders()
{
return $this->headers;
}
/**
* Get a handle to the stream
*
* @return stream
*/
public function getStream()
{
return $this->stream;
}
/**
* Read the contents a few bytes at a time until completed
* Once read to completion, it always returns false
*
* @param int $bytes (default: 2082)
*
* @return string|bool
*/
public function read($bytes = 2082)
{
return feof($this->stream) ? false : fread($this->stream, $bytes);
}
/**
* Retrieve the file content in one go
* Once you retrieve the content you cannot use MimeMailParser_attachment::read()
*
* @return string
*/
public function getContent()
{
if ($this->content === null) {
fseek($this->stream, 0);
while (($buf = $this->read()) !== false) {
$this->content .= $buf;
}
}
return $this->content;
}
/**
* Get mime part string for this attachment
*
* @return string
*/
public function getMimePartStr()
{
return $this->mimePartStr;
}
}

View File

@@ -0,0 +1,338 @@
<?php namespace PhpMimeMailParser;
use PhpMimeMailParser\Contracts\CharsetManager;
class Charset implements CharsetManager
{
/**
* Charset Aliases
*/
private $charsetAlias = [
'ascii' => 'us-ascii',
'us-ascii' => 'us-ascii',
'ansi_x3.4-1968' => 'us-ascii',
'646' => 'us-ascii',
'iso-8859-1' => 'ISO-8859-1',
'iso-8859-2' => 'ISO-8859-2',
'iso-8859-3' => 'ISO-8859-3',
'iso-8859-4' => 'ISO-8859-4',
'iso-8859-5' => 'ISO-8859-5',
'iso-8859-6' => 'ISO-8859-6',
'iso-8859-6-i' => 'ISO-8859-6-I',
'iso-8859-6-e' => 'ISO-8859-6-E',
'iso-8859-7' => 'ISO-8859-7',
'iso-8859-8' => 'ISO-8859-8',
'iso-8859-8-i' => 'ISO-8859-8',
'iso-8859-8-e' => 'ISO-8859-8-E',
'iso-8859-9' => 'ISO-8859-9',
'iso-8859-10' => 'ISO-8859-10',
'iso-8859-11' => 'ISO-8859-11',
'iso-8859-13' => 'ISO-8859-13',
'iso-8859-14' => 'ISO-8859-14',
'iso-8859-15' => 'ISO-8859-15',
'iso-8859-16' => 'ISO-8859-16',
'iso-ir-111' => 'ISO-IR-111',
'iso-2022-cn' => 'ISO-2022-CN',
'iso-2022-cn-ext' => 'ISO-2022-CN',
'iso-2022-kr' => 'ISO-2022-KR',
'iso-2022-jp' => 'ISO-2022-JP',
'utf-16be' => 'UTF-16BE',
'utf-16le' => 'UTF-16LE',
'utf-16' => 'UTF-16',
'windows-1250' => 'windows-1250',
'windows-1251' => 'windows-1251',
'windows-1252' => 'windows-1252',
'windows-1253' => 'windows-1253',
'windows-1254' => 'windows-1254',
'windows-1255' => 'windows-1255',
'windows-1256' => 'windows-1256',
'windows-1257' => 'windows-1257',
'windows-1258' => 'windows-1258',
'ibm866' => 'IBM866',
'ibm850' => 'IBM850',
'ibm852' => 'IBM852',
'ibm855' => 'IBM855',
'ibm857' => 'IBM857',
'ibm862' => 'IBM862',
'ibm864' => 'IBM864',
'utf-8' => 'UTF-8',
'utf-7' => 'UTF-7',
'shift_jis' => 'Shift_JIS',
'big5' => 'Big5',
'euc-jp' => 'EUC-JP',
'euc-kr' => 'EUC-KR',
'gb2312' => 'GB2312',
'gb18030' => 'gb18030',
'viscii' => 'VISCII',
'koi8-r' => 'KOI8-R',
'koi8_r' => 'KOI8-R',
'cskoi8r' => 'KOI8-R',
'koi' => 'KOI8-R',
'koi8' => 'KOI8-R',
'koi8-u' => 'KOI8-U',
'tis-620' => 'TIS-620',
't.61-8bit' => 'T.61-8bit',
'hz-gb-2312' => 'HZ-GB-2312',
'big5-hkscs' => 'Big5-HKSCS',
'gbk' => 'gbk',
'cns11643' => 'x-euc-tw',
'x-imap4-modified-utf7' => 'x-imap4-modified-utf7',
'x-euc-tw' => 'x-euc-tw',
'x-mac-ce' => 'x-mac-ce',
'x-mac-turkish' => 'x-mac-turkish',
'x-mac-greek' => 'x-mac-greek',
'x-mac-icelandic' => 'x-mac-icelandic',
'x-mac-croatian' => 'x-mac-croatian',
'x-mac-romanian' => 'x-mac-romanian',
'x-mac-cyrillic' => 'x-mac-cyrillic',
'x-mac-ukrainian' => 'x-mac-cyrillic',
'x-mac-hebrew' => 'x-mac-hebrew',
'x-mac-arabic' => 'x-mac-arabic',
'x-mac-farsi' => 'x-mac-farsi',
'x-mac-devanagari' => 'x-mac-devanagari',
'x-mac-gujarati' => 'x-mac-gujarati',
'x-mac-gurmukhi' => 'x-mac-gurmukhi',
'armscii-8' => 'armscii-8',
'x-viet-tcvn5712' => 'x-viet-tcvn5712',
'x-viet-vps' => 'x-viet-vps',
'iso-10646-ucs-2' => 'UTF-16BE',
'x-iso-10646-ucs-2-be' => 'UTF-16BE',
'x-iso-10646-ucs-2-le' => 'UTF-16LE',
'x-user-defined' => 'x-user-defined',
'x-johab' => 'x-johab',
'latin1' => 'ISO-8859-1',
'iso_8859-1' => 'ISO-8859-1',
'iso8859-1' => 'ISO-8859-1',
'iso8859-2' => 'ISO-8859-2',
'iso8859-3' => 'ISO-8859-3',
'iso8859-4' => 'ISO-8859-4',
'iso8859-5' => 'ISO-8859-5',
'iso8859-6' => 'ISO-8859-6',
'iso8859-7' => 'ISO-8859-7',
'iso8859-8' => 'ISO-8859-8',
'iso8859-9' => 'ISO-8859-9',
'iso8859-10' => 'ISO-8859-10',
'iso8859-11' => 'ISO-8859-11',
'iso8859-13' => 'ISO-8859-13',
'iso8859-14' => 'ISO-8859-14',
'iso8859-15' => 'ISO-8859-15',
'iso_8859-1:1987' => 'ISO-8859-1',
'iso-ir-100' => 'ISO-8859-1',
'l1' => 'ISO-8859-1',
'ibm819' => 'ISO-8859-1',
'cp819' => 'ISO-8859-1',
'csisolatin1' => 'ISO-8859-1',
'latin2' => 'ISO-8859-2',
'iso_8859-2' => 'ISO-8859-2',
'iso_8859-2:1987' => 'ISO-8859-2',
'iso-ir-101' => 'ISO-8859-2',
'l2' => 'ISO-8859-2',
'csisolatin2' => 'ISO-8859-2',
'latin3' => 'ISO-8859-3',
'iso_8859-3' => 'ISO-8859-3',
'iso_8859-3:1988' => 'ISO-8859-3',
'iso-ir-109' => 'ISO-8859-3',
'l3' => 'ISO-8859-3',
'csisolatin3' => 'ISO-8859-3',
'latin4' => 'ISO-8859-4',
'iso_8859-4' => 'ISO-8859-4',
'iso_8859-4:1988' => 'ISO-8859-4',
'iso-ir-110' => 'ISO-8859-4',
'l4' => 'ISO-8859-4',
'csisolatin4' => 'ISO-8859-4',
'cyrillic' => 'ISO-8859-5',
'iso_8859-5' => 'ISO-8859-5',
'iso_8859-5:1988' => 'ISO-8859-5',
'iso-ir-144' => 'ISO-8859-5',
'csisolatincyrillic' => 'ISO-8859-5',
'arabic' => 'ISO-8859-6',
'iso_8859-6' => 'ISO-8859-6',
'iso_8859-6:1987' => 'ISO-8859-6',
'iso-ir-127' => 'ISO-8859-6',
'ecma-114' => 'ISO-8859-6',
'asmo-708' => 'ISO-8859-6',
'csisolatinarabic' => 'ISO-8859-6',
'csiso88596i' => 'ISO-8859-6-I',
'csiso88596e' => 'ISO-8859-6-E',
'greek' => 'ISO-8859-7',
'greek8' => 'ISO-8859-7',
'sun_eu_greek' => 'ISO-8859-7',
'iso_8859-7' => 'ISO-8859-7',
'iso_8859-7:1987' => 'ISO-8859-7',
'iso-ir-126' => 'ISO-8859-7',
'elot_928' => 'ISO-8859-7',
'ecma-118' => 'ISO-8859-7',
'csisolatingreek' => 'ISO-8859-7',
'hebrew' => 'ISO-8859-8',
'iso_8859-8' => 'ISO-8859-8',
'visual' => 'ISO-8859-8',
'iso_8859-8:1988' => 'ISO-8859-8',
'iso-ir-138' => 'ISO-8859-8',
'csisolatinhebrew' => 'ISO-8859-8',
'csiso88598i' => 'ISO-8859-8',
'iso-8859-8i' => 'ISO-8859-8',
'logical' => 'ISO-8859-8',
'csiso88598e' => 'ISO-8859-8-E',
'latin5' => 'ISO-8859-9',
'iso_8859-9' => 'ISO-8859-9',
'iso_8859-9:1989' => 'ISO-8859-9',
'iso-ir-148' => 'ISO-8859-9',
'l5' => 'ISO-8859-9',
'csisolatin5' => 'ISO-8859-9',
'unicode-1-1-utf-8' => 'UTF-8',
'utf8' => 'UTF-8',
'x-sjis' => 'Shift_JIS',
'shift-jis' => 'Shift_JIS',
'ms_kanji' => 'Shift_JIS',
'csshiftjis' => 'Shift_JIS',
'windows-31j' => 'Shift_JIS',
'cp932' => 'Shift_JIS',
'sjis' => 'Shift_JIS',
'cseucpkdfmtjapanese' => 'EUC-JP',
'x-euc-jp' => 'EUC-JP',
'csiso2022jp' => 'ISO-2022-JP',
'iso-2022-jp-2' => 'ISO-2022-JP',
'csiso2022jp2' => 'ISO-2022-JP',
'csbig5' => 'Big5',
'cn-big5' => 'Big5',
'x-x-big5' => 'Big5',
'zh_tw-big5' => 'Big5',
'cseuckr' => 'EUC-KR',
'ks_c_5601-1987' => 'EUC-KR',
'iso-ir-149' => 'EUC-KR',
'ks_c_5601-1989' => 'EUC-KR',
'ksc_5601' => 'EUC-KR',
'ksc5601' => 'EUC-KR',
'korean' => 'EUC-KR',
'csksc56011987' => 'EUC-KR',
'5601' => 'EUC-KR',
'windows-949' => 'EUC-KR',
'gb_2312-80' => 'GB2312',
'iso-ir-58' => 'GB2312',
'chinese' => 'GB2312',
'csiso58gb231280' => 'GB2312',
'csgb2312' => 'GB2312',
'zh_cn.euc' => 'GB2312',
'gb_2312' => 'GB2312',
'x-cp1250' => 'windows-1250',
'x-cp1251' => 'windows-1251',
'x-cp1252' => 'windows-1252',
'x-cp1253' => 'windows-1253',
'x-cp1254' => 'windows-1254',
'x-cp1255' => 'windows-1255',
'x-cp1256' => 'windows-1256',
'x-cp1257' => 'windows-1257',
'x-cp1258' => 'windows-1258',
'windows-874' => 'windows-874',
'ibm874' => 'windows-874',
'dos-874' => 'windows-874',
'macintosh' => 'macintosh',
'x-mac-roman' => 'macintosh',
'mac' => 'macintosh',
'csmacintosh' => 'macintosh',
'cp866' => 'IBM866',
'cp-866' => 'IBM866',
'866' => 'IBM866',
'csibm866' => 'IBM866',
'cp850' => 'IBM850',
'850' => 'IBM850',
'csibm850' => 'IBM850',
'cp852' => 'IBM852',
'852' => 'IBM852',
'csibm852' => 'IBM852',
'cp855' => 'IBM855',
'855' => 'IBM855',
'csibm855' => 'IBM855',
'cp857' => 'IBM857',
'857' => 'IBM857',
'csibm857' => 'IBM857',
'cp862' => 'IBM862',
'862' => 'IBM862',
'csibm862' => 'IBM862',
'cp864' => 'IBM864',
'864' => 'IBM864',
'csibm864' => 'IBM864',
'ibm-864' => 'IBM864',
't.61' => 'T.61-8bit',
'iso-ir-103' => 'T.61-8bit',
'csiso103t618bit' => 'T.61-8bit',
'x-unicode-2-0-utf-7' => 'UTF-7',
'unicode-2-0-utf-7' => 'UTF-7',
'unicode-1-1-utf-7' => 'UTF-7',
'csunicode11utf7' => 'UTF-7',
'csunicode' => 'UTF-16BE',
'csunicode11' => 'UTF-16BE',
'iso-10646-ucs-basic' => 'UTF-16BE',
'csunicodeascii' => 'UTF-16BE',
'iso-10646-unicode-latin1' => 'UTF-16BE',
'csunicodelatin1' => 'UTF-16BE',
'iso-10646' => 'UTF-16BE',
'iso-10646-j-1' => 'UTF-16BE',
'latin6' => 'ISO-8859-10',
'iso-ir-157' => 'ISO-8859-10',
'l6' => 'ISO-8859-10',
'csisolatin6' => 'ISO-8859-10',
'iso_8859-15' => 'ISO-8859-15',
'csisolatin9' => 'ISO-8859-15',
'l9' => 'ISO-8859-15',
'ecma-cyrillic' => 'ISO-IR-111',
'csiso111ecmacyrillic' => 'ISO-IR-111',
'csiso2022kr' => 'ISO-2022-KR',
'csviscii' => 'VISCII',
'zh_tw-euc' => 'x-euc-tw',
'iso88591' => 'ISO-8859-1',
'iso88592' => 'ISO-8859-2',
'iso88593' => 'ISO-8859-3',
'iso88594' => 'ISO-8859-4',
'iso88595' => 'ISO-8859-5',
'iso88596' => 'ISO-8859-6',
'iso88597' => 'ISO-8859-7',
'iso88598' => 'ISO-8859-8',
'iso88599' => 'ISO-8859-9',
'iso885910' => 'ISO-8859-10',
'iso885911' => 'ISO-8859-11',
'iso885912' => 'ISO-8859-12',
'iso885913' => 'ISO-8859-13',
'iso885914' => 'ISO-8859-14',
'iso885915' => 'ISO-8859-15',
'tis620' => 'TIS-620',
'cp1250' => 'windows-1250',
'cp1251' => 'windows-1251',
'cp1252' => 'windows-1252',
'cp1253' => 'windows-1253',
'cp1254' => 'windows-1254',
'cp1255' => 'windows-1255',
'cp1256' => 'windows-1256',
'cp1257' => 'windows-1257',
'cp1258' => 'windows-1258',
'x-gbk' => 'gbk',
'windows-936' => 'gbk',
'ansi-1251' => 'windows-1251',
];
/**
* {@inheritdoc}
*/
public function decodeCharset($encodedString, $charset)
{
if (strtolower($charset) == 'utf-8' || strtolower($charset) == 'us-ascii') {
return $encodedString;
} else {
return iconv($this->getCharsetAlias($charset), 'UTF-8//TRANSLIT//IGNORE', $encodedString);
}
}
/**
* {@inheritdoc}
*/
public function getCharsetAlias($charset)
{
$charset = strtolower($charset);
if (array_key_exists($charset, $this->charsetAlias)) {
return $this->charsetAlias[$charset];
} else {
return null;
}
}
}

View File

@@ -0,0 +1,24 @@
<?php namespace PhpMimeMailParser\Contracts;
interface CharsetManager
{
/**
* Decode the string from Charset
*
* @param string $encodedString The string in its original encoded state
* @param string $charset The Charset header of the part.
*
* @return string The decoded string
*/
public function decodeCharset($encodedString, $charset);
/**
* Get charset alias
*
* @param string $charset .
*
* @return string The charset alias
*/
public function getCharsetAlias($charset);
}

View File

@@ -0,0 +1,8 @@
<?php
namespace PhpMimeMailParser;
class Exception extends \RuntimeException
{
}

View File

@@ -0,0 +1,893 @@
<?php
namespace PhpMimeMailParser;
use PhpMimeMailParser\Contracts\CharsetManager;
/**
* Parser of php-mime-mail-parser
*
* Fully Tested Mailparse Extension Wrapper for PHP 5.4+
*
*/
class Parser
{
/**
* Attachment filename argument option for ->saveAttachments().
*/
const ATTACHMENT_DUPLICATE_THROW = 'DuplicateThrow';
const ATTACHMENT_DUPLICATE_SUFFIX = 'DuplicateSuffix';
const ATTACHMENT_RANDOM_FILENAME = 'RandomFilename';
/**
* PHP MimeParser Resource ID
*
* @var resource $resource
*/
protected $resource;
/**
* A file pointer to email
*
* @var resource $stream
*/
protected $stream;
/**
* A text of an email
*
* @var string $data
*/
protected $data;
/**
* Parts of an email
*
* @var array $parts
*/
protected $parts;
/**
* @var CharsetManager object
*/
protected $charset;
/**
* Parser constructor.
*
* @param CharsetManager|null $charset
*/
public function __construct(CharsetManager $charset = null)
{
if ($charset == null) {
$charset = new Charset();
}
$this->charset = $charset;
}
/**
* Free the held resources
*
* @return void
*/
public function __destruct()
{
// clear the email file resource
if (is_resource($this->stream)) {
fclose($this->stream);
}
// clear the MailParse resource
if (is_resource($this->resource)) {
mailparse_msg_free($this->resource);
}
}
/**
* Set the file path we use to get the email text
*
* @param string $path File path to the MIME mail
*
* @return Parser MimeMailParser Instance
*/
public function setPath($path)
{
// should parse message incrementally from file
$this->resource = mailparse_msg_parse_file($path);
$this->stream = fopen($path, 'r');
$this->parse();
return $this;
}
/**
* Set the Stream resource we use to get the email text
*
* @param resource $stream
*
* @return Parser MimeMailParser Instance
* @throws Exception
*/
public function setStream($stream)
{
// streams have to be cached to file first
$meta = @stream_get_meta_data($stream);
if (!$meta || !$meta['mode'] || $meta['mode'][0] != 'r' || $meta['eof']) {
throw new Exception(
'setStream() expects parameter stream to be readable stream resource.'
);
}
/** @var resource $tmp_fp */
$tmp_fp = tmpfile();
if ($tmp_fp) {
while (!feof($stream)) {
fwrite($tmp_fp, fread($stream, 2028));
}
fseek($tmp_fp, 0);
$this->stream = &$tmp_fp;
} else {
throw new Exception(
'Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.'
);
}
fclose($stream);
$this->resource = mailparse_msg_create();
// parses the message incrementally (low memory usage but slower)
while (!feof($this->stream)) {
mailparse_msg_parse($this->resource, fread($this->stream, 2082));
}
$this->parse();
return $this;
}
/**
* Set the email text
*
* @param string $data
*
* @return Parser MimeMailParser Instance
*/
public function setText($data)
{
if (!$data) {
throw new Exception('You must not call MimeMailParser::setText with an empty string parameter');
}
$this->resource = mailparse_msg_create();
// does not parse incrementally, fast memory hog might explode
mailparse_msg_parse($this->resource, $data);
$this->data = $data;
$this->parse();
return $this;
}
/**
* Parse the Message into parts
*
* @return void
*/
protected function parse()
{
$structure = mailparse_msg_get_structure($this->resource);
$this->parts = [];
foreach ($structure as $part_id) {
$part = mailparse_msg_get_part($this->resource, $part_id);
$this->parts[$part_id] = mailparse_msg_get_part_data($part);
}
}
/**
* Retrieve a specific Email Header, without charset conversion.
*
* @param string $name Header name (case-insensitive)
*
* @return string
* @throws Exception
*/
public function getRawHeader($name)
{
$name = strtolower($name);
if (isset($this->parts[1])) {
$headers = $this->getPart('headers', $this->parts[1]);
return (isset($headers[$name])) ? $headers[$name] : false;
} else {
throw new Exception(
'setPath() or setText() or setStream() must be called before retrieving email headers.'
);
}
}
/**
* Retrieve a specific Email Header
*
* @param string $name Header name (case-insensitive)
*
* @return string
*/
public function getHeader($name)
{
$rawHeader = $this->getRawHeader($name);
if ($rawHeader === false) {
return false;
}
return $this->decodeHeader($rawHeader);
}
/**
* Retrieve all mail headers
*
* @return array
* @throws Exception
*/
public function getHeaders()
{
if (isset($this->parts[1])) {
$headers = $this->getPart('headers', $this->parts[1]);
foreach ($headers as $name => &$value) {
if (is_array($value)) {
foreach ($value as &$v) {
$v = $this->decodeSingleHeader($v);
}
} else {
$value = $this->decodeSingleHeader($value);
}
}
return $headers;
} else {
throw new Exception(
'setPath() or setText() or setStream() must be called before retrieving email headers.'
);
}
}
/**
* Retrieve the raw mail headers as a string
*
* @return string
* @throws Exception
*/
public function getHeadersRaw()
{
if (isset($this->parts[1])) {
return $this->getPartHeader($this->parts[1]);
} else {
throw new Exception(
'setPath() or setText() or setStream() must be called before retrieving email headers.'
);
}
}
/**
* Retrieve the raw Header of a MIME part
*
* @return String
* @param $part Object
* @throws Exception
*/
protected function getPartHeader(&$part)
{
$header = '';
if ($this->stream) {
$header = $this->getPartHeaderFromFile($part);
} elseif ($this->data) {
$header = $this->getPartHeaderFromText($part);
}
return $header;
}
/**
* Retrieve the Header from a MIME part from file
*
* @return String Mime Header Part
* @param $part Array
*/
protected function getPartHeaderFromFile(&$part)
{
$start = $part['starting-pos'];
$end = $part['starting-pos-body'];
fseek($this->stream, $start, SEEK_SET);
$header = fread($this->stream, $end-$start);
return $header;
}
/**
* Retrieve the Header from a MIME part from text
*
* @return String Mime Header Part
* @param $part Array
*/
protected function getPartHeaderFromText(&$part)
{
$start = $part['starting-pos'];
$end = $part['starting-pos-body'];
$header = substr($this->data, $start, $end-$start);
return $header;
}
/**
* Checks whether a given part ID is a child of another part
* eg. an RFC822 attachment may have one or more text parts
*
* @param string $partId
* @param string $parentPartId
* @return bool
*/
protected function partIdIsChildOfPart($partId, $parentPartId)
{
return substr($partId, 0, strlen($parentPartId)) == $parentPartId;
}
/**
* Whether the given part ID is a child of any attachment part in the message.
*
* @param string $checkPartId
* @return bool
*/
protected function partIdIsChildOfAnAttachment($checkPartId)
{
foreach ($this->parts as $partId => $part) {
if ($this->getPart('content-disposition', $part) == 'attachment') {
if ($this->partIdIsChildOfPart($checkPartId, $partId)) {
return true;
}
}
}
return false;
}
/**
* Returns the email message body in the specified format
*
* @param string $type text, html or htmlEmbedded
*
* @return false|string Body or False if not found
* @throws Exception
*/
public function getMessageBody($type = 'text')
{
$body = false;
$mime_types = [
'text' => 'text/plain',
'html' => 'text/html',
'htmlEmbedded' => 'text/html',
];
if (in_array($type, array_keys($mime_types))) {
$part_type = $type === 'htmlEmbedded' ? 'html' : $type;
$inline_parts = $this->getInlineParts($part_type);
$body = empty($inline_parts) ? '' : $inline_parts[0];
} else {
throw new Exception(
'Invalid type specified for getMessageBody(). Expected: text, html or htmlEmbeded.'
);
}
if ($type == 'htmlEmbedded') {
$attachments = $this->getAttachments();
foreach ($attachments as $attachment) {
if ($attachment->getContentID() != '') {
$body = str_replace(
'"cid:'.$attachment->getContentID().'"',
'"'.$this->getEmbeddedData($attachment->getContentID()).'"',
$body
);
}
}
}
return $body;
}
/**
* Returns the embedded data structure
*
* @param string $contentId Content-Id
*
* @return string
*/
protected function getEmbeddedData($contentId)
{
foreach ($this->parts as $part) {
if ($this->getPart('content-id', $part) == $contentId) {
$embeddedData = 'data:';
$embeddedData .= $this->getPart('content-type', $part);
$embeddedData .= ';'.$this->getPart('transfer-encoding', $part);
$embeddedData .= ','.$this->getPartBody($part);
return $embeddedData;
}
}
return '';
}
/**
* Return an array with the following keys display, address, is_group
*
* @param string $name Header name (case-insensitive)
*
* @return array
*/
public function getAddresses($name)
{
$value = $this->getHeader($name);
return mailparse_rfc822_parse_addresses($value);
}
/**
* Returns the attachments contents in order of appearance
*
* @return Attachment[]
*/
public function getInlineParts($type = 'text')
{
$inline_parts = [];
$dispositions = ['inline'];
$mime_types = [
'text' => 'text/plain',
'html' => 'text/html',
];
if (!in_array($type, array_keys($mime_types))) {
throw new Exception('Invalid type specified for getInlineParts(). "type" can either be text or html.');
}
foreach ($this->parts as $partId => $part) {
if ($this->getPart('content-type', $part) == $mime_types[$type]
&& $this->getPart('content-disposition', $part) != 'attachment'
&& !$this->partIdIsChildOfAnAttachment($partId)
) {
$headers = $this->getPart('headers', $part);
$encodingType = array_key_exists('content-transfer-encoding', $headers) ?
$headers['content-transfer-encoding'] : '';
if (is_array($encodingType)) {
$encodingType = $encodingType[0];
}
$undecoded_body = $this->decodeContentTransfer($this->getPartBody($part), $encodingType);
$inline_parts[] = $this->charset->decodeCharset($undecoded_body, $this->getPartCharset($part));
}
}
return $inline_parts;
}
/**
* Returns the attachments contents in order of appearance
*
* @return Attachment[]
*/
public function getAttachments($include_inline = true)
{
$attachments = [];
$dispositions = $include_inline ?
['attachment', 'inline'] :
['attachment'];
$non_attachment_types = ['text/plain', 'text/html'];
$nonameIter = 0;
foreach ($this->parts as $part) {
$disposition = $this->getPart('content-disposition', $part);
$filename = 'noname';
if (isset($part['disposition-filename'])) {
$filename = $this->decodeHeader($part['disposition-filename']);
// Escape all potentially unsafe characters from the filename
$filename = preg_replace('((^\.)|\/|(\.$))', '_', $filename);
} elseif (isset($part['content-name'])) {
// if we have no disposition but we have a content-name, it's a valid attachment.
// we simulate the presence of an attachment disposition with a disposition filename
$filename = $this->decodeHeader($part['content-name']);
// Escape all potentially unsafe characters from the filename
$filename = preg_replace('((^\.)|\/|(\.$))', '_', $filename);
$disposition = 'attachment';
} elseif (in_array($part['content-type'], $non_attachment_types, true)
&& $disposition !== 'attachment') {
// it is a message body, no attachment
continue;
} elseif (substr($part['content-type'], 0, 10) !== 'multipart/') {
// if we cannot get it by getMessageBody(), we assume it is an attachment
$disposition = 'attachment';
}
if (in_array($disposition, $dispositions) === true) {
if ($filename == 'noname') {
$nonameIter++;
$filename = 'noname'.$nonameIter;
}
$headersAttachments = $this->getPart('headers', $part);
$contentidAttachments = $this->getPart('content-id', $part);
$mimePartStr = $this->getPartComplete($part);
$attachments[] = new Attachment(
$filename,
$this->getPart('content-type', $part),
$this->getAttachmentStream($part),
$disposition,
$contentidAttachments,
$headersAttachments,
$mimePartStr
);
}
}
return $attachments;
}
/**
* Save attachments in a folder
*
* @param string $attach_dir directory
* @param bool $include_inline
* @param string $filenameStrategy How to generate attachment filenames
*
* @return array Saved attachments paths
* @throws Exception
*/
public function saveAttachments(
$attach_dir,
$include_inline = true,
$filenameStrategy = self::ATTACHMENT_DUPLICATE_SUFFIX
) {
$attachments = $this->getAttachments($include_inline);
if (empty($attachments)) {
return false;
}
if (!is_dir($attach_dir)) {
mkdir($attach_dir);
}
$attachments_paths = [];
foreach ($attachments as $attachment) {
// Determine filename
switch ($filenameStrategy) {
case self::ATTACHMENT_RANDOM_FILENAME:
$attachment_path = tempnam($attach_dir, '');
break;
case self::ATTACHMENT_DUPLICATE_THROW:
case self::ATTACHMENT_DUPLICATE_SUFFIX:
$attachment_path = $attach_dir . $attachment->getFilename();
break;
default:
throw new Exception('Invalid filename strategy argument provided.');
}
// Handle duplicate filename
if (file_exists($attachment_path)) {
switch ($filenameStrategy) {
case self::ATTACHMENT_DUPLICATE_THROW:
throw new Exception('Could not create file for attachment: duplicate filename.');
case self::ATTACHMENT_DUPLICATE_SUFFIX:
$attachment_path = tempnam($attach_dir, $attachment->getFilename());
break;
}
}
/** @var resource $fp */
if ($fp = fopen($attachment_path, 'w')) {
while ($bytes = $attachment->read()) {
fwrite($fp, $bytes);
}
fclose($fp);
$attachments_paths[] = realpath($attachment_path);
} else {
throw new Exception('Could not write attachments. Your directory may be unwritable by PHP.');
}
}
return $attachments_paths;
}
/**
* Read the attachment Body and save temporary file resource
*
* @param array $part
*
* @return resource Mime Body Part
* @throws Exception
*/
protected function getAttachmentStream(&$part)
{
/** @var resource $temp_fp */
$temp_fp = tmpfile();
$headers = $this->getPart('headers', $part);
$encodingType = array_key_exists('content-transfer-encoding', $headers) ?
$headers['content-transfer-encoding'] : '';
if ($temp_fp) {
if ($this->stream) {
$start = $part['starting-pos-body'];
$end = $part['ending-pos-body'];
fseek($this->stream, $start, SEEK_SET);
$len = $end - $start;
$written = 0;
while ($written < $len) {
$write = $len;
$part = fread($this->stream, $write);
fwrite($temp_fp, $this->decodeContentTransfer($part, $encodingType));
$written += $write;
}
} elseif ($this->data) {
$attachment = $this->decodeContentTransfer($this->getPartBodyFromText($part), $encodingType);
fwrite($temp_fp, $attachment, strlen($attachment));
}
fseek($temp_fp, 0, SEEK_SET);
} else {
throw new Exception(
'Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.'
);
}
return $temp_fp;
}
/**
* Decode the string from Content-Transfer-Encoding
*
* @param string $encodedString The string in its original encoded state
* @param string $encodingType The encoding type from the Content-Transfer-Encoding header of the part.
*
* @return string The decoded string
*/
protected function decodeContentTransfer($encodedString, $encodingType)
{
$encodingType = strtolower($encodingType);
if ($encodingType == 'base64') {
return base64_decode($encodedString);
} elseif ($encodingType == 'quoted-printable') {
return quoted_printable_decode($encodedString);
} else {
return $encodedString; //8bit, 7bit, binary
}
}
/**
* $input can be a string or array
*
* @param string|array $input
*
* @return string
*/
protected function decodeHeader($input)
{
//Sometimes we have 2 label From so we take only the first
if (is_array($input)) {
return $this->decodeSingleHeader($input[0]);
}
return $this->decodeSingleHeader($input);
}
/**
* Decodes a single header (= string)
*
* @param string $input
*
* @return string
*/
protected function decodeSingleHeader($input)
{
// For each encoded-word...
while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)((\s+)=\?)?/i', $input, $matches)) {
$encoded = $matches[1];
$charset = $matches[2];
$encoding = $matches[3];
$text = $matches[4];
$space = isset($matches[6]) ? $matches[6] : '';
switch (strtolower($encoding)) {
case 'b':
$text = $this->decodeContentTransfer($text, 'base64');
break;
case 'q':
$text = str_replace('_', ' ', $text);
preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
foreach ($matches[1] as $value) {
$text = str_replace('='.$value, chr(hexdec($value)), $text);
}
break;
}
$text = $this->charset->decodeCharset($text, $this->charset->getCharsetAlias($charset));
$input = str_replace($encoded . $space, $text, $input);
}
return $input;
}
/**
* Return the charset of the MIME part
*
* @param array $part
*
* @return string|false
*/
protected function getPartCharset($part)
{
if (isset($part['charset'])) {
return $this->charset->getCharsetAlias($part['charset']);
} else {
return false;
}
}
/**
* Retrieve a specified MIME part
*
* @param string $type
* @param array $parts
*
* @return string|array
*/
protected function getPart($type, $parts)
{
return (isset($parts[$type])) ? $parts[$type] : false;
}
/**
* Retrieve the Body of a MIME part
*
* @param array $part
*
* @return string
*/
protected function getPartBody(&$part)
{
$body = '';
if ($this->stream) {
$body = $this->getPartBodyFromFile($part);
} elseif ($this->data) {
$body = $this->getPartBodyFromText($part);
}
return $body;
}
/**
* Retrieve the Body from a MIME part from file
*
* @param array $part
*
* @return string Mime Body Part
*/
protected function getPartBodyFromFile(&$part)
{
$start = $part['starting-pos-body'];
$end = $part['ending-pos-body'];
$body = '';
if ($end - $start > 0) {
fseek($this->stream, $start, SEEK_SET);
$body = fread($this->stream, $end - $start);
}
return $body;
}
/**
* Retrieve the Body from a MIME part from text
*
* @param array $part
*
* @return string Mime Body Part
*/
protected function getPartBodyFromText(&$part)
{
$start = $part['starting-pos-body'];
$end = $part['ending-pos-body'];
return substr($this->data, $start, $end - $start);
}
/**
* Retrieve the content of a MIME part
*
* @param array $part
*
* @return string
*/
protected function getPartComplete(&$part)
{
$body = '';
if ($this->stream) {
$body = $this->getPartFromFile($part);
} elseif ($this->data) {
$body = $this->getPartFromText($part);
}
return $body;
}
/**
* Retrieve the content from a MIME part from file
*
* @param array $part
*
* @return string Mime Content
*/
protected function getPartFromFile(&$part)
{
$start = $part['starting-pos'];
$end = $part['ending-pos'];
$body = '';
if ($end - $start > 0) {
fseek($this->stream, $start, SEEK_SET);
$body = fread($this->stream, $end - $start);
}
return $body;
}
/**
* Retrieve the content from a MIME part from text
*
* @param array $part
*
* @return string Mime Content
*/
protected function getPartFromText(&$part)
{
$start = $part['starting-pos'];
$end = $part['ending-pos'];
return substr($this->data, $start, $end - $start);
}
/**
* Retrieve the resource
*
* @return resource resource
*/
public function getResource()
{
return $this->resource;
}
/**
* Retrieve the file pointer to email
*
* @return resource stream
*/
public function getStream()
{
return $this->stream;
}
/**
* Retrieve the text of an email
*
* @return string data
*/
public function getData()
{
return $this->data;
}
/**
* Retrieve the parts of an email
*
* @return array parts
*/
public function getParts()
{
return $this->parts;
}
/**
* Retrieve the charset manager object
*
* @return CharsetManager charset
*/
public function getCharset()
{
return $this->charset;
}
}

View File

@@ -1 +1 @@
5.2.26
5.2.25

View File

@@ -31,7 +31,7 @@ class PHPMailer
* The PHPMailer Version number.
* @var string
*/
public $Version = '5.2.26';
public $Version = '5.2.25';
/**
* Email priority.
@@ -659,8 +659,6 @@ class PHPMailer
if ($exceptions !== null) {
$this->exceptions = (boolean)$exceptions;
}
//Pick an appropriate debug output format automatically
$this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html');
}
/**

View File

@@ -34,7 +34,7 @@ class POP3
* @var string
* @access public
*/
public $Version = '5.2.26';
public $Version = '5.2.25';
/**
* Default POP3 port number.

View File

@@ -30,7 +30,7 @@ class SMTP
* The PHPMailer SMTP version number.
* @var string
*/
const VERSION = '5.2.26';
const VERSION = '5.2.25';
/**
* SMTP line break constant.
@@ -81,7 +81,7 @@ class SMTP
* @deprecated Use the `VERSION` constant instead
* @see SMTP::VERSION
*/
public $Version = '5.2.26';
public $Version = '5.2.25';
/**
* SMTP server port number.

View File

@@ -183,7 +183,4 @@ UpgradeLog*.htm
FakesAssemblies/
# Composer
/vendor
# .vs
.vs/
/vendor

View File

@@ -1,18 +1,11 @@
language: php
dist: trusty
matrix:
include:
- php: 5.3
dist: precise
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- 7
- hhvm
script:
- if [[ "$TRAVIS_PHP_VERSION" == '5.6' ]]; then phpunit --coverage-text tests ; fi
script: phpunit --coverage-text tests

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@ PHP library for [two-factor (or multi-factor) authentication](http://en.wikipedi
## Requirements
* Tested on PHP 5.3, 5.4, 5.5 and 5.6, 7.0, 7.1 and HHVM
* Tested on PHP 5.3, 5.4, 5.5 and 5.6, 7 and HHVM
* [cURL](http://php.net/manual/en/book.curl.php) when using the provided `GoogleQRCodeProvider` (default), `QRServerProvider` or `QRicketProvider` but you can also provide your own QR-code provider.
* [random_bytes()](http://php.net/manual/en/function.random-bytes.php), [MCrypt](http://php.net/manual/en/book.mcrypt.php), [OpenSSL](http://php.net/manual/en/book.openssl.php) or [Hash](http://php.net/manual/en/book.hash.php) depending on which built-in RNG you use (TwoFactorAuth will try to 'autodetect' and use the best available); however: feel free to provide your own (CS)RNG.

View File

@@ -1,7 +1,7 @@
{
"name": "robthree/twofactorauth",
"description": "Two Factor Authentication",
"version": "1.6.1",
"version": "1.6",
"type": "library",
"keywords": [ "Authentication", "Two Factor Authentication", "Multi Factor Authentication", "TFA", "MFA", "PHP", "Authenticator", "Authy" ],
"homepage": "https://github.com/RobThree/TwoFactorAuth",

View File

@@ -6,7 +6,7 @@ class ConvertUnixTimeDotComTimeProvider implements ITimeProvider
{
public function getTime() {
$json = @json_decode(
@file_get_contents('http://www.convert-unix-time.com/api?timestamp=now&r=' . uniqid(null, true))
@file_get_contents('http://www.convert-unix-time.com/api?timestamp=now')
);
if ($json === null || !is_int($json->timestamp))
throw new \TimeException('Unable to retrieve time from convert-unix-time.com');

View File

@@ -26,8 +26,7 @@ class HttpTimeProvider implements ITimeProvider
'request_fulluri' => true,
'header' => array(
'Connection: close',
'User-agent: TwoFactorAuth HttpTimeProvider (https://github.com/RobThree/TwoFactorAuth)',
'Cache-Control: no-cache'
'User-agent: TwoFactorAuth HttpTimeProvider (https://github.com/RobThree/TwoFactorAuth)'
)
)
);

View File

@@ -1,507 +0,0 @@
<?php
/* Copyright (c) 2014 Yubico AB
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace u2flib_server;
/** Constant for the version of the u2f protocol */
const U2F_VERSION = "U2F_V2";
/** Error for the authentication message not matching any outstanding
* authentication request */
const ERR_NO_MATCHING_REQUEST = 1;
/** Error for the authentication message not matching any registration */
const ERR_NO_MATCHING_REGISTRATION = 2;
/** Error for the signature on the authentication message not verifying with
* the correct key */
const ERR_AUTHENTICATION_FAILURE = 3;
/** Error for the challenge in the registration message not matching the
* registration challenge */
const ERR_UNMATCHED_CHALLENGE = 4;
/** Error for the attestation signature on the registration message not
* verifying */
const ERR_ATTESTATION_SIGNATURE = 5;
/** Error for the attestation verification not verifying */
const ERR_ATTESTATION_VERIFICATION = 6;
/** Error for not getting good random from the system */
const ERR_BAD_RANDOM = 7;
/** Error when the counter is lower than expected */
const ERR_COUNTER_TOO_LOW = 8;
/** Error decoding public key */
const ERR_PUBKEY_DECODE = 9;
/** Error user-agent returned error */
const ERR_BAD_UA_RETURNING = 10;
/** Error old OpenSSL version */
const ERR_OLD_OPENSSL = 11;
/** @internal */
const PUBKEY_LEN = 65;
class U2F
{
/** @var string */
private $appId;
/** @var null|string */
private $attestDir;
/** @internal */
private $FIXCERTS = array(
'349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8',
'dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f',
'1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae',
'd0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb',
'6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897',
'ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511'
);
/**
* @param string $appId Application id for the running application
* @param string|null $attestDir Directory where trusted attestation roots may be found
* @throws Error If OpenSSL older than 1.0.0 is used
*/
public function __construct($appId, $attestDir = null)
{
if(OPENSSL_VERSION_NUMBER < 0x10000000) {
throw new Error('OpenSSL has to be at least version 1.0.0, this is ' . OPENSSL_VERSION_TEXT, ERR_OLD_OPENSSL);
}
$this->appId = $appId;
$this->attestDir = $attestDir;
}
/**
* Called to get a registration request to send to a user.
* Returns an array of one registration request and a array of sign requests.
*
* @param array $registrations List of current registrations for this
* user, to prevent the user from registering the same authenticator several
* times.
* @return array An array of two elements, the first containing a
* RegisterRequest the second being an array of SignRequest
* @throws Error
*/
public function getRegisterData(array $registrations = array())
{
$challenge = $this->createChallenge();
$request = new RegisterRequest($challenge, $this->appId);
$signs = $this->getAuthenticateData($registrations);
return array($request, $signs);
}
/**
* Called to verify and unpack a registration message.
*
* @param RegisterRequest $request this is a reply to
* @param object $response response from a user
* @param bool $includeCert set to true if the attestation certificate should be
* included in the returned Registration object
* @return Registration
* @throws Error
*/
public function doRegister($request, $response, $includeCert = true)
{
if( !is_object( $request ) ) {
throw new \InvalidArgumentException('$request of doRegister() method only accepts object.');
}
if( !is_object( $response ) ) {
throw new \InvalidArgumentException('$response of doRegister() method only accepts object.');
}
if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) {
throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING );
}
if( !is_bool( $includeCert ) ) {
throw new \InvalidArgumentException('$include_cert of doRegister() method only accepts boolean.');
}
$rawReg = $this->base64u_decode($response->registrationData);
$regData = array_values(unpack('C*', $rawReg));
$clientData = $this->base64u_decode($response->clientData);
$cli = json_decode($clientData);
if($cli->challenge !== $request->challenge) {
throw new Error('Registration challenge does not match', ERR_UNMATCHED_CHALLENGE );
}
$registration = new Registration();
$offs = 1;
$pubKey = substr($rawReg, $offs, PUBKEY_LEN);
$offs += PUBKEY_LEN;
// decode the pubKey to make sure it's good
$tmpKey = $this->pubkey_to_pem($pubKey);
if($tmpKey === null) {
throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
}
$registration->publicKey = base64_encode($pubKey);
$khLen = $regData[$offs++];
$kh = substr($rawReg, $offs, $khLen);
$offs += $khLen;
$registration->keyHandle = $this->base64u_encode($kh);
// length of certificate is stored in byte 3 and 4 (excluding the first 4 bytes)
$certLen = 4;
$certLen += ($regData[$offs + 2] << 8);
$certLen += $regData[$offs + 3];
$rawCert = $this->fixSignatureUnusedBits(substr($rawReg, $offs, $certLen));
$offs += $certLen;
$pemCert = "-----BEGIN CERTIFICATE-----\r\n";
$pemCert .= chunk_split(base64_encode($rawCert), 64);
$pemCert .= "-----END CERTIFICATE-----";
if($includeCert) {
$registration->certificate = base64_encode($rawCert);
}
if($this->attestDir) {
if(openssl_x509_checkpurpose($pemCert, -1, $this->get_certs()) !== true) {
throw new Error('Attestation certificate can not be validated', ERR_ATTESTATION_VERIFICATION );
}
}
if(!openssl_pkey_get_public($pemCert)) {
throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
}
$signature = substr($rawReg, $offs);
$dataToVerify = chr(0);
$dataToVerify .= hash('sha256', $request->appId, true);
$dataToVerify .= hash('sha256', $clientData, true);
$dataToVerify .= $kh;
$dataToVerify .= $pubKey;
if(openssl_verify($dataToVerify, $signature, $pemCert, 'sha256') === 1) {
return $registration;
} else {
throw new Error('Attestation signature does not match', ERR_ATTESTATION_SIGNATURE );
}
}
/**
* Called to get an authentication request.
*
* @param array $registrations An array of the registrations to create authentication requests for.
* @return array An array of SignRequest
* @throws Error
*/
public function getAuthenticateData(array $registrations)
{
$sigs = array();
$challenge = $this->createChallenge();
foreach ($registrations as $reg) {
if( !is_object( $reg ) ) {
throw new \InvalidArgumentException('$registrations of getAuthenticateData() method only accepts array of object.');
}
$sig = new SignRequest();
$sig->appId = $this->appId;
$sig->keyHandle = $reg->keyHandle;
$sig->challenge = $challenge;
$sigs[] = $sig;
}
return $sigs;
}
/**
* Called to verify an authentication response
*
* @param array $requests An array of outstanding authentication requests
* @param array $registrations An array of current registrations
* @param object $response A response from the authenticator
* @return Registration
* @throws Error
*
* The Registration object returned on success contains an updated counter
* that should be saved for future authentications.
* If the Error returned is ERR_COUNTER_TOO_LOW this is an indication of
* token cloning or similar and appropriate action should be taken.
*/
public function doAuthenticate(array $requests, array $registrations, $response)
{
if( !is_object( $response ) ) {
throw new \InvalidArgumentException('$response of doAuthenticate() method only accepts object.');
}
if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) {
throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING );
}
/** @var object|null $req */
$req = null;
/** @var object|null $reg */
$reg = null;
$clientData = $this->base64u_decode($response->clientData);
$decodedClient = json_decode($clientData);
foreach ($requests as $req) {
if( !is_object( $req ) ) {
throw new \InvalidArgumentException('$requests of doAuthenticate() method only accepts array of object.');
}
if($req->keyHandle === $response->keyHandle && $req->challenge === $decodedClient->challenge) {
break;
}
$req = null;
}
if($req === null) {
throw new Error('No matching request found', ERR_NO_MATCHING_REQUEST );
}
foreach ($registrations as $reg) {
if( !is_object( $reg ) ) {
throw new \InvalidArgumentException('$registrations of doAuthenticate() method only accepts array of object.');
}
if($reg->keyHandle === $response->keyHandle) {
break;
}
$reg = null;
}
if($reg === null) {
throw new Error('No matching registration found', ERR_NO_MATCHING_REGISTRATION );
}
$pemKey = $this->pubkey_to_pem($this->base64u_decode($reg->publicKey));
if($pemKey === null) {
throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
}
$signData = $this->base64u_decode($response->signatureData);
$dataToVerify = hash('sha256', $req->appId, true);
$dataToVerify .= substr($signData, 0, 5);
$dataToVerify .= hash('sha256', $clientData, true);
$signature = substr($signData, 5);
if(openssl_verify($dataToVerify, $signature, $pemKey, 'sha256') === 1) {
$ctr = unpack("Nctr", substr($signData, 1, 4));
$counter = $ctr['ctr'];
/* TODO: wrap-around should be handled somehow.. */
if($counter > $reg->counter) {
$reg->counter = $counter;
return $reg;
} else {
throw new Error('Counter too low.', ERR_COUNTER_TOO_LOW );
}
} else {
throw new Error('Authentication failed', ERR_AUTHENTICATION_FAILURE );
}
}
/**
* @return array
*/
private function get_certs()
{
$files = array();
$dir = $this->attestDir;
if($dir && $handle = opendir($dir)) {
while(false !== ($entry = readdir($handle))) {
if(is_file("$dir/$entry")) {
$files[] = "$dir/$entry";
}
}
closedir($handle);
}
return $files;
}
/**
* @param string $data
* @return string
*/
private function base64u_encode($data)
{
return trim(strtr(base64_encode($data), '+/', '-_'), '=');
}
/**
* @param string $data
* @return string
*/
private function base64u_decode($data)
{
return base64_decode(strtr($data, '-_', '+/'));
}
/**
* @param string $key
* @return null|string
*/
private function pubkey_to_pem($key)
{
if(strlen($key) !== PUBKEY_LEN || $key[0] !== "\x04") {
return null;
}
/*
* Convert the public key to binary DER format first
* Using the ECC SubjectPublicKeyInfo OIDs from RFC 5480
*
* SEQUENCE(2 elem) 30 59
* SEQUENCE(2 elem) 30 13
* OID1.2.840.10045.2.1 (id-ecPublicKey) 06 07 2a 86 48 ce 3d 02 01
* OID1.2.840.10045.3.1.7 (secp256r1) 06 08 2a 86 48 ce 3d 03 01 07
* BIT STRING(520 bit) 03 42 ..key..
*/
$der = "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01";
$der .= "\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42";
$der .= "\0".$key;
$pem = "-----BEGIN PUBLIC KEY-----\r\n";
$pem .= chunk_split(base64_encode($der), 64);
$pem .= "-----END PUBLIC KEY-----";
return $pem;
}
/**
* @return string
* @throws Error
*/
private function createChallenge()
{
$challenge = openssl_random_pseudo_bytes(32, $crypto_strong );
if( $crypto_strong !== true ) {
throw new Error('Unable to obtain a good source of randomness', ERR_BAD_RANDOM);
}
$challenge = $this->base64u_encode( $challenge );
return $challenge;
}
/**
* Fixes a certificate where the signature contains unused bits.
*
* @param string $cert
* @return mixed
*/
private function fixSignatureUnusedBits($cert)
{
if(in_array(hash('sha256', $cert), $this->FIXCERTS)) {
$cert[strlen($cert) - 257] = "\0";
}
return $cert;
}
}
/**
* Class for building a registration request
*
* @package u2flib_server
*/
class RegisterRequest
{
/** Protocol version */
public $version = U2F_VERSION;
/** Registration challenge */
public $challenge;
/** Application id */
public $appId;
/**
* @param string $challenge
* @param string $appId
* @internal
*/
public function __construct($challenge, $appId)
{
$this->challenge = $challenge;
$this->appId = $appId;
}
}
/**
* Class for building up an authentication request
*
* @package u2flib_server
*/
class SignRequest
{
/** Protocol version */
public $version = U2F_VERSION;
/** Authentication challenge */
public $challenge;
/** Key handle of a registered authenticator */
public $keyHandle;
/** Application id */
public $appId;
}
/**
* Class returned for successful registrations
*
* @package u2flib_server
*/
class Registration
{
/** The key handle of the registered authenticator */
public $keyHandle;
/** The public key of the registered authenticator */
public $publicKey;
/** The attestation certificate of the registered authenticator */
public $certificate;
/** The counter associated with this registration */
public $counter = -1;
}
/**
* Error class, returned on errors
*
* @package u2flib_server
*/
class Error extends \Exception
{
/**
* Override constructor and make message and code mandatory
* @param string $message
* @param int $code
* @param \Exception|null $previous
*/
public function __construct($message, $code, \Exception $previous = null) {
parent::__construct($message, $code, $previous);
}
}