Statistics
| Revision:

root / trunk / src / mod_compress.c

History | View | Annotate | Download (21.3 KB)

1
#include <sys/types.h>
2
#include <sys/stat.h>
3

    
4
#include <fcntl.h>
5
#include <ctype.h>
6
#include <stdlib.h>
7
#include <string.h>
8
#include <errno.h>
9
#include <time.h>
10

    
11
#include "base.h"
12
#include "log.h"
13
#include "buffer.h"
14
#include "response.h"
15
#include "stat_cache.h"
16

    
17
#include "plugin.h"
18

    
19
#include "crc32.h"
20
#include "etag.h"
21

    
22
#if defined HAVE_ZLIB_H && defined HAVE_LIBZ
23
# define USE_ZLIB
24
# include <zlib.h>
25
#endif
26

    
27
#if defined HAVE_BZLIB_H && defined HAVE_LIBBZ2
28
# define USE_BZ2LIB
29
/* we don't need stdio interface */
30
# define BZ_NO_STDIO
31
# include <bzlib.h>
32
#endif
33

    
34
#include "sys-mmap.h"
35
#include "sys-files.h"
36

    
37
/* request: accept-encoding */
38
#define HTTP_ACCEPT_ENCODING_IDENTITY BV(0)
39
#define HTTP_ACCEPT_ENCODING_GZIP     BV(1)
40
#define HTTP_ACCEPT_ENCODING_DEFLATE  BV(2)
41
#define HTTP_ACCEPT_ENCODING_COMPRESS BV(3)
42
#define HTTP_ACCEPT_ENCODING_BZIP2    BV(4)
43

    
44
#ifdef __WIN32
45
#define mkdir(x,y) mkdir(x)
46
#endif
47

    
48
typedef struct {
49
        buffer *compress_cache_dir;
50
        array  *compress;
51
        off_t   compress_max_filesize; /** max filesize in kb */
52
        int     allowed_encodings;
53
} plugin_config;
54

    
55
typedef struct {
56
        PLUGIN_DATA;
57
        buffer *ofn;
58
        buffer *b;
59

    
60
        plugin_config **config_storage;
61
        plugin_config conf;
62
} plugin_data;
63

    
64
INIT_FUNC(mod_compress_init) {
65
        plugin_data *p;
66

    
67
        UNUSED(srv);
68

    
69
        p = calloc(1, sizeof(*p));
70

    
71
        p->ofn = buffer_init();
72
        p->b = buffer_init();
73

    
74
        return p;
75
}
76

    
77
FREE_FUNC(mod_compress_free) {
78
        plugin_data *p = p_d;
79

    
80
        UNUSED(srv);
81

    
82
        if (!p) return HANDLER_GO_ON;
83

    
84
        buffer_free(p->ofn);
85
        buffer_free(p->b);
86

    
87
        if (p->config_storage) {
88
                size_t i;
89
                for (i = 0; i < srv->config_context->used; i++) {
90
                        plugin_config *s = p->config_storage[i];
91

    
92
                        if (!s) continue;
93

    
94
                        array_free(s->compress);
95
                        buffer_free(s->compress_cache_dir);
96

    
97
                        free(s);
98
                }
99
                free(p->config_storage);
100
        }
101

    
102

    
103
        free(p);
104

    
105
        return HANDLER_GO_ON;
106
}
107

    
108
/* 0 on success, -1 for error */
109
static int mkdir_recursive(char *dir) {
110
        char *p = dir;
111

    
112
        if (!dir || !dir[0])
113
                return 0;
114

    
115
        while ((p = strchr(p + 1, '/')) != NULL) {
116

    
117
                *p = '\0';
118
                if ((mkdir(dir, 0700) != 0) && (errno != EEXIST)) {
119
                        *p = '/';
120
                        return -1;
121
                }
122

    
123
                *p++ = '/';
124
                if (!*p) return 0; /* Ignore trailing slash */
125
        }
126

    
127
        return (mkdir(dir, 0700) != 0) && (errno != EEXIST) ? -1 : 0;
128
}
129

    
130
/* 0 on success, -1 for error */
131
static int mkdir_for_file(char *filename) {
132
        char *p = filename;
133

    
134
        if (!filename || !filename[0])
135
                return -1;
136

    
137
        while ((p = strchr(p + 1, '/')) != NULL) {
138

    
139
                *p = '\0';
140
                if ((mkdir(filename, 0700) != 0) && (errno != EEXIST)) {
141
                        ERROR("creating cache-directory \"%s\" failed: %s", filename, strerror(errno));
142
                        *p = '/';
143
                        return -1;
144
                }
145

    
146
                *p++ = '/';
147
                if (!*p) {
148
                        ERROR("unexpected trailing slash for filename \"%s\"", filename);
149
                        return -1;
150
                }
151
        }
152

    
153
        return 0;
154
}
155

    
156
SETDEFAULTS_FUNC(mod_compress_setdefaults) {
157
        plugin_data *p = p_d;
158
        size_t i = 0;
159

    
160
        config_values_t cv[] = {
161
                { "compress.cache-dir",             NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION },
162
                { "compress.filetype",              NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },
163
                { "compress.max-filesize",          NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION },
164
                { "compress.allowed-encodings",     NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION },
165
                { NULL,                             NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
166
        };
167

    
168
        p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
169

    
170
        for (i = 0; i < srv->config_context->used; i++) {
171
                plugin_config *s;
172
                array  *encodings_arr = array_init();
173

    
174
                s = calloc(1, sizeof(plugin_config));
175
                s->compress_cache_dir = buffer_init();
176
                s->compress = array_init();
177
                s->compress_max_filesize = 0;
178
                s->allowed_encodings = 0;
179

    
180
                cv[0].destination = s->compress_cache_dir;
181
                cv[1].destination = s->compress;
182
                cv[2].destination = &(s->compress_max_filesize);
183
                cv[3].destination = encodings_arr; /* temp array for allowed encodings list */
184

    
185
                p->config_storage[i] = s;
186

    
187
                if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
188
                        return HANDLER_ERROR;
189
                }
190

    
191
                if (encodings_arr->used) {
192
                        size_t j = 0;
193
                        for (j = 0; j < encodings_arr->used; j++) {
194
                                data_string *ds = (data_string *)encodings_arr->data[j];
195
#ifdef USE_ZLIB
196
                                if (NULL != strstr(ds->value->ptr, "gzip"))
197
                                        s->allowed_encodings |= HTTP_ACCEPT_ENCODING_GZIP;
198
                                if (NULL != strstr(ds->value->ptr, "deflate"))
199
                                        s->allowed_encodings |= HTTP_ACCEPT_ENCODING_DEFLATE;
200
                                /*
201
                                if (NULL != strstr(ds->value->ptr, "compress"))
202
                                        s->allowed_encodings |= HTTP_ACCEPT_ENCODING_COMPRESS;
203
                                */
204
#endif
205
#ifdef USE_BZ2LIB
206
                                if (NULL != strstr(ds->value->ptr, "bzip2"))
207
                                        s->allowed_encodings |= HTTP_ACCEPT_ENCODING_BZIP2;
208
#endif
209
                        }
210
                } else {
211
                        /* default encodings */
212
                        s->allowed_encodings = 0
213
#ifdef USE_ZLIB
214
                                | HTTP_ACCEPT_ENCODING_GZIP | HTTP_ACCEPT_ENCODING_DEFLATE
215
#endif
216
#ifdef USE_BZ2LIB
217
                                | HTTP_ACCEPT_ENCODING_BZIP2
218
#endif
219
                                ;
220
                }
221

    
222
                array_free(encodings_arr);
223

    
224
                if (!buffer_is_empty(s->compress_cache_dir)) {
225
                        struct stat st;
226
                        if (0 != stat(s->compress_cache_dir->ptr, &st)) {
227

    
228
                                ERROR("can't stat compress.cache-dir (%s), attempting to create '%s'", strerror(errno),SAFE_BUF_STR(s->compress_cache_dir));
229
                                mkdir_recursive(s->compress_cache_dir->ptr);
230

    
231
                                if (0 != stat(s->compress_cache_dir->ptr, &st)) {
232
                                        ERROR("can't stat compress.cache-dir (%s), failed to create '%s'", strerror(errno),SAFE_BUF_STR(s->compress_cache_dir));
233
                                        return HANDLER_ERROR;
234
                                }
235
                        }
236
                }
237
        }
238

    
239
        return HANDLER_GO_ON;
240

    
241
}
242

    
243
#ifdef USE_ZLIB
244
static int deflate_file_to_buffer_gzip(server *srv, connection *con, plugin_data *p, char *start, off_t st_size, time_t mtime) {
245
        unsigned char *c;
246
        unsigned long crc;
247
        z_stream z;
248

    
249
        UNUSED(srv);
250
        UNUSED(con);
251

    
252
        z.zalloc = Z_NULL;
253
        z.zfree = Z_NULL;
254
        z.opaque = Z_NULL;
255

    
256
        if (Z_OK != deflateInit2(&z,
257
                                 Z_DEFAULT_COMPRESSION,
258
                                 Z_DEFLATED,
259
                                 -MAX_WBITS,  /* supress zlib-header */
260
                                 8,
261
                                 Z_DEFAULT_STRATEGY)) {
262
                return -1;
263
        }
264

    
265
        z.next_in = (unsigned char *)start;
266
        z.avail_in = st_size;
267
        z.total_in = 0;
268

    
269

    
270
        buffer_prepare_copy(p->b, (z.avail_in * 1.1) + 12 + 18);
271

    
272
        /* write gzip header */
273

    
274
        c = (unsigned char *)p->b->ptr;
275
        c[0] = 0x1f;
276
        c[1] = 0x8b;
277
        c[2] = Z_DEFLATED;
278
        c[3] = 0; /* options */
279
        c[4] = (mtime >>  0) & 0xff;
280
        c[5] = (mtime >>  8) & 0xff;
281
        c[6] = (mtime >> 16) & 0xff;
282
        c[7] = (mtime >> 24) & 0xff;
283
        c[8] = 0x00; /* extra flags */
284
        c[9] = 0x03; /* UNIX */
285

    
286
        p->b->used = 10;
287
        z.next_out = (unsigned char *)p->b->ptr + p->b->used;
288
        z.avail_out = p->b->size - p->b->used - 8;
289
        z.total_out = 0;
290

    
291
        if (Z_STREAM_END != deflate(&z, Z_FINISH)) {
292
                deflateEnd(&z);
293
                return -1;
294
        }
295

    
296
        /* trailer */
297
        p->b->used += z.total_out;
298

    
299
        crc = generate_crc32c(start, st_size);
300

    
301
        c = (unsigned char *)p->b->ptr + p->b->used;
302

    
303
        c[0] = (crc >>  0) & 0xff;
304
        c[1] = (crc >>  8) & 0xff;
305
        c[2] = (crc >> 16) & 0xff;
306
        c[3] = (crc >> 24) & 0xff;
307
        c[4] = (z.total_in >>  0) & 0xff;
308
        c[5] = (z.total_in >>  8) & 0xff;
309
        c[6] = (z.total_in >> 16) & 0xff;
310
        c[7] = (z.total_in >> 24) & 0xff;
311
        p->b->used += 8;
312

    
313
        if (Z_OK != deflateEnd(&z)) {
314
                return -1;
315
        }
316

    
317
        return 0;
318
}
319

    
320
static int deflate_file_to_buffer_deflate(server *srv, connection *con, plugin_data *p, unsigned char *start, off_t st_size) {
321
        z_stream z;
322

    
323
        UNUSED(srv);
324
        UNUSED(con);
325

    
326
        z.zalloc = Z_NULL;
327
        z.zfree = Z_NULL;
328
        z.opaque = Z_NULL;
329

    
330
        if (Z_OK != deflateInit2(&z,
331
                                 Z_DEFAULT_COMPRESSION,
332
                                 Z_DEFLATED,
333
                                 -MAX_WBITS,  /* supress zlib-header */
334
                                 8,
335
                                 Z_DEFAULT_STRATEGY)) {
336
                return -1;
337
        }
338

    
339
        z.next_in = start;
340
        z.avail_in = st_size;
341
        z.total_in = 0;
342

    
343
        buffer_prepare_copy(p->b, (z.avail_in * 1.1) + 12);
344

    
345
        z.next_out = (unsigned char *)p->b->ptr;
346
        z.avail_out = p->b->size;
347
        z.total_out = 0;
348

    
349
        if (Z_STREAM_END != deflate(&z, Z_FINISH)) {
350
                deflateEnd(&z);
351
                return -1;
352
        }
353

    
354
        /* trailer */
355
        p->b->used += z.total_out;
356

    
357
        if (Z_OK != deflateEnd(&z)) {
358
                return -1;
359
        }
360

    
361
        return 0;
362
}
363

    
364
#endif
365

    
366
#ifdef USE_BZ2LIB
367
static int deflate_file_to_buffer_bzip2(server *srv, connection *con, plugin_data *p, unsigned char *start, off_t st_size) {
368
        bz_stream bz;
369

    
370
        UNUSED(srv);
371
        UNUSED(con);
372

    
373
        bz.bzalloc = NULL;
374
        bz.bzfree = NULL;
375
        bz.opaque = NULL;
376

    
377
        if (BZ_OK != BZ2_bzCompressInit(&bz,
378
                                        9, /* blocksize = 900k */
379
                                        0, /* no output */
380
                                        0)) { /* workFactor: default */
381
                return -1;
382
        }
383

    
384
        bz.next_in = (char *)start;
385
        bz.avail_in = st_size;
386
        bz.total_in_lo32 = 0;
387
        bz.total_in_hi32 = 0;
388

    
389
        buffer_prepare_copy(p->b, (bz.avail_in * 1.1) + 12);
390

    
391
        bz.next_out = p->b->ptr;
392
        bz.avail_out = p->b->size;
393
        bz.total_out_lo32 = 0;
394
        bz.total_out_hi32 = 0;
395

    
396
        if (BZ_STREAM_END != BZ2_bzCompress(&bz, BZ_FINISH)) {
397
                BZ2_bzCompressEnd(&bz);
398
                return -1;
399
        }
400

    
401
        /* file is too large for now */
402
        if (bz.total_out_hi32) return -1;
403

    
404
        /* trailer */
405
        p->b->used = bz.total_out_lo32;
406

    
407
        if (BZ_OK != BZ2_bzCompressEnd(&bz)) {
408
                return -1;
409
        }
410

    
411
        return 0;
412
}
413
#endif
414

    
415
static int deflate_file_to_file(server *srv, connection *con, plugin_data *p, buffer *fn, stat_cache_entry *sce, int type) {
416
        int ifd, ofd;
417
        int ret = -1;
418
        void *start;
419
        const char *filename = fn->ptr;
420
        ssize_t r;
421
        stat_cache_entry *compressed_sce = NULL;
422

    
423
        if (buffer_is_empty(p->conf.compress_cache_dir)) return -1;
424

    
425
        /* overflow */
426
        if ((off_t)(sce->st.st_size * 1.1) < sce->st.st_size) return -1;
427

    
428
        /* don't mmap files > 128Mb
429
         *
430
         * we could use a sliding window, but currently there is no need for it
431
         */
432

    
433
        if (sce->st.st_size > 128 * 1024 * 1024) return -1;
434

    
435
        buffer_reset(p->ofn);
436
        buffer_copy_string_buffer(p->ofn, p->conf.compress_cache_dir);
437
        PATHNAME_APPEND_SLASH(p->ofn);
438

    
439
        if (0 == strncmp(con->physical.path->ptr, con->physical.doc_root->ptr, con->physical.doc_root->used-1)) {
440
                buffer_append_string(p->ofn, con->physical.path->ptr + con->physical.doc_root->used - 1);
441
                buffer_copy_string_buffer(p->b, p->ofn);
442
        } else {
443
                buffer_append_string_buffer(p->ofn, con->uri.path);
444
        }
445

    
446
        switch(type) {
447
        case HTTP_ACCEPT_ENCODING_GZIP:
448
                buffer_append_string_len(p->ofn, CONST_STR_LEN("-gzip-"));
449
                break;
450
        case HTTP_ACCEPT_ENCODING_DEFLATE:
451
                buffer_append_string_len(p->ofn, CONST_STR_LEN("-deflate-"));
452
                break;
453
        case HTTP_ACCEPT_ENCODING_BZIP2:
454
                buffer_append_string_len(p->ofn, CONST_STR_LEN("-bzip2-"));
455
                break;
456
        default:
457
                ERROR("unknown compression type %d", type);
458
                return -1;
459
        }
460

    
461
        buffer_append_string_buffer(p->ofn, sce->etag);
462

    
463

    
464
        if (HANDLER_ERROR != stat_cache_get_entry(srv, con, p->ofn, &compressed_sce)) {
465
                /* file exists */
466
                if (con->conf.log_request_handling) TRACE("file exists in the cache (%s), sending it", SAFE_BUF_STR(p->ofn));
467

    
468
                chunkqueue_reset(con->send);
469
                chunkqueue_append_file(con->send, p->ofn, 0, compressed_sce->st.st_size);
470
                con->send->is_closed = 1;
471

    
472
                return 0;
473
        }
474

    
475
        if (-1 == (ofd = open(p->ofn->ptr, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0600))) {
476
                if (-1 == mkdir_for_file(p->ofn->ptr)) {
477
                        return -1; // error message in mkdir_for_file
478
                } else if (-1 == (ofd = open(p->ofn->ptr, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0600))) {
479
                        ERROR("creating cachefile '%s' failed: %s", SAFE_BUF_STR(p->ofn), strerror(errno));
480
                        return -1;
481
                }
482
        }
483

    
484
        if (-1 == (ifd = open(filename, O_RDONLY | O_BINARY))) {
485
                ERROR("opening plain-file '%s' failed: %s", SAFE_BUF_STR(fn), strerror(errno));
486
                close(ofd);
487
                return -1;
488
        }
489

    
490

    
491
        if (MAP_FAILED == (start = mmap(NULL, sce->st.st_size, PROT_READ, MAP_SHARED, ifd, 0))) {
492
                ERROR("mmaping '%s' failed: %s", SAFE_BUF_STR(fn), strerror(errno));
493
                close(ofd);
494
                close(ifd);
495
                return -1;
496
        }
497

    
498
        switch(type) {
499
#ifdef USE_ZLIB
500
        case HTTP_ACCEPT_ENCODING_GZIP:
501
                ret = deflate_file_to_buffer_gzip(srv, con, p, start, sce->st.st_size, sce->st.st_mtime);
502
                break;
503
        case HTTP_ACCEPT_ENCODING_DEFLATE:
504
                ret = deflate_file_to_buffer_deflate(srv, con, p, start, sce->st.st_size);
505
                break;
506
#endif
507
#ifdef USE_BZ2LIB
508
        case HTTP_ACCEPT_ENCODING_BZIP2:
509
                ret = deflate_file_to_buffer_bzip2(srv, con, p, start, sce->st.st_size);
510
                break;
511
#endif
512
        default:
513
                ret = -1;
514
                break;
515
        }
516

    
517
        if (-1 == (r = write(ofd, p->b->ptr, p->b->used))) {
518
                munmap(start, sce->st.st_size);
519
                close(ofd);
520
                close(ifd);
521
                return -1;
522
        }
523

    
524
        if ((size_t)r != p->b->used) {
525

    
526
        }
527

    
528
        munmap(start, sce->st.st_size);
529
        close(ofd);
530
        close(ifd);
531

    
532
        if (ret != 0) return -1;
533

    
534
        chunkqueue_reset(con->send);
535
        chunkqueue_append_file(con->send, p->ofn, 0, r);
536
        con->send->is_closed = 1;
537

    
538
        return 0;
539
}
540

    
541
static int deflate_file_to_buffer(server *srv, connection *con, plugin_data *p, buffer *fn, stat_cache_entry *sce, int type) {
542
        int ifd;
543
        int ret = -1;
544
        void *start;
545
        buffer *b;
546

    
547
        /* overflow */
548
        if ((off_t)(sce->st.st_size * 1.1) < sce->st.st_size) return -1;
549

    
550
        /* don't mmap files > 128M
551
         *
552
         * we could use a sliding window, but currently there is no need for it
553
         */
554

    
555
        if (sce->st.st_size > 128 * 1024 * 1024) return -1;
556

    
557
        if (-1 == (ifd = open(fn->ptr, O_RDONLY | O_BINARY))) {
558
                ERROR("opening plain-file '%s' failed: %s", SAFE_BUF_STR(fn), strerror(errno));
559

    
560
                return -1;
561
        }
562

    
563
        start = mmap(NULL, sce->st.st_size, PROT_READ, MAP_SHARED, ifd, 0);
564

    
565
        close(ifd);
566

    
567
        if (MAP_FAILED == start) {
568
                ERROR("mmaping '%s' failed: %s", SAFE_BUF_STR(fn), strerror(errno));
569

    
570
                return -1;
571
        }
572

    
573
        switch(type) {
574
#ifdef USE_ZLIB
575
        case HTTP_ACCEPT_ENCODING_GZIP:
576
                ret = deflate_file_to_buffer_gzip(srv, con, p, start, sce->st.st_size, sce->st.st_mtime);
577
                break;
578
        case HTTP_ACCEPT_ENCODING_DEFLATE:
579
                ret = deflate_file_to_buffer_deflate(srv, con, p, start, sce->st.st_size);
580
                break;
581
#endif
582
#ifdef USE_BZ2LIB
583
        case HTTP_ACCEPT_ENCODING_BZIP2:
584
                ret = deflate_file_to_buffer_bzip2(srv, con, p, start, sce->st.st_size);
585
                break;
586
#endif
587
        default:
588
                ret = -1;
589
                break;
590
        }
591

    
592
        munmap(start, sce->st.st_size);
593

    
594
        if (ret != 0) return -1;
595

    
596
        chunkqueue_reset(con->send);
597
        b = chunkqueue_get_append_buffer(con->send);
598
        buffer_copy_memory(b, p->b->ptr, p->b->used + 1);
599
        con->send->bytes_in += b->used-1;
600

    
601
        buffer_reset(con->physical.path);
602

    
603
        con->send->is_closed = 1;
604
        con->file_started  = 1;
605

    
606
        return 0;
607
}
608

    
609
static int mod_compress_patch_connection(server *srv, connection *con, plugin_data *p) {
610
        size_t i, j;
611
        plugin_config *s = p->config_storage[0];
612

    
613
        PATCH_OPTION(compress_cache_dir);
614
        PATCH_OPTION(compress);
615
        PATCH_OPTION(compress_max_filesize);
616
        PATCH_OPTION(allowed_encodings);
617

    
618
        /* skip the first, the global context */
619
        for (i = 1; i < srv->config_context->used; i++) {
620
                data_config *dc = (data_config *)srv->config_context->data[i];
621
                s = p->config_storage[i];
622

    
623
                /* condition didn't match */
624
                if (!config_check_cond(srv, con, dc)) continue;
625

    
626
                /* merge config */
627
                for (j = 0; j < dc->value->used; j++) {
628
                        data_unset *du = dc->value->data[j];
629

    
630
                        if (buffer_is_equal_string(du->key, CONST_STR_LEN("compress.cache-dir"))) {
631
                                PATCH_OPTION(compress_cache_dir);
632
                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("compress.filetype"))) {
633
                                PATCH_OPTION(compress);
634
                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("compress.max-filesize"))) {
635
                                PATCH_OPTION(compress_max_filesize);
636
                        } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("compress.allowed-encodings"))) {
637
                                PATCH_OPTION(allowed_encodings);
638
                        }
