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