]> git.ipfire.org Git - thirdparty/squid.git/blob - src/mime.cc
Merged from trunk rev.14183
[thirdparty/squid.git] / src / mime.cc
1 /*
2 * Copyright (C) 1996-2015 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 "disk.h"
14 #include "fde.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 #if HAVE_SYS_STAT_H
29 #include <sys/stat.h>
30 #endif
31
32 /* forward declarations */
33 static void mimeFreeMemory(void);
34 static const SBuf mimeGetIcon(const char *fn);
35
36 class MimeIcon : public StoreClient
37 {
38 MEMPROXY_CLASS(MimeIcon);
39
40 public:
41 explicit MimeIcon(const char *aName);
42 ~MimeIcon();
43 void setName(char const *);
44 SBuf getName() const;
45 void load();
46
47 /* StoreClient API */
48 virtual void created(StoreEntry *);
49
50 private:
51 SBuf icon_;
52 char *url_;
53 };
54
55 class MimeEntry
56 {
57 MEMPROXY_CLASS(MimeEntry);
58
59 public:
60 MimeEntry(const char *aPattern, const decltype(RegexPattern::flags) &reFlags,
61 const char *aContentType,
62 const char *aContentEncoding, const char *aTransferMode,
63 bool optionViewEnable, bool optionDownloadEnable,
64 const char *anIconName);
65 MimeEntry(const MimeEntry &) = delete;
66 MimeEntry(const MimeEntry &&) = delete;
67 ~MimeEntry();
68
69 RegexPattern 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 (m->pattern.match(name))
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 icon_(aName)
122 {
123 url_ = xstrdup(internalLocalUri("/squid-internal-static/icons/", icon_));
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 *icon;
237 char *type;
238 char *encoding;
239 char *mode;
240 char *option;
241 int view_option;
242 int download_option;
243 MimeEntry *m;
244
245 if (filename == NULL)
246 return;
247
248 if ((fp = fopen(filename, "r")) == NULL) {
249 debugs(25, DBG_IMPORTANT, "mimeInit: " << filename << ": " << xstrerror());
250 return;
251 }
252
253 #if _SQUID_WINDOWS_
254 setmode(fileno(fp), O_TEXT);
255 #endif
256
257 mimeFreeMemory();
258
259 const auto re_flags = std::regex::extended | std::regex::nosubs | std::regex::icase;
260
261 while (fgets(buf, BUFSIZ, fp)) {
262 if ((t = strchr(buf, '#')))
263 *t = '\0';
264
265 if ((t = strchr(buf, '\r')))
266 *t = '\0';
267
268 if ((t = strchr(buf, '\n')))
269 *t = '\0';
270
271 if (buf[0] == '\0')
272 continue;
273
274 xstrncpy(chopbuf, buf, BUFSIZ);
275
276 char *pattern;
277 if ((pattern = strtok(chopbuf, w_space)) == NULL) {
278 debugs(25, DBG_IMPORTANT, "mimeInit: parse error: '" << buf << "'");
279 continue;
280 }
281
282 if ((type = strtok(NULL, w_space)) == NULL) {
283 debugs(25, DBG_IMPORTANT, "mimeInit: parse error: '" << buf << "'");
284 continue;
285 }
286
287 if ((icon = strtok(NULL, w_space)) == NULL) {
288 debugs(25, DBG_IMPORTANT, "mimeInit: parse error: '" << buf << "'");
289 continue;
290 }
291
292 if ((encoding = strtok(NULL, w_space)) == NULL) {
293 debugs(25, DBG_IMPORTANT, "mimeInit: parse error: '" << buf << "'");
294 continue;
295 }
296
297 if ((mode = strtok(NULL, w_space)) == NULL) {
298 debugs(25, DBG_IMPORTANT, "mimeInit: parse error: '" << buf << "'");
299 continue;
300 }
301
302 download_option = 0;
303 view_option = 0;
304
305 while ((option = strtok(NULL, w_space)) != NULL) {
306 if (!strcmp(option, "+download"))
307 download_option = 1;
308 else if (!strcmp(option, "+view"))
309 view_option = 1;
310 else
311 debugs(25, DBG_IMPORTANT, "mimeInit: unknown option: '" << buf << "' (" << option << ")");
312 }
313
314 try {
315 m = new MimeEntry(pattern, re_flags, type, encoding, mode, view_option, download_option, icon);
316
317 } catch (std::regex_error &e) {
318 debugs(25, DBG_IMPORTANT, "mimeInit: invalid regular expression: '" << buf << "'");
319 continue;
320 }
321
322 *MimeTableTail = m;
323
324 MimeTableTail = &m->next;
325
326 debugs(25, 5, "mimeInit: added '" << buf << "'");
327 }
328
329 fclose(fp);
330
331 for (m = MimeTable; m != NULL; m = m->next)
332 m->theIcon.load();
333 debugs(25, DBG_IMPORTANT, "Finished loading MIME types and icons.");
334 }
335
336 void
337 mimeFreeMemory(void)
338 {
339 MimeEntry *m;
340
341 while ((m = MimeTable)) {
342 MimeTable = m->next;
343 delete m;
344 }
345
346 MimeTableTail = &MimeTable;
347 }
348
349 void
350 MimeIcon::load()
351 {
352 const char *type = mimeGetContentType(icon_.c_str());
353
354 if (type == NULL)
355 fatal("Unknown icon format while reading mime.conf\n");
356
357 StoreEntry::getPublic(this, url_, Http::METHOD_GET);
358 }
359
360 void
361 MimeIcon::created(StoreEntry *newEntry)
362 {
363 /* if the icon is already in the store, do nothing */
364 if (!newEntry->isNull())
365 return;
366 // XXX: if a 204 is cached due to earlier load 'failure' we should try to reload.
367
368 // default is a 200 object with image data.
369 // set to the backup value of 204 on image loading errors
370 Http::StatusCode status = Http::scOkay;
371
372 static char path[MAXPATHLEN];
373 *path = 0;
374 if (snprintf(path, sizeof(path)-1, "%s/" SQUIDSBUFPH, Config.icons.directory, SQUIDSBUFPRINT(icon_)) < 0) {
375 debugs(25, DBG_CRITICAL, "ERROR: icon file '" << Config.icons.directory << "/" << icon_ << "' path is longer than " << MAXPATHLEN << " bytes");
376 status = Http::scNoContent;
377 }
378
379 int fd = -1;
380 errno = 0;
381 if (status == Http::scOkay && (fd = file_open(path, O_RDONLY | O_BINARY)) < 0) {
382 int xerrno = errno;
383 debugs(25, DBG_CRITICAL, "ERROR: opening icon file " << path << ": " << xstrerr(xerrno));
384 status = Http::scNoContent;
385 }
386
387 struct stat sb;
388 errno = 0;
389 if (status == Http::scOkay && fstat(fd, &sb) < 0) {
390 int xerrno = errno;
391 debugs(25, DBG_CRITICAL, "ERROR: opening icon file " << path << " FD " << fd << ", fstat error " << xstrerr(xerrno));
392 file_close(fd);
393 status = Http::scNoContent;
394 }
395
396 // fill newEntry with a canned 2xx response object
397 RequestFlags flags;
398 flags.cachable = true;
399 StoreEntry *e = storeCreateEntry(url_,url_,flags,Http::METHOD_GET);
400 assert(e != NULL);
401 EBIT_SET(e->flags, ENTRY_SPECIAL);
402 e->setPublicKey();
403 e->buffer();
404 HttpRequest *r = HttpRequest::CreateFromUrl(url_);
405
406 if (NULL == r)
407 fatalf("mimeLoadIcon: cannot parse internal URL: %s", url_);
408
409 e->mem_obj->request = r;
410 HTTPMSGLOCK(e->mem_obj->request);
411
412 HttpReply *reply = new HttpReply;
413
414 if (status == Http::scNoContent)
415 reply->setHeaders(status, NULL, NULL, 0, -1, -1);
416 else
417 reply->setHeaders(status, NULL, mimeGetContentType(icon_.c_str()), sb.st_size, sb.st_mtime, -1);
418 reply->cache_control = new HttpHdrCc();
419 reply->cache_control->maxAge(86400);
420 reply->header.putCc(reply->cache_control);
421 e->replaceHttpReply(reply);
422
423 if (status == Http::scOkay) {
424 /* read the file into the buffer and append it to store */
425 int n;
426 char *buf = (char *)memAllocate(MEM_4K_BUF);
427 while ((n = FD_READ_METHOD(fd, buf, sizeof(*buf))) > 0)
428 e->append(buf, n);
429
430 file_close(fd);
431 memFree(buf, MEM_4K_BUF);
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(content_type);
444 xfree(content_encoding);
445 }
446
447 MimeEntry::MimeEntry(const char *aPattern, const decltype(RegexPattern::flags) &reFlags,
448 const char *aContentType, const char *aContentEncoding,
449 const char *aTransferMode, bool optionViewEnable,
450 bool optionDownloadEnable, const char *anIconName) :
451 pattern(reFlags, aPattern),
452 content_type(xstrdup(aContentType)),
453 content_encoding(xstrdup(aContentEncoding)),
454 view_option(optionViewEnable),
455 download_option(optionDownloadEnable),
456 theIcon(anIconName),
457 next(nullptr)
458 {
459 if (!strcasecmp(aTransferMode, "ascii"))
460 transfer_mode = 'A';
461 else if (!strcasecmp(aTransferMode, "text"))
462 transfer_mode = 'A';
463 else
464 transfer_mode = 'I';
465 }
466