]> git.ipfire.org Git - thirdparty/squid.git/blob - src/mime.cc
Merged from v5 r14954
[thirdparty/squid.git] / src / mime.cc
1 /*
2 * Copyright (C) 1996-2016 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 "base/RegexPattern.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 "RequestFlags.h"
24 #include "SquidConfig.h"
25 #include "Store.h"
26 #include "StoreClient.h"
27
28 #include <array>
29
30 #if HAVE_SYS_STAT_H
31 #include <sys/stat.h>
32 #endif
33
34 /* forward declarations */
35 static void mimeFreeMemory(void);
36 static const SBuf mimeGetIcon(const char *fn);
37
38 class MimeIcon : public StoreClient
39 {
40 MEMPROXY_CLASS(MimeIcon);
41
42 public:
43 explicit MimeIcon(const char *aName);
44 ~MimeIcon();
45 void setName(char const *);
46 SBuf getName() const;
47 void load();
48
49 /* StoreClient API */
50 virtual void created(StoreEntry *);
51
52 private:
53 SBuf icon_;
54 char *url_;
55 };
56
57 class MimeEntry
58 {
59 MEMPROXY_CLASS(MimeEntry);
60
61 public:
62 MimeEntry(const char *aPattern, const decltype(RegexPattern::flags) &reFlags,
63 const char *aContentType,
64 const char *aContentEncoding, const char *aTransferMode,
65 bool optionViewEnable, bool optionDownloadEnable,
66 const char *anIconName);
67 MimeEntry(const MimeEntry &) = delete;
68 MimeEntry(const MimeEntry &&) = delete;
69 ~MimeEntry();
70
71 RegexPattern pattern;
72 const char *content_type;
73 const char *content_encoding;
74 char transfer_mode;
75 bool view_option;
76 bool download_option;
77 MimeIcon theIcon;
78 MimeEntry *next;
79 };
80
81 static MimeEntry *MimeTable = NULL;
82 static MimeEntry **MimeTableTail = &MimeTable;
83
84 static MimeEntry *
85 mimeGetEntry(const char *fn, int skip_encodings)
86 {
87 MimeEntry *m;
88 char *t;
89 char *name = xstrdup(fn);
90
91 do {
92 t = NULL;
93
94 for (m = MimeTable; m; m = m->next) {
95 if (m->pattern.match(name))
96 break;
97 }
98
99 if (!skip_encodings)
100 (void) 0;
101 else if (m == NULL)
102 (void) 0;
103 else if (strcmp(m->content_type, dash_str))
104 (void) 0;
105 else if (!strcmp(m->content_encoding, dash_str))
106 (void) 0;
107 else {
108 /* Assume we matched /\.\w$/ and cut off the last extension */
109 if ((t = strrchr(name, '.'))) {
110 *t = '\0';
111 } else {
112 /* What? A encoding without a extension? */
113 m = NULL;
114 }
115 }
116 } while (t);
117
118 xfree(name);
119 return m;
120 }
121
122 MimeIcon::MimeIcon(const char *aName) :
123 url_(nullptr)
124 {
125 setName(aName);
126 }
127
128 MimeIcon::~MimeIcon()
129 {
130 xfree(url_);
131 }
132
133 void
134 MimeIcon::setName(char const *aString)
135 {
136 xfree(url_);
137 icon_ = aString;
138 url_ = xstrdup(internalLocalUri("/squid-internal-static/icons/", icon_));
139 }
140
141 SBuf
142 MimeIcon::getName() const
143 {
144 return icon_;
145 }
146
147 const SBuf
148 mimeGetIcon(const char *fn)
149 {
150 MimeEntry *m = mimeGetEntry(fn, 1);
151
152 if (!m || !m->theIcon.getName().cmp(dash_str))
153 return SBuf();
154
155 return m->theIcon.getName();
156 }
157
158 const char *
159 mimeGetIconURL(const char *fn)
160 {
161 SBuf icon(mimeGetIcon(fn));
162
163 if (icon.isEmpty())
164 return null_string;
165
166 if (Config.icons.use_short_names) {
167 static SBuf mb;
168 mb.clear();
169 mb.append("/squid-internal-static/icons/");
170 mb.append(icon);
171 return mb.c_str();
172 } else {
173 return internalLocalUri("/squid-internal-static/icons/", icon);
174 }
175 }
176
177 const char *
178 mimeGetContentType(const char *fn)
179 {
180 MimeEntry *m = mimeGetEntry(fn, 1);
181
182 if (m == NULL)
183 return NULL;
184
185 if (!strcmp(m->content_type, dash_str))
186 return NULL;
187
188 return m->content_type;
189 }
190
191 const char *
192 mimeGetContentEncoding(const char *fn)
193 {
194 MimeEntry *m = mimeGetEntry(fn, 0);
195
196 if (m == NULL)
197 return NULL;
198
199 if (!strcmp(m->content_encoding, dash_str))
200 return NULL;
201
202 return m->content_encoding;
203 }
204
205 char
206 mimeGetTransferMode(const char *fn)
207 {
208 MimeEntry *m = mimeGetEntry(fn, 0);
209 return m ? m->transfer_mode : 'I';
210 }
211
212 bool
213 mimeGetDownloadOption(const char *fn)
214 {
215 MimeEntry *m = mimeGetEntry(fn, 1);
216 return m ? m->download_option : 0;
217 }
218
219 bool
220 mimeGetViewOption(const char *fn)
221 {
222 MimeEntry *m = mimeGetEntry(fn, 0);
223 return m != 0 ? m->view_option : false;
224 }
225
226 /* Initializes/reloads the mime table
227 * Note: Due to Solaris STDIO problems the caller should NOT
228 * call mimeFreeMemory on reconfigure. This way, if STDIO
229 * fails we at least have the old copy loaded.
230 */
231 void
232 mimeInit(char *filename)
233 {
234 FILE *fp;
235 char buf[BUFSIZ];
236 char chopbuf[BUFSIZ];
237 char *t;
238 char *icon;
239 char *type;
240 char *encoding;
241 char *mode;
242 char *option;
243 int view_option;
244 int download_option;
245 MimeEntry *m;
246
247 if (filename == NULL)
248 return;
249
250 if ((fp = fopen(filename, "r")) == NULL) {
251 int xerrno = errno;
252 debugs(25, DBG_IMPORTANT, "mimeInit: " << filename << ": " << xstrerr(xerrno));
253 return;
254 }
255
256 #if _SQUID_WINDOWS_
257 setmode(fileno(fp), O_TEXT);
258 #endif
259
260 mimeFreeMemory();
261
262 const auto re_flags = std::regex::extended | std::regex::nosubs | std::regex::icase;
263
264 while (fgets(buf, BUFSIZ, fp)) {
265 if ((t = strchr(buf, '#')))
266 *t = '\0';
267
268 if ((t = strchr(buf, '\r')))
269 *t = '\0';
270
271 if ((t = strchr(buf, '\n')))
272 *t = '\0';
273
274 if (buf[0] == '\0')
275 continue;
276
277 xstrncpy(chopbuf, buf, BUFSIZ);
278
279 char *pattern;
280 if ((pattern = strtok(chopbuf, w_space)) == NULL) {
281 debugs(25, DBG_IMPORTANT, "mimeInit: parse error: '" << buf << "'");
282 continue;
283 }
284
285 if ((type = strtok(NULL, w_space)) == NULL) {
286 debugs(25, DBG_IMPORTANT, "mimeInit: parse error: '" << buf << "'");
287 continue;
288 }
289
290 if ((icon = strtok(NULL, w_space)) == NULL) {
291 debugs(25, DBG_IMPORTANT, "mimeInit: parse error: '" << buf << "'");
292 continue;
293 }
294
295 if ((encoding = strtok(NULL, w_space)) == NULL) {
296 debugs(25, DBG_IMPORTANT, "mimeInit: parse error: '" << buf << "'");
297 continue;
298 }
299
300 if ((mode = strtok(NULL, w_space)) == NULL) {
301 debugs(25, DBG_IMPORTANT, "mimeInit: parse error: '" << buf << "'");
302 continue;
303 }
304
305 download_option = 0;
306 view_option = 0;
307
308 while ((option = strtok(NULL, w_space)) != NULL) {
309 if (!strcmp(option, "+download"))
310 download_option = 1;
311 else if (!strcmp(option, "+view"))
312 view_option = 1;
313 else
314 debugs(25, DBG_IMPORTANT, "mimeInit: unknown option: '" << buf << "' (" << option << ")");
315 }
316
317 try {
318 m = new MimeEntry(pattern, re_flags, type, encoding, mode, view_option, download_option, icon);
319
320 } catch (std::regex_error &e) {
321 debugs(25, DBG_IMPORTANT, "mimeInit: invalid regular expression: '" << buf << "'");
322 continue;
323 }
324
325 *MimeTableTail = m;
326
327 MimeTableTail = &m->next;
328
329 debugs(25, 5, "mimeInit: added '" << buf << "'");
330 }
331
332 fclose(fp);
333
334 for (m = MimeTable; m != NULL; m = m->next)
335 m->theIcon.load();
336 debugs(25, DBG_IMPORTANT, "Finished loading MIME types and icons.");
337 }
338
339 void
340 mimeFreeMemory(void)
341 {
342 MimeEntry *m;
343
344 while ((m = MimeTable)) {
345 MimeTable = m->next;
346 delete m;
347 }
348
349 MimeTableTail = &MimeTable;
350 }
351
352 void
353 MimeIcon::load()
354 {
355 const char *type = mimeGetContentType(icon_.c_str());
356
357 if (type == NULL)
358 fatal("Unknown icon format while reading mime.conf\n");
359
360 StoreEntry::getPublic(this, url_, Http::METHOD_GET);
361 }
362
363 void
364 MimeIcon::created(StoreEntry *newEntry)
365 {
366 /* if the icon is already in the store, do nothing */
367 if (!newEntry->isNull())
368 return;
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 // fill newEntry with a canned 2xx response object
400 RequestFlags flags;
401 flags.cachable = true;
402 StoreEntry *e = storeCreateEntry(url_,url_,flags,Http::METHOD_GET);
403 assert(e != NULL);
404 EBIT_SET(e->flags, ENTRY_SPECIAL);
405 e->setPublicKey();
406 e->buffer();
407 HttpRequest *r = HttpRequest::CreateFromUrl(url_);
408
409 if (NULL == r)
410 fatalf("mimeLoadIcon: cannot parse internal URL: %s", url_);
411
412 e->mem_obj->request = r;
413 HTTPMSGLOCK(e->mem_obj->request);
414
415 HttpReply *reply = new HttpReply;
416
417 if (status == Http::scNoContent)
418 reply->setHeaders(status, NULL, NULL, 0, -1, -1);
419 else
420 reply->setHeaders(status, NULL, mimeGetContentType(icon_.c_str()), sb.st_size, sb.st_mtime, -1);
421 reply->cache_control = new HttpHdrCc();
422 reply->cache_control->maxAge(86400);
423 reply->header.putCc(reply->cache_control);
424 e->replaceHttpReply(reply);
425
426 if (status == Http::scOkay) {
427 /* read the file into the buffer and append it to store */
428 int n;
429 std::array<char, 4096> buf;
430 while ((n = FD_READ_METHOD(fd, buf.data(), buf.size())) > 0)
431 e->append(buf.data(), n);
432
433 file_close(fd);
434 }
435
436 e->flush();
437 e->complete();
438 e->timestampsSet();
439 e->unlock("MimeIcon::created");
440 debugs(25, 3, "Loaded icon " << url_);
441 }
442
443 MimeEntry::~MimeEntry()
444 {
445 xfree(content_type);
446 xfree(content_encoding);
447 }
448
449 MimeEntry::MimeEntry(const char *aPattern, const decltype(RegexPattern::flags) &reFlags,
450 const char *aContentType, const char *aContentEncoding,
451 const char *aTransferMode, bool optionViewEnable,
452 bool optionDownloadEnable, const char *anIconName) :
453 pattern(reFlags, aPattern),
454 content_type(xstrdup(aContentType)),
455 content_encoding(xstrdup(aContentEncoding)),
456 view_option(optionViewEnable),
457 download_option(optionDownloadEnable),
458 theIcon(anIconName),
459 next(nullptr)
460 {
461 if (!strcasecmp(aTransferMode, "ascii"))
462 transfer_mode = 'A';
463 else if (!strcasecmp(aTransferMode, "text"))
464 transfer_mode = 'A';
465 else
466 transfer_mode = 'I';
467 }
468