639
                }
640
        }
641

    
642
        return 0;
643
}
644

    
645
PHYSICALPATH_FUNC(mod_compress_physical) {
646
        plugin_data *p = p_d;
647
        size_t m;
648
        off_t max_fsize;
649
        stat_cache_entry *sce = NULL;
650
        data_string *ds;
651
        int accept_encoding = 0;
652
        char *value;
653
        int matched_encodings = 0;
654
        const char *dflt_gzip = "gzip";
655
        const char *dflt_deflate = "deflate";
656
        const char *dflt_bzip2 = "bzip2";
657

    
658
        const char *compression_name = NULL;
659
        int compression_type = 0;
660
        buffer *mtime, *content_type;
661

    
662
        if (con->mode != DIRECT) return HANDLER_GO_ON;
663

    
664
        if (con->conf.log_request_handling) TRACE("-- %s", "handling in mod_compress");
665

    
666
        /* only GET and POST can get compressed */
667
        if (con->request.http_method != HTTP_METHOD_GET &&
668
            con->request.http_method != HTTP_METHOD_POST) {
669
                return HANDLER_GO_ON;
670
        }
671

    
672
        if (buffer_is_empty(con->physical.path)) {
673
                return HANDLER_GO_ON;
674
        }
675

    
676
        mod_compress_patch_connection(srv, con, p);
677

    
678
        max_fsize = p->conf.compress_max_filesize;
679

    
680
        if (HANDLER_GO_ON != stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
681
                if (con->conf.log_request_handling) TRACE("file '%s' not found", SAFE_BUF_STR(con->physical.path));
682
                return HANDLER_GO_ON;
683
        }
684

    
685
        /* don't compress files that are too large as we need to much time to handle them */
686
        if (max_fsize && (sce->st.st_size >> 10) > max_fsize) {
687
                if (con->conf.log_request_handling) TRACE("file '%s' is too large: %jd", 
688
                                SAFE_BUF_STR(con->physical.path), 
689
                                (intmax_t) sce->st.st_size);
690

    
691
                return HANDLER_GO_ON;
692
        }
693

    
694
        /* compressing the file might lead to larger files instead */
695
        if (sce->st.st_size < 128) {
696
                if (con->conf.log_request_handling) TRACE("file '%s' is too small: %jd", 
697
                                SAFE_BUF_STR(con->physical.path), 
698
                                (intmax_t) sce->st.st_size);
699

    
700
                return HANDLER_GO_ON;
701
        }
702

    
703
        /* check if mimetype is in compress-config */
704
        content_type = 0;
705
        if (sce->content_type->ptr) {
706
                char *c;
707
                if ( (c = strchr(BUF_STR(sce->content_type), ';')) != 0) {
708
                        content_type = buffer_init();
709
                        buffer_copy_string_len(content_type, BUF_STR(sce->content_type), c - BUF_STR(sce->content_type)); 
710
                }
711
        }
712
        for (m = 0; m < p->conf.compress->used; m++) {
713
                data_string *compress_ds = (data_string *)p->conf.compress->data[m];
714

    
715
                if (!compress_ds) {
716
                        ERROR("evil: %s .. %s", SAFE_BUF_STR(con->physical.path), SAFE_BUF_STR(con->uri.path));
717

    
718
                        return HANDLER_GO_ON;
719
                }
720

    
721
                if (buffer_is_equal(compress_ds->value, sce->content_type)
722
                        || (content_type && buffer_is_equal(compress_ds->value, content_type))) {
723
                        break;
724
                }
725
        }
726
        buffer_free(content_type);
727

    
728
        if (m == p->conf.compress->used) {
729
                return HANDLER_GO_ON;
730
        }
731
        /* mimetype found */
732

    
733

    
734
        if (con->send->is_closed == 0) {
735
                if (con->conf.log_request_handling) TRACE("we can't compress streams: is_closed = %d", con->send->is_closed);
736
                return HANDLER_GO_ON;
737
        }
738

    
739
        if (con->send->first == NULL) {
740
                if (con->conf.log_request_handling) TRACE("we can't compress streams: ->first = %p", NULL);
741
                return HANDLER_GO_ON;
742
        }
743

    
744
        if (con->send->first->next != NULL) {
745
                if (con->conf.log_request_handling) TRACE("we can't compress streams: ->first->next = %p", NULL);
746
                return HANDLER_GO_ON;
747
        }
748

    
749
        if (con->send->first->type != FILE_CHUNK) {
750
                if (con->conf.log_request_handling) TRACE("we can compress file-chunks: ->type = %d", con->send->first->type);
751
                return HANDLER_GO_ON;
752
        }
753

    
754
        /* the response might change according to Accept-Encoding */
755
        response_header_insert(srv, con, CONST_STR_LEN("Vary"), CONST_STR_LEN("Accept-Encoding"));
756

    
757
        if (NULL == (ds = (data_string *)array_get_element(con->request.headers, CONST_STR_LEN("Accept-Encoding")))) {
758
                if (con->conf.log_request_handling) TRACE("couldn't find a Accept-Encoding header: %s", "");
759
                return HANDLER_GO_ON;
760
        }
761

    
762
        value = ds->value->ptr;
763

    
764
        /* get client side support encodings */
765
#ifdef USE_ZLIB
766
        if (NULL != strstr(value, "gzip")) accept_encoding |= HTTP_ACCEPT_ENCODING_GZIP;
767
        if (NULL != strstr(value, "deflate")) accept_encoding |= HTTP_ACCEPT_ENCODING_DEFLATE;
768
        if (NULL != strstr(value, "compress")) accept_encoding |= HTTP_ACCEPT_ENCODING_COMPRESS;
769
#endif
770
#ifdef USE_BZ2LIB
771
        if (NULL != strstr(value, "bzip2")) accept_encoding |= HTTP_ACCEPT_ENCODING_BZIP2;
772
#endif
773
        if (NULL != strstr(value, "identity")) accept_encoding |= HTTP_ACCEPT_ENCODING_IDENTITY;
774

    
775
        /* find matching entries */
776
        matched_encodings = accept_encoding & p->conf.allowed_encodings;
777
        if (0 == matched_encodings) {
778
                if (con->conf.log_request_handling) TRACE("we don't support the requested encoding: %s", value);
779
                return HANDLER_GO_ON;
780
        }
781

    
782
        mtime = strftime_cache_get(srv, sce->st.st_mtime);
783
        etag_mutate(con->physical.etag, sce->etag);
784

    
785
        response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
786
        response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
787

    
788
        /* perhaps we don't even have to compress the file as the browser still has the
789
         * current version */
790
        if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) {
791
                if (con->conf.log_request_handling) TRACE("%s is still the same, caching", SAFE_BUF_STR(con->physical.path));
792
                return HANDLER_FINISHED;
793
        }
