MythTV master
mythhttpcache.cpp
Go to the documentation of this file.
1// Qt
2#include <QCryptographicHash>
3
4// MythTV
5#include "mythdate.h"
6#include "http/mythhttpdata.h"
7#include "http/mythhttpfile.h"
9
51{
52 // Retrieve content
53 auto * data = std::get_if<HTTPData>(&Response->m_response);
54 auto * file = std::get_if<HTTPFile>(&Response->m_response);
55 if (!(file || data))
56 return;
57
58 int cache = data ? (*data)->m_cacheType : (*file)->m_cacheType;
59
60 // Explicitly ignore caching (error responses etc)
61 if (cache == HTTPIgnoreCache)
62 return;
63
64 // Explicitly request no caching
65 if ((cache & HTTPNoCache) == HTTPNoCache)
66 return;
67
68 // We use the last modified data for both the last modified header and potentially
69 // hashing for the etag
70 auto & lastmodified = data ? (*data)->m_lastModified : (*file)->m_lastModified;
71 if (!lastmodified.isValid())
72 lastmodified = QDateTime::currentDateTime();
73
74 // If-Range precondition can contain either a last-modified or ETag validator
75 QString ifrange = MythHTTP::GetHeader(Response->m_requestHeaders, "if-range");
76 bool checkifrange = !ifrange.isEmpty();
77 bool removeranges = false;
78
79 if ((cache & HTTPETag) == HTTPETag)
80 {
81 QByteArray& etag = data ? (*data)->m_etag : (*file)->m_etag;
82 if (file)
83 {
84 QByteArray hashdata = ((*file)->fileName() + lastmodified.toString("ddMMyyyyhhmmsszzz")).toLocal8Bit().constData();
85 etag = QCryptographicHash::hash(hashdata, QCryptographicHash::Sha224).toHex();
86 }
87 else
88 {
89 etag = QCryptographicHash::hash((*data)->constData(), QCryptographicHash::Sha224).toHex();
90 }
91
92 // This assumes only one or other is present...
93 if (checkifrange)
94 {
95 removeranges = !ifrange.contains(etag);
96 }
97 else
98 {
99 QString nonematch = MythHTTP::GetHeader(Response->m_requestHeaders, "if-none-match");
100 if (!nonematch.isEmpty() && (nonematch.contains(etag)))
101 Response->m_status = HTTPNotModified;
102 }
103 }
104 else if (((cache & HTTPLastModified) == HTTPLastModified))
105 {
106 auto RemoveMilliseconds = [](QDateTime& DateTime)
107 {
108 auto residual = DateTime.time().msec();
109 DateTime = DateTime.addMSecs(-residual);
110 };
111
112 auto ParseModified = [](const QString& Modified)
113 {
114 QDateTime time = QDateTime::fromString(Modified, Qt::RFC2822Date);
115#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
116 time.setTimeSpec(Qt::UTC);
117#else
118 time.setTimeZone(QTimeZone(QTimeZone::UTC));
119#endif
120 return time;
121 };
122
123 if (checkifrange)
124 {
125 auto time = ParseModified(ifrange);
126 if (time.isValid())
127 {
128 RemoveMilliseconds(lastmodified);
129 removeranges = lastmodified > time;
130 }
131 }
132 else if (Response->m_requestType == HTTPGet || Response->m_requestType == HTTPHead)
133 {
134 QString modified = MythHTTP::GetHeader(Response->m_requestHeaders, "if-modified-since");
135 if (!modified.isEmpty())
136 {
137 auto time = ParseModified(modified);
138 if (time.isValid())
139 {
140 RemoveMilliseconds(lastmodified);
141 if (lastmodified <= time)
142 Response->m_status = HTTPNotModified;
143 }
144 }
145 }
146 }
147
148 // If the If-Range precondition fails, then we need to send back the complete
149 // contents i.e. ignore any ranges. So search for the range headers and remove them.
150 if (removeranges && Response->m_requestHeaders && Response->m_requestHeaders->contains("range"))
151 Response->m_requestHeaders->replace("range", "");
152}
153
159{
160 // Retrieve content
161 auto * data = std::get_if<HTTPData>(&Response->m_response);
162 auto * file = std::get_if<HTTPFile>(&Response->m_response);
163 if (!(file || data))
164 return;
165
166 int cache = data ? (*data)->m_cacheType : (*file)->m_cacheType;
167
168 // Explicitly ignore caching (error responses etc)
169 if (cache == HTTPIgnoreCache)
170 return;
171
172 // Explicitly request no caching
173 if ((cache & HTTPNoCache) == HTTPNoCache)
174 {
175 Response->AddHeader("Cache-Control", "no-store, max-age=0");
176 return;
177 }
178
179 // Add the default cache control header
180 QString duration {"0"}; // ??
181 if ((cache & HTTPLongLife) == HTTPLongLife)
182 duration = "604800"; // 7 days
183 else if ((cache & HTTPMediumLife) == HTTPMediumLife)
184 duration = "7200"; // 2 Hours
185 Response->AddHeader("Cache-Control", "no-cache=\"Ext\",max-age=" + duration);
186
187 if ((cache & HTTPETag) == HTTPETag)
188 {
189 Response->AddHeader("ETag", QString("\"") + (data ? (*data)->m_etag : (*file)->m_etag) + "\"");
190 }
191 else if ((cache & HTTPLastModified) == HTTPLastModified)
192 {
193 auto & lastmodified = data ? (*data)->m_lastModified : (*file)->m_lastModified;
195 Response->AddHeader("Last-Modified", last);
196 }
197}
static void PreConditionHeaders(const HTTPResponse &Response)
Add precondition (cache) headers to the response.
static void PreConditionCheck(const HTTPResponse &Response)
Process precondition checks.
static QString GetHeader(const HTTPHeaders &Headers, const QString &Value, const QString &Default="")
static bool modified(uint64_t sig)
Definition: eitcache.cpp:90
@ HTTPNotModified
@ HTTPGet
Definition: mythhttptypes.h:94
@ HTTPHead
Definition: mythhttptypes.h:93
std::shared_ptr< MythHTTPResponse > HTTPResponse
Definition: mythhttptypes.h:40
@ HTTPMediumLife
@ HTTPNoCache
@ HTTPETag
@ HTTPLastModified
@ HTTPIgnoreCache
@ HTTPLongLife
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
Definition: mythdate.cpp:93
@ kOverrideUTC
Present date/time in UTC.
Definition: mythdate.h:31
@ kRFC822
HTTP Date format.
Definition: mythdate.h:30
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:39