1. 说明
Safari官方文档比较简单,生成 push package 的方法 createPushPackage.php 也没有更新…
2. 优化后的 createPushPackage 的方法
class CreatePushPackage {
static $cert_path = '你的 web push 证书.p12';
static $cert_password = '证书密码';
static $intermediate_cert_path = '中间证书.pem' // 获取中间证书方法:见文末
static $origin_dir = '存放原始配置文件目录路径';
static $package_dir = '存放配置文件目录路径';
static $version = 'v2';
static $zip_path = '';
public static function raw_files() {
return array(
'icon.iconset/icon_16x16.png',
'icon.iconset/icon_16x16@2x.png',
'icon.iconset/icon_32x32.png',
'icon.iconset/icon_32x32@2x.png',
'icon.iconset/icon_128x128.png',
'icon.iconset/icon_128x128@2x.png',
'website.json'
);
}
public static function copy_raw_push_package_files() {
if (!is_dir(self::$package_dir . '/icon.iconset')) {
mkdir(self::$package_dir . '/icon.iconset');
}
foreach (self::raw_files() as $raw_file) {
copy(self::$origin_dir . $raw_file, self::$package_dir . '/' .$raw_file);
if(strpos($raw_file, "website.json") !== false) {
$wjson = file_get_contents(self::$package_dir . '/' .$raw_file);
$wjson = str_replace("{HTTP_HOST}", HTTP_HOST, $wjson);
unlink(self::$package_dir . '/' .$raw_file);
$ff = fopen(self::$package_dir . '/' .$raw_file, "x");
fwrite($ff, $wjson);
fclose($ff);
}
}
}
// Creates the manifest by calculating the SHA1 hashes for all of the raw files in the package.
public static function create_manifest() {
// Obtain SHA1 hashes of all the files in the push package
$manifest_data = array();
foreach (self::raw_files() as $raw_file) {
$fileContent = file_get_contents(self::$package_dir . '/' .$raw_file);
if (self::$version == 'v1') {
$manifest_data[$raw_file] = sha1($fileContent);
} else if (self::$version == 'v2') {
$manifest_data[$raw_file]['hashType'] = 'sha512';
$manifest_data[$raw_file]['hashValue'] = hash('sha512', $fileContent);
}
}
file_put_contents(self::$package_dir . '/manifest.json', json_encode((object)$manifest_data));
}
// Creates a signature of the manifest using the push notification certificate.
public static function create_signature() {
// Load the push notification certificate
$pkcs12 = file_get_contents(self::$cert_path);
$certs = array();
if(!openssl_pkcs12_read($pkcs12, $certs, self::$cert_password)) {
return;
}
$signature_path = self::$package_dir . '/signature';
// Sign the manifest.json file with the private key from the certificate
$cert_data = openssl_x509_read($certs['cert']);
$private_key = openssl_pkey_get_private($certs['pkey'], self::$cert_password);
openssl_pkcs7_sign(self::$package_dir . '/manifest.json', $signature_path, $cert_data, $private_key, array(), PKCS7_BINARY | PKCS7_DETACHED, self::$intermediate_cert_path);
// Convert the signature from PEM to DER
$signature_pem = file_get_contents($signature_path);
$matches = array();
if (!preg_match('~Content-Disposition:[^\n]+\s*?([A-Za-z0-9+=/\r\n]+)\s*?-----~', $signature_pem, $matches)) {
return;
}
$signature_der = base64_decode($matches[1]);
file_put_contents($signature_path, $signature_der);
}
// Zips the directory structure into a push package, and returns the path to the archive.
public static function package_raw_data() {
if (file_exists(self::$zip_path)) {
unlink(self::$zip_path);
}
// Package files as a zip file
$zip = new \ZipArchive();
if (!$zip->open(self::$zip_path, \ZIPARCHIVE::CREATE)) {
Logger::Log('Could not create push package zip: ' . self::$zip_path, Logger::SCOPE_BROWSER);
return;
}
$raw_files = self::raw_files();
$raw_files[] = 'manifest.json';
$raw_files[] = 'signature';
foreach ($raw_files as $raw_file) {
$zip->addFile(self::$package_dir . '/' . $raw_file, $raw_file);
}
$zip->close();
return self::$zip_path;
}
// Creates the push package, and returns the path to the archive.
public static function create_push_package() {
self::$zip_path = self::$package_dir . '_' . str_replace('.', '_', HTTP_HOST) . '_' . self::$version . '.zip';
// if (file_exists(self::$zip_path)) {
// return self::$zip_path;
// }
if (!is_dir(self::$package_dir)) {
mkdir(self::$package_dir);
}
self::copy_raw_push_package_files();
self::create_manifest();
self::create_signature();
$package_path = self::package_raw_data();
return $package_path;
}
}
使用方法
获取到权限后,Safari浏览器会构造出下面地址
// 如果有log 会回调到 v1 方法
public function v1()
{
$type = trim($this->uri->segment(3));
if ($type == 'log') {
// 记录相关log
Log('get push packages error >> ' . file_get_contents('php://input'));
return true;
}
return true;
}
// 请求 push package
public function v2()
{
$type = trim($this->uri->segment(3));
$pushID = trim($this->uri->segment(4));
if ($type == 'pushPackages' && $pushID == '你的 web push ID') {
$package_path = CreatePushPackage::create_push_package();
if (empty($package_path)) {
http_response_code(500);
die;
}
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=pushpackage.zip");
header("Content-length: " . filesize($package_path));
header("Pragma: no-cache");
header("Expires: 0");
echo file_get_contents($package_path);
die;
}
return true;
}