File indexing completed on 2024-05-12 05:09:27

0001 /***************************************************************************
0002     Copyright (C) 2007-2020 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "amazonrequest.h"
0026 #include "../tellico_debug.h"
0027 
0028 #include <QDateTime>
0029 #include <QCryptographicHash>
0030 #include <QMessageAuthenticationCode>
0031 
0032 namespace {
0033   static const char* AMAZON_REQUEST = "aws4_request";
0034   static const char* AMAZON_REQUEST_ALGORITHM = "AWS4-HMAC-SHA256";
0035   static const char* AMAZON_REQUEST_NAMESPACE = "com.amazon.paapi5";
0036   static const char* AMAZON_REQUEST_VERSION = "v1";
0037   static const char* AMAZON_REQUEST_SERVICE = "ProductAdvertisingAPI";
0038 }
0039 
0040 using Tellico::Fetch::AmazonRequest;
0041 
0042 AmazonRequest::AmazonRequest(const QString& accessKey_, const QString& secretKey_)
0043     : m_accessKey(accessKey_.toUtf8())
0044     , m_secretKey(secretKey_.toUtf8())
0045     , m_method("POST")
0046     , m_service(AMAZON_REQUEST_SERVICE)
0047     , m_operation(SearchItems) {
0048   m_amzDate = QDateTime::currentDateTimeUtc().toString(QStringLiteral("yyyyMMdd'T'hhmmss'Z'")).toUtf8();
0049 }
0050 
0051 void AmazonRequest::setHost(const QByteArray& host_) {
0052   m_host = host_;
0053 }
0054 
0055 void AmazonRequest::setPath(const QByteArray& path_) {
0056   m_path = path_;
0057 }
0058 
0059 void AmazonRequest::setRegion(const QByteArray& region_) {
0060   m_region = region_;
0061 }
0062 
0063 void AmazonRequest::setOperation(int op_) {
0064   m_operation = static_cast<Operation>(op_);
0065 }
0066 
0067 QMap<QByteArray, QByteArray> AmazonRequest::headers(const QByteArray& payload_) {
0068   m_headers.insert("content-encoding", "amz-1.0");
0069   m_headers.insert("content-type", "application/json; charset=utf-8");
0070   m_headers.insert("host", m_host);
0071   m_headers.insert("x-amz-date", m_amzDate);
0072   m_headers.insert("x-amz-target", targetOperation());
0073 
0074   const QByteArray canonicalURL = prepareCanonicalRequest(payload_);
0075   const QByteArray stringToSign = prepareStringToSign(canonicalURL);
0076   const QByteArray signature = calculateSignature(stringToSign);
0077   m_headers.insert("Authorization", buildAuthorizationString(signature));
0078   return m_headers;
0079 }
0080 
0081 QByteArray AmazonRequest::prepareCanonicalRequest(const QByteArray& payload_) const {
0082   Q_ASSERT(!m_method.isEmpty());
0083   Q_ASSERT(!m_path.isEmpty());
0084   Q_ASSERT(!m_headers.isEmpty());
0085   QByteArray req = m_method + '\n'
0086                  + m_path + '\n' + '\n';
0087 
0088   m_signedHeaders.clear();
0089   // a map so that the query elements are sorted
0090   QMapIterator<QByteArray, QByteArray> i(m_headers);
0091   while(i.hasNext()) {
0092     i.next();
0093 
0094     m_signedHeaders.append(i.key());
0095     m_signedHeaders.append(';');
0096 
0097     req.append(i.key());
0098     req.append(':');
0099     req.append(i.value());
0100     req.append('\n');
0101   }
0102   req.append('\n');
0103 
0104   m_signedHeaders.chop(1); // remove final ';'
0105   req.append(m_signedHeaders);
0106   req.append('\n');
0107 
0108   req.append(toHexHash(payload_));
0109   return req;
0110 }
0111 
0112 QByteArray AmazonRequest::prepareStringToSign(const QByteArray& canonicalUrl_) const {
0113   QByteArray stringToSign(AMAZON_REQUEST_ALGORITHM);
0114   stringToSign += '\n';
0115   stringToSign += m_amzDate + '\n';
0116   stringToSign += m_amzDate.left(8) + '/' + m_region + '/' + m_service + '/' + AMAZON_REQUEST + '\n';
0117   stringToSign += toHexHash(canonicalUrl_);
0118   return stringToSign;
0119 }
0120 
0121 QByteArray AmazonRequest::calculateSignature(const QByteArray& stringToSign_) const {
0122   QByteArray signatureKey;
0123   signatureKey = QMessageAuthenticationCode::hash(m_amzDate.left(8), "AWS4" + m_secretKey, QCryptographicHash::Sha256);
0124   signatureKey = QMessageAuthenticationCode::hash(m_region, signatureKey, QCryptographicHash::Sha256);
0125   signatureKey = QMessageAuthenticationCode::hash(m_service, signatureKey, QCryptographicHash::Sha256);
0126   signatureKey = QMessageAuthenticationCode::hash(AMAZON_REQUEST, signatureKey, QCryptographicHash::Sha256);
0127   // '0' says no separators between hex encoded characters
0128   return QMessageAuthenticationCode::hash(stringToSign_, signatureKey, QCryptographicHash::Sha256).toHex(0);
0129 }
0130 
0131 QByteArray AmazonRequest::buildAuthorizationString(const QByteArray& signature_) const {
0132   Q_ASSERT(!m_accessKey.isEmpty());
0133   Q_ASSERT(!m_region.isEmpty());
0134   Q_ASSERT(!m_service.isEmpty());
0135   Q_ASSERT(!m_signedHeaders.isEmpty());
0136   QByteArray authString(AMAZON_REQUEST_ALGORITHM);
0137   authString += ' ';
0138   authString += "Credential=" + m_accessKey;
0139   authString += '/' + m_amzDate.left(8) + '/' + m_region + '/' + m_service + "/" + AMAZON_REQUEST + ", ";
0140   authString += "SignedHeaders=" + m_signedHeaders + ", " + "Signature=" + signature_;
0141   return authString;
0142 }
0143 
0144 QByteArray AmazonRequest::toHexHash(const QByteArray& data_) const {
0145   const QByteArray hash = QCryptographicHash::hash(data_, QCryptographicHash::Sha256);
0146   return hash.toHex(0); // '0' says no separators between hex encoded characters
0147 }
0148 
0149 QByteArray AmazonRequest::targetOperation() const {
0150   QByteArray target(AMAZON_REQUEST_NAMESPACE);
0151   target.append('.');
0152   target.append(AMAZON_REQUEST_VERSION);
0153   target.append('.');
0154   target.append(m_service);
0155   target.append(AMAZON_REQUEST_VERSION);
0156   target.append('.');
0157   switch(m_operation) {
0158     case SearchItems:
0159       target.append("SearchItems");
0160       break;
0161     case GetItems:
0162       target.append("GetItems");
0163       break;
0164   }
0165   return target;
0166 }