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 }