Anonymous
Как заставить WebPush (с использованием Qt) работать с браузером Windows Edge?
Сообщение
Anonymous » 07 янв 2025, 08:09
Моя реализация Web-Push отлично работает с Chrome и Firefox, но не с MS Edge (даже после % декодирования).
Вопрос : Как заставить это работать?
К вашему сведению, их URL-адреса отображаются в разных форматах:
URL-адрес Chrome:
"
https://fcm.googleapis.com/fcm/send/eBt ... 0GS69gx0ZK bnbrOD4FB7m8Ou8ZpS0X0hcEeSLjxYdsYKi896YibgFrocO7L8qDU2SeZfr6L7Pqc2DZ7A_82Qik7PINAF_S2rskKofe"
Пограничный URL:
"
https://wns2-pn1p.notify.windows.com/w/ ... wUFnM6fJEy SP1Jr0Fi3OU2rgTICDfl2Bsj6jS4eNZLo0FQK1diyqN1v6zi7k9yBijIksEvKegd2q2Z%2bGAIoIt0QfnQyluOGSNgXCrF0jr4h3 Ka5aJUVdl7aBSkkULq5PI7wvGl3mGMD9I3xk71jG%2bjBAwoF2ThvYfefeEpd5xMAJ%2bWLBd3FD56kD1zTplOhwS4Leysw3SFBX h393%2b8MfDFJCAi%2f0mfKLBy%2fTCuha50GJT7oBJHemhFCi5E2CliZ9dFB2IPCpEa%2bEfgVWHC6GkpRMjUSCs0%3d"
Ошибка напечатана на стороне клиента:
400: «Ошибка передачи – сервер ответил: "
ChatGPT говорит, что сервер не обязан предоставлять фактический текст того, что именно пошло не так, чтобы предотвратить его внутреннюю работу.
Для Chrome он просто возвращает 201, и уведомление отображается в панель мониторинга.
Обратите внимание, что та же комбинация ключей VAPID, Endpoint, P256DH, AUTH и т. д. работает с известным API push-уведомлений C# (github).
Исходный код: Из библиотеки pusha(github) я извлек соответствующую часть и реализовал минимальный рабочий пример с помощью Qt framework. Возможно, придется добавить несколько файлов библиотеки ecec (github). После этого все работает нормально только с одним исходным файлом ниже!
web-push.pro
Код: Выделить всё
TEMPLATE = app
CONFIG += console c++17
CONFIG -= app_bundle
QT += network
LIBS += -lcrypto -lssl #ssl is optional
SOURCES += \
ecec/encrypt.c \
ecec/keys.c \
ecec/trailer.c \
main.cpp
HEADERS += \
ecec/ece.h \
ecec/keys.h \
ecec/trailer.h
main.cpp
Код: Выделить всё
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ENDPOINT "https://fcm.googleapis.com/fcm/send/eBt-ioGXt4A:APA91bGkIsaIeVWAG-cU-UysXKl5pevjnZzIRT0GS69gx0ZKbnbrOD4FB7m8Ou8ZpS0X0hcEeSLjxYdsYKi896YibgFrocO7L8qDU2SeZfr6L7Pqc2DZ7A_82Qik7PINAF_S2rskKofe"
#define P256DH "BM8Md9QY7egq1UqZytneixPkITMK5556EHBQD0yTZY6eW6eeK89TsdpymT79rIc9xfouL1FLH-ACb9kEuf6iea0"
#define AUTH "w7o5Sip9yBm6ME1C88pebg"
#define VAPID_PRIVATE_KEY "T2blCzCxRzFl2yjI-Q6WLxW1PoiTxSLPExtP9yOxkgM"
#define VAPID_PUBLIC_KEY "BMRAeeyEY6imWuCktv6NX3o5prv4UWndTUWTEO4dCgmzX8YDgjuIbPslpSM2fdfTbOjmnLIBkJKch2wGnTm8_sY"
#define WEBPUSH_PAYLOAD_KEY_AUD "aud"
#define WEBPUSH_PAYLOAD_KEY_EXP "exp"
#define WEBPUSH_PAYLOAD_KEY_SUB "sub"
#define WEBPUSH_VAPID_KEY_ALG "alg"
#define WEBPUSH_VAPID_KEY_TYP "typ"
#define HTTP_DH ";dh="
#define HTTP_AUTHORIZATION "authorization"
#define HTTP_CONTENT_ENCODING "content-encoding"
#define HTTP_CONTENT_TYPE "content-type"
#define HTTP_CONTENT_LENGTH "content-length"
#define HTTP_CRYPTO_KEY "crypto-key"
#define HTTP_ENCRYPTION "encryption"
#define HTTP_P256 "p256ecdsa="
#define HTTP_RS_SALT "rs=4096;salt="
#define HTTP_TTL "ttl"
#define QT_URL_ENCODING QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals
#define MAKE_UNIQUE(MALLOC, FREE) std::unique_ptr{MALLOC, &FREE}
static const QByteArray HTTP_AUTHORIZATION_v = "webPush ",
HTTP_ENCODING_v = "aesgcm",
HTTP_CONTENT_TYPE_v = "application/octet-stream",
HTTP_TTL_v = "3600",
WEBPUSH_VAPID_ALG_v = "ES256", // must be in capital
WEBPUSH_VAPID_TYP_v = "jwt";
#define SUBSCRIBER "mailto:root@saarathy.in"
#define MY_PAYLOAD "{ \
\"body\": \"Shree Vallabh!\", \
\"tag\": \"AAHLAAD\", \
\"data\": {\"tag\": \"test\"}, \
\"title\": \"Saarathy\" \
}"
#define SYMBOL_DOT "."
struct Subscription
{
uint8_t m_P256dh[ECE_WEBPUSH_PUBLIC_KEY_LENGTH];
uint8_t m_Auth[ECE_WEBPUSH_AUTH_SECRET_LENGTH];
};
struct Payload
{
uint8_t m_Salt[ECE_SALT_LENGTH], m_PublicKeySender[ECE_WEBPUSH_PUBLIC_KEY_LENGTH];
QByteArray m_Cipher;
size_t m_CipherLength;
};
auto
GetJson (QVariantMap map)
{
auto object = QJsonObject::fromVariantMap(map);
return QJsonDocument(object).toJson(QJsonDocument::Compact);
}
auto
CreateECKey (const QByteArray& rawKey)
{
auto pECKey = MAKE_UNIQUE(::EC_KEY_new_by_curve_name(NID_X9_62_prime256v1), ::EC_KEY_free);
::EC_KEY_oct2priv(pECKey.get(),
reinterpret_cast(rawKey.data()), rawKey.size());
const ::EC_GROUP* const pGroup = ::EC_KEY_get0_group(pECKey.get());
auto point = MAKE_UNIQUE(::EC_POINT_new(pGroup), ::EC_POINT_free);
::EC_POINT_mul(pGroup, point.get(),
::EC_KEY_get0_private_key(pECKey.get()), nullptr, nullptr, nullptr);
::EC_KEY_set_public_key(pECKey.get(), point.get());
return pECKey;
}
QByteArray
VapidSign (EC_KEY& ecKey,
const QByteArray& sign)
{
unsigned char digest[SHA256_DIGEST_LENGTH];
::SHA256(reinterpret_cast(sign.data()), sign.size(), digest);
const auto pSign = MAKE_UNIQUE(::ECDSA_do_sign(digest, SHA256_DIGEST_LENGTH, &ecKey),
::ECDSA_SIG_free);
const ::BIGNUM *pR = nullptr, *pS = nullptr;
::ECDSA_SIG_get0(pSign.get(), &pR, &pS);
const auto r_Size = BN_num_bytes(pR), s_Size = BN_num_bytes(pS);
QByteArray signValue(r_Size + s_Size, 0);
::BN_bn2bin(pR, reinterpret_cast(signValue.data()));
::BN_bn2bin(pS, reinterpret_cast(&signValue[r_Size]));
return signValue;
}
QByteArray
VapidAuthorize (const QString& endpoint,
const QString& subscriber,
const int expiration,
EC_KEY& ecKey)
{
const auto audience = endpoint.section('/', 0, 2);
const auto params = GetJson({{WEBPUSH_PAYLOAD_KEY_AUD, audience},
{WEBPUSH_PAYLOAD_KEY_EXP, expiration},
{WEBPUSH_PAYLOAD_KEY_SUB, subscriber}}),
header = GetJson({{WEBPUSH_VAPID_KEY_ALG, WEBPUSH_VAPID_ALG_v},
{WEBPUSH_VAPID_KEY_TYP, WEBPUSH_VAPID_TYP_v}}),
sign = header.toBase64(QT_URL_ENCODING) + SYMBOL_DOT + params.toBase64(QT_URL_ENCODING);
return sign + SYMBOL_DOT + VapidSign(ecKey, sign).toBase64(QT_URL_ENCODING);
}
size_t
GetCipherLength (const uint32_t rs,
const size_t padSize,
const size_t padLen,
const size_t plaintextLen)
{
const size_t overhead = padSize + ECE_TAG_LENGTH, dataLen = plaintextLen + padLen,
maxBlockLen = rs - overhead, numRecords = (dataLen / maxBlockLen) + 1;
if(rs SIZE_MAX - plaintextLen or
numRecords > (SIZE_MAX - dataLen) / overhead)
return 0;
return dataLen + (overhead * numRecords);
}
void
WebPush (const QString& endpoint,
const QByteArray& authorization,
const QByteArray& vapidPublicKey,
const Payload& payload)
{
int argc;
QCoreApplication app{argc, nullptr};
const auto dh = QByteArray(reinterpret_cast(payload.m_PublicKeySender),
ECE_WEBPUSH_PUBLIC_KEY_LENGTH).toBase64(QT_URL_ENCODING),
salt = QByteArray(reinterpret_cast(payload.m_Salt),
ECE_SALT_LENGTH).toBase64(QT_URL_ENCODING);
QNetworkAccessManager networkManager;
QNetworkRequest request(QUrl{endpoint});
request.setRawHeader(HTTP_AUTHORIZATION, HTTP_AUTHORIZATION_v + authorization);
request.setRawHeader(HTTP_CONTENT_ENCODING, HTTP_ENCODING_v);
request.setRawHeader(HTTP_CONTENT_TYPE, HTTP_CONTENT_TYPE_v);
request.setRawHeader(HTTP_CONTENT_LENGTH, QByteArray::number(payload.m_Cipher.size()));
request.setRawHeader(HTTP_CRYPTO_KEY, HTTP_P256 + vapidPublicKey + HTTP_DH + dh);
request.setRawHeader(HTTP_ENCRYPTION, HTTP_RS_SALT + salt);
request.setRawHeader(HTTP_TTL, HTTP_TTL_v);
QNetworkReply* pReply = networkManager.post(request, payload.m_Cipher);
QObject::connect(pReply, &QNetworkReply::finished, pReply, &QNetworkReply::deleteLater);
QObject::connect(pReply, &QNetworkReply::finished, pReply,
[=] ()
{
qDebug() attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()
Подробнее здесь: [url]https://stackoverflow.com/questions/79322818/how-to-make-webpush-using-qt-work-with-windows-edge-browser[/url]
1736226577
Anonymous
Моя реализация Web-Push отлично работает с Chrome и Firefox, но не с MS Edge (даже после % декодирования). [b]Вопрос[/b]: Как заставить это работать? К вашему сведению, их URL-адреса отображаются в разных форматах: URL-адрес Chrome: "https://fcm.googleapis.com/fcm/send/eBt-ioGXt4A:APA91bGkIsaIeVWAG-cU-UysXKl5pevjnZzIRT0GS69gx0ZK bnbrOD4FB7m8Ou8ZpS0X0hcEeSLjxYdsYKi896YibgFrocO7L8qDU2SeZfr6L7Pqc2DZ7A_82Qik7PINAF_S2rskKofe" Пограничный URL: "https://wns2-pn1p.notify.windows.com/w/?token=BQYAAACbMWA0QtGcCaM00KJntpBBrdmzSvWUnBmgRwUFnM6fJEy SP1Jr0Fi3OU2rgTICDfl2Bsj6jS4eNZLo0FQK1diyqN1v6zi7k9yBijIksEvKegd2q2Z%2bGAIoIt0QfnQyluOGSNgXCrF0jr4h3 Ka5aJUVdl7aBSkkULq5PI7wvGl3mGMD9I3xk71jG%2bjBAwoF2ThvYfefeEpd5xMAJ%2bWLBd3FD56kD1zTplOhwS4Leysw3SFBX h393%2b8MfDFJCAi%2f0mfKLBy%2fTCuha50GJT7oBJHemhFCi5E2CliZ9dFB2IPCpEa%2bEfgVWHC6GkpRMjUSCs0%3d" Ошибка напечатана на стороне клиента: 400: «Ошибка передачи – сервер ответил: " ChatGPT говорит, что сервер не обязан предоставлять фактический текст того, что именно пошло не так, чтобы предотвратить его внутреннюю работу. Для Chrome он просто возвращает 201, и уведомление отображается в панель мониторинга. Обратите внимание, что та же комбинация ключей VAPID, Endpoint, P256DH, AUTH и т. д. работает с известным API push-уведомлений C# (github). Исходный код: Из библиотеки pusha(github) я извлек соответствующую часть и реализовал минимальный рабочий пример с помощью Qt framework. Возможно, придется добавить несколько файлов библиотеки ecec (github). После этого все работает нормально только с одним исходным файлом ниже! [b]web-push.pro[/b] [code]TEMPLATE = app CONFIG += console c++17 CONFIG -= app_bundle QT += network LIBS += -lcrypto -lssl #ssl is optional SOURCES += \ ecec/encrypt.c \ ecec/keys.c \ ecec/trailer.c \ main.cpp HEADERS += \ ecec/ece.h \ ecec/keys.h \ ecec/trailer.h [/code] [b]main.cpp[/b] [code]#include #include #include #include #include #include #include #include #include #include #include #define ENDPOINT "https://fcm.googleapis.com/fcm/send/eBt-ioGXt4A:APA91bGkIsaIeVWAG-cU-UysXKl5pevjnZzIRT0GS69gx0ZKbnbrOD4FB7m8Ou8ZpS0X0hcEeSLjxYdsYKi896YibgFrocO7L8qDU2SeZfr6L7Pqc2DZ7A_82Qik7PINAF_S2rskKofe" #define P256DH "BM8Md9QY7egq1UqZytneixPkITMK5556EHBQD0yTZY6eW6eeK89TsdpymT79rIc9xfouL1FLH-ACb9kEuf6iea0" #define AUTH "w7o5Sip9yBm6ME1C88pebg" #define VAPID_PRIVATE_KEY "T2blCzCxRzFl2yjI-Q6WLxW1PoiTxSLPExtP9yOxkgM" #define VAPID_PUBLIC_KEY "BMRAeeyEY6imWuCktv6NX3o5prv4UWndTUWTEO4dCgmzX8YDgjuIbPslpSM2fdfTbOjmnLIBkJKch2wGnTm8_sY" #define WEBPUSH_PAYLOAD_KEY_AUD "aud" #define WEBPUSH_PAYLOAD_KEY_EXP "exp" #define WEBPUSH_PAYLOAD_KEY_SUB "sub" #define WEBPUSH_VAPID_KEY_ALG "alg" #define WEBPUSH_VAPID_KEY_TYP "typ" #define HTTP_DH ";dh=" #define HTTP_AUTHORIZATION "authorization" #define HTTP_CONTENT_ENCODING "content-encoding" #define HTTP_CONTENT_TYPE "content-type" #define HTTP_CONTENT_LENGTH "content-length" #define HTTP_CRYPTO_KEY "crypto-key" #define HTTP_ENCRYPTION "encryption" #define HTTP_P256 "p256ecdsa=" #define HTTP_RS_SALT "rs=4096;salt=" #define HTTP_TTL "ttl" #define QT_URL_ENCODING QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals #define MAKE_UNIQUE(MALLOC, FREE) std::unique_ptr{MALLOC, &FREE} static const QByteArray HTTP_AUTHORIZATION_v = "webPush ", HTTP_ENCODING_v = "aesgcm", HTTP_CONTENT_TYPE_v = "application/octet-stream", HTTP_TTL_v = "3600", WEBPUSH_VAPID_ALG_v = "ES256", // must be in capital WEBPUSH_VAPID_TYP_v = "jwt"; #define SUBSCRIBER "mailto:root@saarathy.in" #define MY_PAYLOAD "{ \ \"body\": \"Shree Vallabh!\", \ \"tag\": \"AAHLAAD\", \ \"data\": {\"tag\": \"test\"}, \ \"title\": \"Saarathy\" \ }" #define SYMBOL_DOT "." struct Subscription { uint8_t m_P256dh[ECE_WEBPUSH_PUBLIC_KEY_LENGTH]; uint8_t m_Auth[ECE_WEBPUSH_AUTH_SECRET_LENGTH]; }; struct Payload { uint8_t m_Salt[ECE_SALT_LENGTH], m_PublicKeySender[ECE_WEBPUSH_PUBLIC_KEY_LENGTH]; QByteArray m_Cipher; size_t m_CipherLength; }; auto GetJson (QVariantMap map) { auto object = QJsonObject::fromVariantMap(map); return QJsonDocument(object).toJson(QJsonDocument::Compact); } auto CreateECKey (const QByteArray& rawKey) { auto pECKey = MAKE_UNIQUE(::EC_KEY_new_by_curve_name(NID_X9_62_prime256v1), ::EC_KEY_free); ::EC_KEY_oct2priv(pECKey.get(), reinterpret_cast(rawKey.data()), rawKey.size()); const ::EC_GROUP* const pGroup = ::EC_KEY_get0_group(pECKey.get()); auto point = MAKE_UNIQUE(::EC_POINT_new(pGroup), ::EC_POINT_free); ::EC_POINT_mul(pGroup, point.get(), ::EC_KEY_get0_private_key(pECKey.get()), nullptr, nullptr, nullptr); ::EC_KEY_set_public_key(pECKey.get(), point.get()); return pECKey; } QByteArray VapidSign (EC_KEY& ecKey, const QByteArray& sign) { unsigned char digest[SHA256_DIGEST_LENGTH]; ::SHA256(reinterpret_cast(sign.data()), sign.size(), digest); const auto pSign = MAKE_UNIQUE(::ECDSA_do_sign(digest, SHA256_DIGEST_LENGTH, &ecKey), ::ECDSA_SIG_free); const ::BIGNUM *pR = nullptr, *pS = nullptr; ::ECDSA_SIG_get0(pSign.get(), &pR, &pS); const auto r_Size = BN_num_bytes(pR), s_Size = BN_num_bytes(pS); QByteArray signValue(r_Size + s_Size, 0); ::BN_bn2bin(pR, reinterpret_cast(signValue.data())); ::BN_bn2bin(pS, reinterpret_cast(&signValue[r_Size])); return signValue; } QByteArray VapidAuthorize (const QString& endpoint, const QString& subscriber, const int expiration, EC_KEY& ecKey) { const auto audience = endpoint.section('/', 0, 2); const auto params = GetJson({{WEBPUSH_PAYLOAD_KEY_AUD, audience}, {WEBPUSH_PAYLOAD_KEY_EXP, expiration}, {WEBPUSH_PAYLOAD_KEY_SUB, subscriber}}), header = GetJson({{WEBPUSH_VAPID_KEY_ALG, WEBPUSH_VAPID_ALG_v}, {WEBPUSH_VAPID_KEY_TYP, WEBPUSH_VAPID_TYP_v}}), sign = header.toBase64(QT_URL_ENCODING) + SYMBOL_DOT + params.toBase64(QT_URL_ENCODING); return sign + SYMBOL_DOT + VapidSign(ecKey, sign).toBase64(QT_URL_ENCODING); } size_t GetCipherLength (const uint32_t rs, const size_t padSize, const size_t padLen, const size_t plaintextLen) { const size_t overhead = padSize + ECE_TAG_LENGTH, dataLen = plaintextLen + padLen, maxBlockLen = rs - overhead, numRecords = (dataLen / maxBlockLen) + 1; if(rs SIZE_MAX - plaintextLen or numRecords > (SIZE_MAX - dataLen) / overhead) return 0; return dataLen + (overhead * numRecords); } void WebPush (const QString& endpoint, const QByteArray& authorization, const QByteArray& vapidPublicKey, const Payload& payload) { int argc; QCoreApplication app{argc, nullptr}; const auto dh = QByteArray(reinterpret_cast(payload.m_PublicKeySender), ECE_WEBPUSH_PUBLIC_KEY_LENGTH).toBase64(QT_URL_ENCODING), salt = QByteArray(reinterpret_cast(payload.m_Salt), ECE_SALT_LENGTH).toBase64(QT_URL_ENCODING); QNetworkAccessManager networkManager; QNetworkRequest request(QUrl{endpoint}); request.setRawHeader(HTTP_AUTHORIZATION, HTTP_AUTHORIZATION_v + authorization); request.setRawHeader(HTTP_CONTENT_ENCODING, HTTP_ENCODING_v); request.setRawHeader(HTTP_CONTENT_TYPE, HTTP_CONTENT_TYPE_v); request.setRawHeader(HTTP_CONTENT_LENGTH, QByteArray::number(payload.m_Cipher.size())); request.setRawHeader(HTTP_CRYPTO_KEY, HTTP_P256 + vapidPublicKey + HTTP_DH + dh); request.setRawHeader(HTTP_ENCRYPTION, HTTP_RS_SALT + salt); request.setRawHeader(HTTP_TTL, HTTP_TTL_v); QNetworkReply* pReply = networkManager.post(request, payload.m_Cipher); QObject::connect(pReply, &QNetworkReply::finished, pReply, &QNetworkReply::deleteLater); QObject::connect(pReply, &QNetworkReply::finished, pReply, [=] () { qDebug() attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() Подробнее здесь: [url]https://stackoverflow.com/questions/79322818/how-to-make-webpush-using-qt-work-with-windows-edge-browser[/url]