]> git.ipfire.org Git - thirdparty/squid.git/blob - src/mime.cc
95f9ba16d2577bb2a97c56a4d974dc4225da0d53
[thirdparty/squid.git] / src / mime.cc
1 /*
2 * Copyright (C) 1996-2022 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 /* DEBUG: section 25 MIME Parsing and Internal Icons */
10
11 #include "squid.h"
12 #include "debug/Messages.h"
13 #include "fde.h"
14 #include "fs_io.h"
15 #include "globals.h"
16 #include "HttpHdrCc.h"
17 #include "HttpReply.h"
18 #include "HttpRequest.h"
19 #include "internal.h"
20 #include "MemBuf.h"
21 #include "MemObject.h"
22 #include "mime.h"
23 #include "SquidConfig.h"
24 #include "Store.h"
25 #include "StoreClient.h"
26
27 #include <array>
28
29 #if HAVE_REGEX_H
30 #include <regex.h>
31 #endif
32
33 #if HAVE_SYS_STAT_H
34 #include <sys/stat.h>
35 #endif
36
37 /* forward declarations */
38 static void mimeFreeMemory(void);
39 static const SBuf mimeGetIcon(const char *fn);
40
41 class MimeIcon : public StoreClient
42 {
43 MEMPROXY_CLASS(MimeIcon);
44
45 public:
46 explicit MimeIcon(const char *aName);
47 ~MimeIcon() override;
48 void setName(char const *);
49 SBuf getName() const;
50 void load();
51
52 /* StoreClient API */
53 LogTags *loggingTags() const override { return nullptr; } // no access logging/ACLs
54 void fillChecklist(ACLFilledChecklist &) const override;
55
56 private:
57 SBuf icon_;
58 char *url_;
59 };
60
61 class MimeEntry
62 {
63 MEMPROXY_CLASS(MimeEntry);
64
65 public:
66 explicit MimeEntry(const char *aPattern, const regex_t &compiledPattern,
67 const char *aContentType,
68 const char *aContentEncoding, const char *aTransferMode,
69 bool optionViewEnable, bool optionDownloadEnable,
70 const char *anIconName);
71 ~MimeEntry();
72
73 const char *pattern;
74 regex_t compiled_pattern;
75 const char *content_type;
76 const char *content_encoding;
77 char transfer_mode;
78 bool view_option;
79 bool download_option;
80 MimeIcon theIcon;
81 MimeEntry *next;
82 };
83
84 static MimeEntry *MimeTable = nullptr;
85 static MimeEntry **MimeTableTail = &MimeTable;
86
87 static MimeEntry *
88 mimeGetEntry(const char *fn, int skip_encodings)
89 {
90 MimeEntry *m;
91 char *t;
92 char *name = xstrdup(fn);
93
94 do {
95 t = nullptr;
96
97 for (m = MimeTable; m; m = m->next) {
98 if (regexec(&m->compiled_pattern, name, 0, nullptr, 0) == 0)
99 break;
100 }
101
102 if (!skip_encodings)
103 (void) 0;
104 else if (m == nullptr)
105 (void) 0;
106 else if (strcmp(m->content_type, dash_str))
107 (void) 0;
108 else if (!strcmp(m->content_encoding, dash_str))
109 (void) 0;
110 else {
111 /* Assume we matched /\.\w$/ and cut off the last extension */
112 if ((t = strrchr(name, '.'))) {
113 *t = '\0';
114 } else {
115 /* What? A encoding without a extension? */
116 m = nullptr;
117 }
118 }
119 } while (t);
120
121 xfree(name);
122 return m;
123 }
124
125 MimeIcon::MimeIcon(const char *aName) :
126 url_(nullptr)
127 {
128 setName(aName);
129 }
130
131 MimeIcon::~MimeIcon()
132 {
133 xfree(url_);
134 }
135
136 void
137 MimeIcon::setName(char const *aString)
138 {
139 xfree(url_);
140 icon_ = aString;
141 url_ = xstrdup(internalLocalUri("/squid-internal-static/icons/", icon_));
142 }
143
144 SBuf
145 MimeIcon::getName() const
146 {
147 return icon_;
148 }
149
150 const SBuf
151 mimeGetIcon(const char *fn)
152 {
153 MimeEntry *m = mimeGetEntry(fn, 1);
154
155 if (!m || !m->theIcon.getName().cmp(dash_str))
156 return SBuf();
157
158 return m->theIcon.getName();
159 }
160
161 const char *
162 mimeGetIconURL(const char *fn)
163 {
164 SBuf icon(mimeGetIcon(fn));
165
166 if (icon.isEmpty())
167 return null_string;
168
169 if (Config.icons.use_short_names) {
170 static SBuf mb;
171 mb.clear();
172 mb.append("/squid-internal-static/icons/");
173 mb.append(icon);
174 return mb.c_str();
175 } else {
176 return internalLocalUri("/squid-internal-static/icons/", icon);
177 }
178 }
179
180 const char *
181 mimeGetContentType(const char *fn)
182 {
183 MimeEntry *m = mimeGetEntry(fn, 1);
184
185 if (m == nullptr)
186 return nullptr;
187
188 if (!strcmp(m->content_type, dash_str))
189 return nullptr;
190
191 return m->content_type;
192 }
193
194 const char *
195 mimeGetContentEncoding(const char *fn)
196 {
197 MimeEntry *m = mimeGetEntry(fn, 0);
198
199 if (m == nullptr)
200 return nullptr;
201
202 if (!strcmp(m->content_encoding, dash_str))
203 return nullptr;
204
205 return m->content_encoding;
206 }
207
208 char
209 mimeGetTransferMode(const char *fn)
210 {
211 MimeEntry *m = mimeGetEntry(fn, 0);
212 return m ? m->transfer_mode : 'I';
213 }
214
215 bool
216 mimeGetDownloadOption(const char *fn)
217 {
218 MimeEntry *m = mimeGetEntry(fn, 1);
219 return m ? m->download_option : 0;
220 }
221
222 bool
223 mimeGetViewOption(const char *fn)
224 {
225 MimeEntry *m = mimeGetEntry(fn, 0);
226 return m != nullptr ? m->view_option : false;
227 }
228
229 /* Initializes/reloads the mime table
230 * Note: Due to Solaris STDIO problems the caller should NOT
231 * call mimeFreeMemory on reconfigure. This way, if STDIO
232 * fails we at least have the old copy loaded.
233 */
234 void
235 mimeInit(char *filename)
236 {
237 FILE *fp;
238 char buf[BUFSIZ];
239 char chopbuf[BUFSIZ];
240 char *t;
241 char *pattern;
242 char *icon;
243 char *type;
244 char *encoding;
245 char *mode;
246 char *option;
247 int view_option;
248 int download_option;
249 regex_t re;
250 MimeEntry *m;
251 int re_flags = REG_EXTENDED | REG_NOSUB | REG_ICASE;
252
253 if (filename == nullptr)
254 return;
255
256 if ((fp = fopen(filename, "r")) == nullptr) {
257 int xerrno = errno;
258 debugs(25, DBG_IMPORTANT, "mimeInit: " << filename << ": " << xstrerr(xerrno));
259 return;
260 }
261
262 #if _SQUID_WINDOWS_
263 setmode(fileno(fp), O_TEXT);
264 #endif
265
266 mimeFreeMemory();
267
268 while (fgets(buf, BUFSIZ, fp)) {
269 if ((t = strchr(buf, '#')))
270 *t = '\0';
271
272 if ((t = strchr(buf, '\r')))
273 *t = '\0';
274
275 if ((t = strchr(buf, '\n')))
276 *t = '\0';
277
278 if (buf[0] == '\0')
279 continue;
280
281 xstrncpy(chopbuf, buf, BUFSIZ);
282
283 if ((pattern = strtok(chopbuf, w_space)) == nullptr) {
284 debugs(25, DBG_IMPORTANT, "ERROR: mimeInit: parse failure: '" << buf << "'");
285 continue;
286 }
287
288 if ((type = strtok(nullptr, w_space)) == nullptr) {
289 debugs(25, DBG_IMPORTANT, "ERROR: mimeInit: parse failure: '" << buf << "'");
290 continue;
291 }
292
293 if ((icon = strtok(nullptr, w_space)) == nullptr) {
294 debugs(25, DBG_IMPORTANT, "ERROR: mimeInit: parse failure: '" << buf << "'");
295 continue;
296 }
297
298 if ((encoding = strtok(nullptr, w_space)) == nullptr) {
299 debugs(25, DBG_IMPORTANT, "ERROR: mimeInit: parse failure: '" << buf << "'");
300 continue;
301 }
302
303 if ((mode = strtok(nullptr, w_space)) == nullptr) {
304 debugs(25, DBG_IMPORTANT, "ERROR: mimeInit: parse failure: '" << buf << "'");
305 continue;
306 }
307
308 download_option = 0;
309 view_option = 0;
310
311 while ((option = strtok(nullptr, w_space)) != nullptr) {
312 if (!strcmp(option, "+download"))
313 download_option = 1;
314 else if (!strcmp(option, "+view"))
315 view_option = 1;
316 else
317 debugs(25, DBG_IMPORTANT, "ERROR: mimeInit: unknown option: '" << buf << "' (" << option << ")");
318 }
319
320 if (regcomp(&re, pattern, re_flags) != 0) {
321 debugs(25, DBG_IMPORTANT, "ERROR: mimeInit: regcomp failure: '" << buf << "'");
322 continue;
323 }
324
325 m = new MimeEntry(pattern,re,type,encoding,mode,view_option,
326 download_option,icon);
327
328 *MimeTableTail = m;
329
330 MimeTableTail = &m->next;
331
332 debugs(25, 5, "mimeInit: added '" << buf << "'");
333 }
334
335 fclose(fp);
336
337 for (m = MimeTable; m != nullptr; m = m->next)
338 m->theIcon.load();
339 debugs(25, Important(28), "Finished loading MIME types and icons.");
340 }
341
342 void
343 mimeFreeMemory(void)
344 {
345 MimeEntry *m;
346
347 while ((m = MimeTable)) {
348 MimeTable = m->next;
349 delete m;
350 }
351
352 MimeTableTail = &MimeTable;
353 }
354
355 void
356 MimeIcon::load()
357 {
358 const char *type = mimeGetContentType(icon_.c_str());
359
360 if (type == nullptr)
361 fatal("Unknown icon format while reading mime.conf\n");
362
363 if (const auto e = storeGetPublic(url_, Http::METHOD_GET)) {
364 // do not overwrite an already stored icon
365 e->abandon(__FUNCTION__);
366 return;
367 }
368
369 // XXX: if a 204 is cached due to earlier load 'failure' we should try to reload.
370
371 // default is a 200 object with image data.
372 // set to the backup value of 204 on image loading errors
373 Http::StatusCode status = Http::scOkay;
374
375 static char path[MAXPATHLEN];
376 *path = 0;
377 if (snprintf(path, sizeof(path)-1, "%s/" SQUIDSBUFPH, Config.icons.directory, SQUIDSBUFPRINT(icon_)) < 0) {
378 debugs(25, DBG_CRITICAL, "ERROR: icon file '" << Config.icons.directory << "/" << icon_ << "' path is longer than " << MAXPATHLEN << " bytes");
379 status = Http::scNoContent;
380 }
381
382 int fd = -1;
383 errno = 0;
384 if (status == Http::scOkay && (fd = file_open(path, O_RDONLY | O_BINARY)) < 0) {
385 int xerrno = errno;
386 debugs(25, DBG_CRITICAL, "ERROR: opening icon file " << path << ": " << xstrerr(xerrno));
387 status = Http::scNoContent;
388 }
389
390 struct stat sb;
391 errno = 0;
392 if (status == Http::scOkay && fstat(fd, &sb) < 0) {
393 int xerrno = errno;
394 debugs(25, DBG_CRITICAL, "ERROR: opening icon file " << path << " FD " << fd << ", fstat error " << xstrerr(xerrno));
395 file_close(fd);
396 status = Http::scNoContent;
397 }
398
399 StoreEntry *e = storeCreatePureEntry(url_, url_, Http::METHOD_GET);
400 e->lock("MimeIcon::created");
401 EBIT_SET(e->flags, ENTRY_SPECIAL);
402 const auto madePublic = e->setPublicKey();
403 assert(madePublic); // nothing can block ENTRY_SPECIAL from becoming public
404
405 /* fill `e` with a canned 2xx response object */
406
407 const auto mx = MasterXaction::MakePortless<XactionInitiator::initIcon>();
408 HttpRequestPointer r(HttpRequest::FromUrlXXX(url_, mx));
409 if (!r)
410 fatalf("mimeLoadIcon: cannot parse internal URL: %s", url_);
411
412 e->buffer();
413
414 e->mem_obj->request = r;
415
416 HttpReplyPointer reply(new HttpReply);
417
418 if (status == Http::scNoContent)
419 reply->setHeaders(status, nullptr, nullptr, 0, -1, -1);
420 else
421 reply->setHeaders(status, nullptr, mimeGetContentType(icon_.c_str()), sb.st_size, sb.st_mtime, -1);
422 reply->cache_control = new HttpHdrCc();
423 reply->cache_control->maxAge(86400);
424 reply->header.putCc(reply->cache_control);
425 e->replaceHttpReply(reply.getRaw());
426
427 if (status == Http::scOkay) {
428 /* read the file into the buffer and append it to store */
429 int n;
430 std::array<char, 4096> buf;
431 while ((n = FD_READ_METHOD(fd, buf.data(), buf.size())) > 0)
432 e->append(buf.data(), n);
433
434 file_close(fd);
435 }
436
437 e->flush();
438 e->complete();
439 e->timestampsSet();
440 // MimeIcons are only loaded once, prevent accidental destruction
441 // e->unlock("MimeIcon::created");
442 debugs(25, 3, "Loaded icon " << url_);
443 }
444
445 void
446 MimeIcon::fillChecklist(ACLFilledChecklist &) const
447 {
448 // Unreachable: We never mayInitiateCollapsing() or startCollapsingOn().
449 assert(false);
450 }
451
452 MimeEntry::~MimeEntry()
453 {
454 xfree(pattern);
455 xfree(content_type);
456 xfree(content_encoding);
457 regfree(&compiled_pattern);
458 }
459
460 MimeEntry::MimeEntry(const char *aPattern, const regex_t &compiledPattern,
461 const char *aContentType, const char *aContentEncoding,
462 const char *aTransferMode, bool optionViewEnable,
463 bool optionDownloadEnable, const char *anIconName) :
464 pattern(xstrdup(aPattern)),
465 compiled_pattern(compiledPattern),
466 content_type(xstrdup(aContentType)),
467 content_encoding(xstrdup(aContentEncoding)),
468 view_option(optionViewEnable),
469 download_option(optionDownloadEnable),
470 theIcon(anIconName), next(nullptr)
471 {
472 if (!strcasecmp(aTransferMode, "ascii"))
473 transfer_mode = 'A';
474 else if (!strcasecmp(aTransferMode, "text"))
475 transfer_mode = 'A';
476 else
477 transfer_mode = 'I';
478 }
479