Line data Source code
1 : /*
2 : Copyright 2013-2015 Paul Colby
3 :
4 : This file is part of libqtaws.
5 :
6 : Libqtaws is free software: you can redistribute it and/or modify
7 : it under the terms of the GNU Lesser General Public License as published by
8 : the Free Software Foundation, either version 3 of the License, or
9 : (at your option) any later version.
10 :
11 : Libqtaws is distributed in the hope that it will be useful,
12 : but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : GNU Lesser General Public License for more details.
15 :
16 : You should have received a copy of the GNU Lesser General Public License
17 : along with libqtaws. If not, see <http://www.gnu.org/licenses/>.
18 : */
19 :
20 : #include "awsabstractsignature.h"
21 : #include "awsabstractsignature_p.h"
22 :
23 : #include <QDebug>
24 : #include <QDir>
25 :
26 : QTAWS_BEGIN_NAMESPACE
27 :
28 : /**
29 : * @class AwsAbstractSignature
30 : *
31 : * @brief Interface class for providing AWS signatures.
32 : */
33 :
34 : /**
35 : * @internal
36 : *
37 : * @brief Initialises an AwsAbstractSignature object.
38 : *
39 : * This internal constrcutor is used by derived classes that do not wish to
40 : * provider their own private implementations.
41 : */
42 26 : AwsAbstractSignature::AwsAbstractSignature() : d_ptr(new AwsAbstractSignaturePrivate(this))
43 : {
44 :
45 26 : }
46 :
47 : /**
48 : * @internal
49 : *
50 : * @brief Initialises an AwsAbstractSignature object.
51 : *
52 : * This internal constrcutor is used by derived classes to provide their own
53 : * private implementations if they wish to.
54 : *
55 : * @param d Internal private implementation to use.
56 : */
57 236 : AwsAbstractSignature::AwsAbstractSignature(AwsAbstractSignaturePrivate * const d) : d_ptr(d)
58 : {
59 :
60 236 : }
61 :
62 : /**
63 : * @brief AwsAbstractSignature destructor.
64 : */
65 262 : AwsAbstractSignature::~AwsAbstractSignature() {
66 262 : delete d_ptr;
67 262 : }
68 :
69 : /**
70 : * @fn void AwsAbstractSignature::sign() const
71 : *
72 : * @brief Sign an AWS request.
73 : *
74 : * Note, \a credentials must be valid before calling this function. So, for
75 : * example, if \a credentials has expired, and is refreshable, it is the
76 : * caller's responsibility to refresh the credentials before calling this
77 : * function.
78 : *
79 : * @param operation The network operation to sign \a request for.
80 : * @param request The network request to be signed.
81 : * @param credentials The credentials to use for signing.
82 : * @param data Optional POST / PUT data to sign \a request for.
83 : */
84 :
85 : /**
86 : * @internal
87 : *
88 : * @class AwsAbstractSignaturePrivate
89 : *
90 : * @brief Private implementation for AwsAbstractSignature.
91 : *
92 : * @warning This is an internal private implementation class, and as such external should
93 : * code should **not** depend directly on anything contained within this class.
94 : */
95 :
96 : /**
97 : * @internal
98 : *
99 : * @brief Constructs a new AwsAbstractSignaturePrivate object.
100 : *
101 : * @param q Pointer to this object's public AwsAbstractSignature instance.
102 : */
103 262 : AwsAbstractSignaturePrivate::AwsAbstractSignaturePrivate(AwsAbstractSignature * const q) : q_ptr(q)
104 : {
105 :
106 262 : }
107 :
108 : /**
109 : * @internal
110 : *
111 : * @brief AwsAbstractSignaturePrivate destructor.
112 : *
113 : * This virtual destructor does nothing (yet) - its here to allow for safe
114 : * polymorphic destruction.
115 : */
116 288 : AwsAbstractSignaturePrivate::~AwsAbstractSignaturePrivate()
117 : {
118 :
119 288 : }
120 :
121 : /**
122 : * @brief Create an AWS Signature canonical path.
123 : *
124 : * This function simply returns the fully-URL-encoded path. However, if the path
125 : * is empty, then a single '/' is returned, as is required by Amazon for both V2
126 : * and V4 signatures (and presumably other versions too).
127 : *
128 : * @param url URL from which to extract the path.
129 : *
130 : * @return An AWS Signature canonical path.
131 : *
132 : * @see http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
133 : * @see http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
134 : */
135 114 : QString AwsAbstractSignaturePrivate::canonicalPath(const QUrl &url) const
136 : {
137 114 : QString path = QDir::cleanPath(QLatin1Char('/') + url.path(QUrl::FullyEncoded));
138 :
139 : // If the path begins with "//", remove one of the redundant slashes.
140 : // Note, this is only needed on Windows, because there QDir::speparator is
141 : // '\', and internally QDir::cleanPath swaps all separators to '/', before
142 : // calling qt_normalizePathSegments with allowUncPaths set to true, so that
143 : // '//' is preserved to allow of Windows UNC paths beginning with '\\'.
144 : // This should probably be reported as a bug in Qt::cleanPath("//...").
145 : #ifdef Q_OS_WIN
146 : if (path.startsWith(QLatin1String("//"))) {
147 : path.remove(0, 1); // Remove the first of two forward slashes.
148 : }
149 : #endif
150 :
151 : // Restore the trailing '/' if QDir::cleanPath (rightly) removed one.
152 114 : if ((url.path().endsWith(QLatin1Char('/'))) && (!path.endsWith(QLatin1Char('/')))) {
153 4 : path += QLatin1Char('/');
154 : }
155 :
156 114 : return path;
157 : }
158 :
159 : /**
160 : * @brief Create an AWS Signature canonical query.
161 : *
162 : * This function returns an HTTP query string in Amazon's canonical form. That is,
163 : * all query parameters are sorted by keys (**but not keys-then-values**), then
164 : * joined with `&` separators, in `key=value` pairs with both keys and values being
165 : * URL percent encoded.
166 : *
167 : * @note The canonical form produced by this function is used by Amazon's later
168 : * signature formats (versions 2, 3 and 4), but not their earlier formats
169 : * (versions 0 and 1).
170 : *
171 : * @param query Query to encode the HTTP query string from.
172 : *
173 : * @return An AWS Signature canonical query string.
174 : *
175 : * @see http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
176 : * @see http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
177 : */
178 110 : QByteArray AwsAbstractSignaturePrivate::canonicalQuery(const QUrlQuery &query) const
179 : {
180 : typedef QPair<QString, QString> QStringPair;
181 110 : QList<QStringPair> list = query.queryItems(QUrl::FullyDecoded);
182 110 : qSort(list);
183 220 : QString result;
184 179 : foreach (const QStringPair &pair, list) {
185 69 : if (!result.isEmpty()) result += QLatin1Char('&');
186 138 : result += QString::fromUtf8(QUrl::toPercentEncoding(pair.first)) + QLatin1Char('=') +
187 69 : QString::fromUtf8(QUrl::toPercentEncoding(pair.second));
188 110 : }
189 220 : return result.toUtf8();
190 : }
191 :
192 : /**
193 : * @brief Create an AWS Signature request method string.
194 : *
195 : * This function simply converts QNetworkAccessManager operations (enum values)
196 : * to strings appropriate to use in AWS signatures.
197 : *
198 : * @param operation The network operation to convert to string.
199 : *
200 : * @return A string representation of \p operation, or an empty string if the
201 : * operation is not recognised or otherwise unsupported.
202 : */
203 114 : QString AwsAbstractSignaturePrivate::httpMethod(const QNetworkAccessManager::Operation operation) const
204 : {
205 114 : switch (operation) {
206 4 : case QNetworkAccessManager::DeleteOperation: return QLatin1String("DELETE");
207 1 : case QNetworkAccessManager::HeadOperation: return QLatin1String("HEAD");
208 61 : case QNetworkAccessManager::GetOperation: return QLatin1String("GET");
209 46 : case QNetworkAccessManager::PostOperation: return QLatin1String("POST");
210 1 : case QNetworkAccessManager::PutOperation: return QLatin1String("PUT");
211 : case QNetworkAccessManager::CustomOperation: // Fall through.
212 : default:
213 : // Catch this in debug mode for easier development / debugging.
214 1 : Q_ASSERT_X(false, Q_FUNC_INFO, "invalid operation");
215 : }
216 1 : return QString(); // Operation was invalid / unsupported.
217 : }
218 :
219 : /**
220 : * @brief Set a query item, checking for existing values first.
221 : *
222 : * This function is a light wrapper around QUrlQuery::addQueryItem() that first
223 : * checks for existing values. Existing values will not be overwritten, instead
224 : * if existing values are found, this function will simply check if the exsting
225 : * value matches the desired \a value, and if not, will return `false` and
226 : * optionally (according to \a warnOnNonIdenticalDuplicate) issue a qWarning().
227 : *
228 : * Typically, when setting something that must be a specific value, such as an
229 : * access key ID, \a warnOnNonIdenticalDuplicate would be `true`. However, when
230 : * setting query items as a fall-back default, such as a current timestamp,
231 : * \a warnOnNonIdenticalDuplicate would typically be set to `false`.
232 : *
233 : * @param query URL query to add the query item to.
234 : * @param key Query item key to add to \a query.
235 : * @param value Query item value to add to \a query.
236 : * @param warnOnNonIdenticalDuplicate If `true`, and an exisiting \a key
237 : * value is found in \a query that has a value other than \a value,
238 : * then a qWarning() is issued, otherwise the duplicate is silently
239 : * ignored.
240 : *
241 : * @return `true` if the query item was set successfully or was already set to
242 : * the requested value previously, `false` otherwise.
243 : */
244 37 : bool AwsAbstractSignaturePrivate::setQueryItem(QUrlQuery &query, const QString &key, const QString &value,
245 : const bool warnOnNonIdenticalDuplicate) const
246 : {
247 37 : if (query.hasQueryItem(key)) {
248 20 : const QString existingValue = query.queryItemValue(key, QUrl::FullyEncoded);
249 20 : const bool existingQueryItemIsIdentical = (existingValue == value);
250 20 : if ((warnOnNonIdenticalDuplicate) && (!existingQueryItemIsIdentical)) {
251 2 : qWarning() << "AwsAbstractSignature::setQueryItem Not overwriting existing value for key"
252 1 : << key << ':' << existingValue;
253 : }
254 20 : return existingQueryItemIsIdentical;
255 : }
256 17 : query.addQueryItem(key, value);
257 17 : return true;
258 : }
259 :
260 : /**
261 : * @fn int AwsAbstractSignature::version() const
262 : *
263 : * @brief AWS Signature version implemented by this class.
264 : *
265 : * Derived classes must implement this function to report the version of
266 : * the AWS Signature implemented by the class.
267 : *
268 : * @return The AWS Signature version implemented by this class.
269 : */
270 :
271 : QTAWS_END_NAMESPACE
|