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