794

    
795
        /* select best matching encoding */
796
        if (matched_encodings & HTTP_ACCEPT_ENCODING_BZIP2) {
797
                compression_type = HTTP_ACCEPT_ENCODING_BZIP2;
798
                compression_name = dflt_bzip2;
799
        } else if (matched_encodings & HTTP_ACCEPT_ENCODING_GZIP) {
800
                compression_type = HTTP_ACCEPT_ENCODING_GZIP;
801
                compression_name = dflt_gzip;
802
        } else if (matched_encodings & HTTP_ACCEPT_ENCODING_DEFLATE) {
803
                compression_type = HTTP_ACCEPT_ENCODING_DEFLATE;
804
                compression_name = dflt_deflate;
805
        }
806

    
807
        if (con->conf.log_request_handling) TRACE("we are fine, let's compress: %s", "");
808

    
809
        /* deflate it to file (cached) or to memory */
810
        if (0 == deflate_file_to_file(srv, con, p,
811
                        con->physical.path, sce, compression_type) ||
812
            0 == deflate_file_to_buffer(srv, con, p,
813
                        con->physical.path, sce, compression_type)) {
814

    
815
                response_header_overwrite(srv, con,
816
                                CONST_STR_LEN("Content-Encoding"),
817
                                compression_name, strlen(compression_name));
818

    
819
                response_header_overwrite(srv, con,
820
                                CONST_STR_LEN("Content-Type"),
821
                                CONST_BUF_LEN(sce->content_type));
822

    
823
                con->response.content_length = chunkqueue_length(con->send);
824

    
825
                if (con->conf.log_request_handling) TRACE("looks like %s could be compressed", SAFE_BUF_STR(con->physical.path));
826
                return HANDLER_FINISHED;
827
        }
828

    
829
        return HANDLER_GO_ON;
830
}
831

    
832
LI_EXPORT int mod_compress_plugin_init(plugin *p);
833
LI_EXPORT int mod_compress_plugin_init(plugin *p) {
834
        p->version     = LIGHTTPD_VERSION_ID;
835
        p->name        = buffer_init_string("compress");
836

    
837
        p->init        = mod_compress_init;
838
        p->set_defaults = mod_compress_setdefaults;
839

    
840
        /* we have to hook into the response-header settings */
841
        p->handle_response_header  = mod_compress_physical;
842

    
843
        p->cleanup     = mod_compress_free;
844

    
845
        p->data        = NULL;
846

    
847
        return 0;
848
}