]> git.ipfire.org Git - thirdparty/squid.git/blob - src/mime.cc
Merge from trunk rev.14108
[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 "disk.h"
13 #include "fde.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 #if HAVE_SYS_STAT_H
28 #include <sys/stat.h>
29 #endif
30
31 /* forward declarations */
32 static void mimeFreeMemory(void);
33 static char const *mimeGetIcon(const char *fn);
34
35 class MimeIcon : public StoreClient
36 {
37 MEMPROXY_CLASS(MimeIcon);
38
39 public:
40 explicit MimeIcon(const char *aName);
41 ~MimeIcon();
42 void setName(char const *);
43 char const * getName() const;
44 void load();
45
46 /* StoreClient API */
47 virtual void created(StoreEntry *);
48
49 private:
50 const char *icon_;
51 char *url_;
52 };
53
54 class MimeEntry
55 {
56 MEMPROXY_CLASS(MimeEntry);
57
58 public:
59 explicit MimeEntry(const char *aPattern, const regex_t &compiledPattern,
60 const char *aContentType,
61 const char *aContentEncoding, const char *aTransferMode,
62 bool optionViewEnable, bool optionDownloadEnable,
63 const char *anIconName);
64 ~MimeEntry();
65
66 const char *pattern;
67 regex_t compiled_pattern;
68 const char *content_type;
69 const char *content_encoding;
70 char transfer_mode;
71 bool view_option;
72 bool download_option;
73 MimeIcon theIcon;
74 MimeEntry *next;
75 };
76
77 static MimeEntry *MimeTable = NULL;
78 static MimeEntry **MimeTableTail = &MimeTable;
79
80 static MimeEntry *
81 mimeGetEntry(const char *fn, int skip_encodings)
82 {
83 MimeEntry *m;
84 char *t;
85 char *name = xstrdup(fn);
86
87 do {
88 t = NULL;
89
90 for (m = MimeTable; m; m = m->next) {
91 if (regexec(&m->compiled_pattern, name, 0, 0, 0) == 0)
92 break;
93 }
94
95 if (!skip_encodings)
96 (void) 0;
97 else if (m == NULL)
98 (void) 0;
99 else if (strcmp(m->content_type, dash_str))
100 (void) 0;
101 else if (!strcmp(m->content_encoding, dash_str))
102 (void) 0;
103 else {
104 /* Assume we matched /\.\w$/ and cut off the last extension */
105 if ((t = strrchr(name, '.'))) {
106 *t = '\0';
107 } else {
108 /* What? A encoding without a extension? */
109 m = NULL;
110 }
111 }
112 } while (t);
113
114 xfree(name);
115 return m;
116 }
117
118 MimeIcon::MimeIcon(const char *aName) :
119 icon_(xstrdup(aName))
120 {
121 url_ = xstrdup(internalLocalUri("/squid-internal-static/icons/", icon_));
122 }
123
124 MimeIcon::~MimeIcon()
125 {
126 xfree(icon_);
127 xfree(url_);
128 }
129
130 void
131 MimeIcon::setName(char const *aString)
132 {
133 xfree(icon_);
134 xfree(url_);
135 icon_ = xstrdup(aString);
136 url_ = xstrdup(internalLocalUri("/squid-internal-static/icons/", icon_));
137 }
138
139 char const *
140 MimeIcon::getName() const
141 {
142 return icon_;
143 }
144
145 char const *
146 mimeGetIcon(const char *fn)
147 {
148 MimeEntry *m = mimeGetEntry(fn, 1);
149
150 if (m == NULL)
151 return NULL;
152
153 if (!strcmp(m->theIcon.getName(), dash_str))
154 return NULL;
155
156 return m->theIcon.getName();
157 }
158
159 const char *
160 mimeGetIconURL(const char *fn)
161 {
162 char const *icon = mimeGetIcon(fn);
163
164 if (icon == NULL)
165 return null_string;
166
167 if (Config.icons.use_short_names) {
168 static MemBuf mb;
169 mb.reset();
170 mb.appendf("/squid-internal-static/icons/%s", icon);
171 return mb.content();
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 *pattern;
239 char *icon;
240 char *type;
241 char *encoding;
242 char *mode;
243 char *option;
244 int view_option;
245 int download_option;
246 regex_t re;
247 MimeEntry *m;
248 int re_flags = REG_EXTENDED | REG_NOSUB | REG_ICASE;
249
250 if (filename == NULL)
251 return;
252
253 if ((fp = fopen(filename, "r")) == NULL) {
254 debugs(25, DBG_IMPORTANT, "mimeInit: " << filename << ": " << xstrerror());
255 return;
256 }
257
258 #if _SQUID_WINDOWS_
259 setmode(fileno(fp), O_TEXT);
260 #endif
261
262 mimeFreeMemory();
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 if ((pattern = strtok(chopbuf, w_space)) == NULL) {
280 debugs(25, DBG_IMPORTANT, "mimeInit: parse error: '" << buf << "'");
281 continue;
282 }
283
284 if ((type = strtok(NULL, w_space)) == NULL) {
285 debugs(25, DBG_IMPORTANT, "mimeInit: parse error: '" << buf << "'");
286 continue;
287 }
288
289 if ((icon = strtok(NULL, w_space)) == NULL) {
290 debugs(25, DBG_IMPORTANT, "mimeInit: parse error: '" << buf << "'");
291 continue;
292 }
293
294 if ((encoding = strtok(NULL, w_space)) == NULL) {
295 debugs(25, DBG_IMPORTANT, "mimeInit: parse error: '" << buf << "'");
296 continue;
297 }
298
299 if ((mode = strtok(NULL, w_space)) == NULL) {
300 debugs(25, DBG_IMPORTANT, "mimeInit: parse error: '" << buf << "'");
301 continue;
302 }
303
304 download_option = 0;
305 view_option = 0;
306
307 while ((option = strtok(NULL, w_space)) != NULL) {
308 if (!strcmp(option, "+download"))
309 download_option = 1;
310 else if (!strcmp(option, "+view"))
311 view_option = 1;
312 else
313 debugs(25, DBG_IMPORTANT, "mimeInit: unknown option: '" << buf << "' (" << option << ")");
314 }
315
316 if (regcomp(&re, pattern, re_flags) != 0) {
317 debugs(25, DBG_IMPORTANT, "mimeInit: regcomp error: '" << buf << "'");
318 continue;
319 }
320
321 m = new MimeEntry(pattern,re,type,encoding,mode,view_option,
322 download_option,icon);
323
324 *MimeTableTail = m;
325
326 MimeTableTail = &m->next;
327
328 debugs(25, 5, "mimeInit: added '" << buf << "'");
329 }
330
331 fclose(fp);
332
333 for (m = MimeTable; m != NULL; m = m->next)
334 m->theIcon.load();
335 debugs(25, DBG_IMPORTANT, "Finished loading MIME types and icons.");
336 }
337
338 void
339 mimeFreeMemory(void)
340 {
341 MimeEntry *m;
342
343 while ((m = MimeTable)) {
344 MimeTable = m->next;
345 delete m;
346 }
347
348 MimeTableTail = &MimeTable;
349 }
350
351 void
352 MimeIcon::load()
353 {
354 const char *type = mimeGetContentType(icon_);
355
356 if (type == NULL)
357 fatal("Unknown icon format while reading mime.conf\n");
358
359 StoreEntry::getPublic(this, url_, Http::METHOD_GET);
360 }
361
362 void
363 MimeIcon::created(StoreEntry *newEntry)
364 {
365 /* if the icon is already in the store, do nothing */
366 if (!newEntry->isNull())
367 return;
368 // XXX: if a 204 is cached due to earlier load 'failure' we should try to reload.
369
370 // default is a 200 object with image data.
371 // set to the backup value of 204 on image loading errors
372 Http::StatusCode status = Http::scOkay;
373
374 static char path[MAXPATHLEN];
375 *path = 0;
376 if (snprintf(path, sizeof(path)-1, "%s/%s", Config.icons.directory, icon_) < 0) {
377 debugs(25, DBG_CRITICAL, "ERROR: icon file '" << Config.icons.directory << "/" << icon_ << "' path is longer than " << MAXPATHLEN << " bytes");
378 status = Http::scNoContent;
379 }
380
381 int fd = -1;
382 errno = 0;
383 if (status == Http::scOkay && (fd = file_open(path, O_RDONLY | O_BINARY)) < 0) {
384 int xerrno = errno;
385 debugs(25, DBG_CRITICAL, "ERROR: opening icon file " << path << ": " << xstrerr(xerrno));
386 status = Http::scNoContent;
387 }
388
389 struct stat sb;
390 errno = 0;
391 if (status == Http::scOkay && fstat(fd, &sb) < 0) {
392 int xerrno = errno;
393 debugs(25, DBG_CRITICAL, "ERROR: opening icon file " << path << " FD " << fd << ", fstat error " << xstrerr(xerrno));
394 file_close(fd);
395 status = Http::scNoContent;
396 }
397
398 // fill newEntry with a canned 2xx response object
399 RequestFlags flags;
400 flags.cachable = true;
401 StoreEntry *e = storeCreateEntry(url_,url_,flags,Http::METHOD_GET);
402 assert(e != NULL);
403 EBIT_SET(e->flags, ENTRY_SPECIAL);
404 e->setPublicKey();
405 e->buffer();
406 HttpRequest *r = HttpRequest::CreateFromUrl(url_);
407
408 if (NULL == r)
409 fatalf("mimeLoadIcon: cannot parse internal URL: %s", url_);
410
411 e->mem_obj->request = r;
412 HTTPMSGLOCK(e->mem_obj->request);
413
414 HttpReply *reply = new HttpReply;
415
416 if (status == Http::scNoContent)
417 reply->setHeaders(status, NULL, NULL, 0, -1, -1);
418 else
419 reply->setHeaders(status, NULL, mimeGetContentType(icon_), sb.st_size, sb.st_mtime, -1);
420 reply->cache_control = new HttpHdrCc();
421 reply->cache_control->maxAge(86400);
422 reply->header.putCc(reply->cache_control);
423 e->replaceHttpReply(reply);
424
425 if (status == Http::scOkay) {
426 /* read the file into the buffer and append it to store */
427 int n;
428 char *buf = (char *)memAllocate(MEM_4K_BUF);
429 while ((n = FD_READ_METHOD(fd, buf, sizeof(*buf))) > 0)
430 e->append(buf, n);
431
432 file_close(fd);
433 memFree(buf, MEM_4K_BUF);
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(pattern);
446 xfree(content_type);
447 xfree(content_encoding);
448 regfree(&compiled_pattern);
449 }
450
451 MimeEntry::MimeEntry(const char *aPattern, const regex_t &compiledPattern,
452 const char *aContentType, const char *aContentEncoding,
453 const char *aTransferMode, bool optionViewEnable,
454 bool optionDownloadEnable, const char *anIconName) :
455 pattern(xstrdup(aPattern)),
456 compiled_pattern(compiledPattern),
457 content_type(xstrdup(aContentType)),
458 content_encoding(xstrdup(aContentEncoding)),
459 view_option(optionViewEnable),
460 download_option(optionDownloadEnable),
461 theIcon(anIconName), next(NULL)
462 {
463 if (!strcasecmp(aTransferMode, "ascii"))
464 transfer_mode = 'A';
465 else if (!strcasecmp(aTransferMode, "text"))
466 transfer_mode = 'A';
467 else
468 transfer_mode = 'I';
469 }
470