1
2/*
3 * Copyright (C) Igor Sysoev
4 * Copyright (C) Nginx, Inc.
5 */
6
7
8#include <ngx_config.h>
9#include <ngx_core.h>
10#include <ngx_http.h>
11
12#include <gd.h>
13
14
15#define NGX_HTTP_IMAGE_OFF       0
16#define NGX_HTTP_IMAGE_TEST      1
17#define NGX_HTTP_IMAGE_SIZE      2
18#define NGX_HTTP_IMAGE_RESIZE    3
19#define NGX_HTTP_IMAGE_CROP      4
20#define NGX_HTTP_IMAGE_ROTATE    5
21
22
23#define NGX_HTTP_IMAGE_START     0
24#define NGX_HTTP_IMAGE_READ      1
25#define NGX_HTTP_IMAGE_PROCESS   2
26#define NGX_HTTP_IMAGE_PASS      3
27#define NGX_HTTP_IMAGE_DONE      4
28
29
30#define NGX_HTTP_IMAGE_NONE      0
31#define NGX_HTTP_IMAGE_JPEG      1
32#define NGX_HTTP_IMAGE_GIF       2
33#define NGX_HTTP_IMAGE_PNG       3
34#define NGX_HTTP_IMAGE_WEBP      4
35
36
37#define NGX_HTTP_IMAGE_BUFFERED  0x08
38
39
40typedef struct {
41    ngx_uint_t                   filter;
42    ngx_uint_t                   width;
43    ngx_uint_t                   height;
44    ngx_uint_t                   angle;
45    ngx_uint_t                   jpeg_quality;
46    ngx_uint_t                   webp_quality;
47    ngx_uint_t                   sharpen;
48
49    ngx_flag_t                   transparency;
50    ngx_flag_t                   interlace;
51
52    ngx_http_complex_value_t    *wcv;
53    ngx_http_complex_value_t    *hcv;
54    ngx_http_complex_value_t    *acv;
55    ngx_http_complex_value_t    *jqcv;
56    ngx_http_complex_value_t    *wqcv;
57    ngx_http_complex_value_t    *shcv;
58
59    size_t                       buffer_size;
60} ngx_http_image_filter_conf_t;
61
62
63typedef struct {
64    u_char                      *image;
65    u_char                      *last;
66
67    size_t                       length;
68
69    ngx_uint_t                   width;
70    ngx_uint_t                   height;
71    ngx_uint_t                   max_width;
72    ngx_uint_t                   max_height;
73    ngx_uint_t                   angle;
74
75    ngx_uint_t                   phase;
76    ngx_uint_t                   type;
77    ngx_uint_t                   force;
78} ngx_http_image_filter_ctx_t;
79
80
81static ngx_int_t ngx_http_image_send(ngx_http_request_t *r,
82    ngx_http_image_filter_ctx_t *ctx, ngx_chain_t *in);
83static ngx_uint_t ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in);
84static ngx_int_t ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in);
85static ngx_buf_t *ngx_http_image_process(ngx_http_request_t *r);
86static ngx_buf_t *ngx_http_image_json(ngx_http_request_t *r,
87    ngx_http_image_filter_ctx_t *ctx);
88static ngx_buf_t *ngx_http_image_asis(ngx_http_request_t *r,
89    ngx_http_image_filter_ctx_t *ctx);
90static void ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b);
91static ngx_int_t ngx_http_image_size(ngx_http_request_t *r,
92    ngx_http_image_filter_ctx_t *ctx);
93
94static ngx_buf_t *ngx_http_image_resize(ngx_http_request_t *r,
95    ngx_http_image_filter_ctx_t *ctx);
96static gdImagePtr ngx_http_image_source(ngx_http_request_t *r,
97    ngx_http_image_filter_ctx_t *ctx);
98static gdImagePtr ngx_http_image_new(ngx_http_request_t *r, int w, int h,
99    int colors);
100static u_char *ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type,
101    gdImagePtr img, int *size);
102static void ngx_http_image_cleanup(void *data);
103static ngx_uint_t ngx_http_image_filter_get_value(ngx_http_request_t *r,
104    ngx_http_complex_value_t *cv, ngx_uint_t v);
105static ngx_uint_t ngx_http_image_filter_value(ngx_str_t *value);
106
107
108static void *ngx_http_image_filter_create_conf(ngx_conf_t *cf);
109static char *ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent,
110    void *child);
111static char *ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd,
112    void *conf);
113static char *ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf,
114    ngx_command_t *cmd, void *conf);
115static char *ngx_http_image_filter_webp_quality(ngx_conf_t *cf,
116    ngx_command_t *cmd, void *conf);
117static char *ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd,
118    void *conf);
119static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf);
120
121
122static ngx_command_t  ngx_http_image_filter_commands[] = {
123
124    { ngx_string("image_filter"),
125      NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
126      ngx_http_image_filter,
127      NGX_HTTP_LOC_CONF_OFFSET,
128      0,
129      NULL },
130
131    { ngx_string("image_filter_jpeg_quality"),
132      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
133      ngx_http_image_filter_jpeg_quality,
134      NGX_HTTP_LOC_CONF_OFFSET,
135      0,
136      NULL },
137
138    { ngx_string("image_filter_webp_quality"),
139      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
140      ngx_http_image_filter_webp_quality,
141      NGX_HTTP_LOC_CONF_OFFSET,
142      0,
143      NULL },
144
145    { ngx_string("image_filter_sharpen"),
146      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
147      ngx_http_image_filter_sharpen,
148      NGX_HTTP_LOC_CONF_OFFSET,
149      0,
150      NULL },
151
152    { ngx_string("image_filter_transparency"),
153      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
154      ngx_conf_set_flag_slot,
155      NGX_HTTP_LOC_CONF_OFFSET,
156      offsetof(ngx_http_image_filter_conf_t, transparency),
157      NULL },
158
159    { ngx_string("image_filter_interlace"),
160      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
161      ngx_conf_set_flag_slot,
162      NGX_HTTP_LOC_CONF_OFFSET,
163      offsetof(ngx_http_image_filter_conf_t, interlace),
164      NULL },
165
166    { ngx_string("image_filter_buffer"),
167      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
168      ngx_conf_set_size_slot,
169      NGX_HTTP_LOC_CONF_OFFSET,
170      offsetof(ngx_http_image_filter_conf_t, buffer_size),
171      NULL },
172
173      ngx_null_command
174};
175
176
177static ngx_http_module_t  ngx_http_image_filter_module_ctx = {
178    NULL,                                  /* preconfiguration */
179    ngx_http_image_filter_init,            /* postconfiguration */
180
181    NULL,                                  /* create main configuration */
182    NULL,                                  /* init main configuration */
183
184    NULL,                                  /* create server configuration */
185    NULL,                                  /* merge server configuration */
186
187    ngx_http_image_filter_create_conf,     /* create location configuration */
188    ngx_http_image_filter_merge_conf       /* merge location configuration */
189};
190
191
192ngx_module_t  ngx_http_image_filter_module = {
193    NGX_MODULE_V1,
194    &ngx_http_image_filter_module_ctx,     /* module context */
195    ngx_http_image_filter_commands,        /* module directives */
196    NGX_HTTP_MODULE,                       /* module type */
197    NULL,                                  /* init master */
198    NULL,                                  /* init module */
199    NULL,                                  /* init process */
200    NULL,                                  /* init thread */
201    NULL,                                  /* exit thread */
202    NULL,                                  /* exit process */
203    NULL,                                  /* exit master */
204    NGX_MODULE_V1_PADDING
205};
206
207
208static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
209static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
210
211
212static ngx_str_t  ngx_http_image_types[] = {
213    ngx_string("image/jpeg"),
214    ngx_string("image/gif"),
215    ngx_string("image/png"),
216    ngx_string("image/webp")
217};
218
219
220static ngx_int_t
221ngx_http_image_header_filter(ngx_http_request_t *r)
222{
223    off_t                          len;
224    ngx_http_image_filter_ctx_t   *ctx;
225    ngx_http_image_filter_conf_t  *conf;
226
227    if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
228        return ngx_http_next_header_filter(r);
229    }
230
231    ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
232
233    if (ctx) {
234        ngx_http_set_ctx(r, NULL, ngx_http_image_filter_module);
235        return ngx_http_next_header_filter(r);
236    }
237
238    conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
239
240    if (conf->filter == NGX_HTTP_IMAGE_OFF) {
241        return ngx_http_next_header_filter(r);
242    }
243
244    if (r->headers_out.content_type.len
245            >= sizeof("multipart/x-mixed-replace") - 1
246        && ngx_strncasecmp(r->headers_out.content_type.data,
247                           (u_char *) "multipart/x-mixed-replace",
248                           sizeof("multipart/x-mixed-replace") - 1)
249           == 0)
250    {
251        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
252                      "image filter: multipart/x-mixed-replace response");
253
254        return NGX_ERROR;
255    }
256
257    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_image_filter_ctx_t));
258    if (ctx == NULL) {
259        return NGX_ERROR;
260    }
261
262    ngx_http_set_ctx(r, ctx, ngx_http_image_filter_module);
263
264    len = r->headers_out.content_length_n;
265
266    if (len != -1 && len > (off_t) conf->buffer_size) {
267        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
268                      "image filter: too big response: %O", len);
269
270        return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
271    }
272
273    if (len == -1) {
274        ctx->length = conf->buffer_size;
275
276    } else {
277        ctx->length = (size_t) len;
278    }
279
280    if (r->headers_out.refresh) {
281        r->headers_out.refresh->hash = 0;
282    }
283
284    r->main_filter_need_in_memory = 1;
285    r->allow_ranges = 0;
286
287    return NGX_OK;
288}
289
290
291static ngx_int_t
292ngx_http_image_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
293{
294    ngx_int_t                      rc;
295    ngx_str_t                     *ct;
296    ngx_chain_t                    out;
297    ngx_http_image_filter_ctx_t   *ctx;
298    ngx_http_image_filter_conf_t  *conf;
299
300    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image filter");
301
302    if (in == NULL) {
303        return ngx_http_next_body_filter(r, in);
304    }
305
306    ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
307
308    if (ctx == NULL) {
309        return ngx_http_next_body_filter(r, in);
310    }
311
312    switch (ctx->phase) {
313
314    case NGX_HTTP_IMAGE_START:
315
316        ctx->type = ngx_http_image_test(r, in);
317
318        conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
319
320        if (ctx->type == NGX_HTTP_IMAGE_NONE) {
321
322            if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
323                out.buf = ngx_http_image_json(r, NULL);
324
325                if (out.buf) {
326                    out.next = NULL;
327                    ctx->phase = NGX_HTTP_IMAGE_DONE;
328
329                    return ngx_http_image_send(r, ctx, &out);
330                }
331            }
332
333            return ngx_http_filter_finalize_request(r,
334                                              &ngx_http_image_filter_module,
335                                              NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
336        }
337
338        /* override content type */
339
340        ct = &ngx_http_image_types[ctx->type - 1];
341        r->headers_out.content_type_len = ct->len;
342        r->headers_out.content_type = *ct;
343        r->headers_out.content_type_lowcase = NULL;
344
345        if (conf->filter == NGX_HTTP_IMAGE_TEST) {
346            ctx->phase = NGX_HTTP_IMAGE_PASS;
347
348            return ngx_http_image_send(r, ctx, in);
349        }
350
351        ctx->phase = NGX_HTTP_IMAGE_READ;
352
353        /* fall through */
354
355    case NGX_HTTP_IMAGE_READ:
356
357        rc = ngx_http_image_read(r, in);
358
359        if (rc == NGX_AGAIN) {
360            return NGX_OK;
361        }
362
363        if (rc == NGX_ERROR) {
364            return ngx_http_filter_finalize_request(r,
365                                              &ngx_http_image_filter_module,
366                                              NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
367        }
368
369        /* fall through */
370
371    case NGX_HTTP_IMAGE_PROCESS:
372
373        out.buf = ngx_http_image_process(r);
374
375        if (out.buf == NULL) {
376            return ngx_http_filter_finalize_request(r,
377                                              &ngx_http_image_filter_module,
378                                              NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
379        }
380
381        out.next = NULL;
382        ctx->phase = NGX_HTTP_IMAGE_PASS;
383
384        return ngx_http_image_send(r, ctx, &out);
385
386    case NGX_HTTP_IMAGE_PASS:
387
388        return ngx_http_next_body_filter(r, in);
389
390    default: /* NGX_HTTP_IMAGE_DONE */
391
392        rc = ngx_http_next_body_filter(r, NULL);
393
394        /* NGX_ERROR resets any pending data */
395        return (rc == NGX_OK) ? NGX_ERROR : rc;
396    }
397}
398
399
400static ngx_int_t
401ngx_http_image_send(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx,
402    ngx_chain_t *in)
403{
404    ngx_int_t  rc;
405
406    rc = ngx_http_next_header_filter(r);
407
408    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
409        return NGX_ERROR;
410    }
411
412    rc = ngx_http_next_body_filter(r, in);
413
414    if (ctx->phase == NGX_HTTP_IMAGE_DONE) {
415        /* NGX_ERROR resets any pending data */
416        return (rc == NGX_OK) ? NGX_ERROR : rc;
417    }
418
419    return rc;
420}
421
422
423static ngx_uint_t
424ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in)
425{
426    u_char  *p;
427
428    p = in->buf->pos;
429
430    if (in->buf->last - p < 16) {
431        return NGX_HTTP_IMAGE_NONE;
432    }
433
434    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
435                   "image filter: \"%c%c\"", p[0], p[1]);
436
437    if (p[0] == 0xff && p[1] == 0xd8) {
438
439        /* JPEG */
440
441        return NGX_HTTP_IMAGE_JPEG;
442
443    } else if (p[0] == 'G' && p[1] == 'I' && p[2] == 'F' && p[3] == '8'
444               && p[5] == 'a')
445    {
446        if (p[4] == '9' || p[4] == '7') {
447            /* GIF */
448            return NGX_HTTP_IMAGE_GIF;
449        }
450
451    } else if (p[0] == 0x89 && p[1] == 'P' && p[2] == 'N' && p[3] == 'G'
452               && p[4] == 0x0d && p[5] == 0x0a && p[6] == 0x1a && p[7] == 0x0a)
453    {
454        /* PNG */
455
456        return NGX_HTTP_IMAGE_PNG;
457
458    } else if (p[0] == 'R' && p[1] == 'I' && p[2] == 'F' && p[3] == 'F'
459               && p[8] == 'W' && p[9] == 'E' && p[10] == 'B' && p[11] == 'P')
460    {
461        /* WebP */
462
463        return NGX_HTTP_IMAGE_WEBP;
464    }
465
466    return NGX_HTTP_IMAGE_NONE;
467}
468
469
470static ngx_int_t
471ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in)
472{
473    u_char                       *p;
474    size_t                        size, rest;
475    ngx_buf_t                    *b;
476    ngx_chain_t                  *cl;
477    ngx_http_image_filter_ctx_t  *ctx;
478
479    ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
480
481    if (ctx->image == NULL) {
482        ctx->image = ngx_palloc(r->pool, ctx->length);
483        if (ctx->image == NULL) {
484            return NGX_ERROR;
485        }
486
487        ctx->last = ctx->image;
488    }
489
490    p = ctx->last;
491
492    for (cl = in; cl; cl = cl->next) {
493
494        b = cl->buf;
495        size = b->last - b->pos;
496
497        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
498                       "image buf: %uz", size);
499
500        rest = ctx->image + ctx->length - p;
501
502        if (size > rest) {
503            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
504                          "image filter: too big response");
505            return NGX_ERROR;
506        }
507
508        p = ngx_cpymem(p, b->pos, size);
509        b->pos += size;
510
511        if (b->last_buf) {
512            ctx->last = p;
513            return NGX_OK;
514        }
515    }
516
517    ctx->last = p;
518    r->connection->buffered |= NGX_HTTP_IMAGE_BUFFERED;
519
520    return NGX_AGAIN;
521}
522
523
524static ngx_buf_t *
525ngx_http_image_process(ngx_http_request_t *r)
526{
527    ngx_int_t                      rc;
528    ngx_http_image_filter_ctx_t   *ctx;
529    ngx_http_image_filter_conf_t  *conf;
530
531    r->connection->buffered &= ~NGX_HTTP_IMAGE_BUFFERED;
532
533    ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
534
535    rc = ngx_http_image_size(r, ctx);
536
537    conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
538
539    if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
540        return ngx_http_image_json(r, rc == NGX_OK ? ctx : NULL);
541    }
542
543    ctx->angle = ngx_http_image_filter_get_value(r, conf->acv, conf->angle);
544
545    if (conf->filter == NGX_HTTP_IMAGE_ROTATE) {
546
547        if (ctx->angle != 90 && ctx->angle != 180 && ctx->angle != 270) {
548            return NULL;
549        }
550
551        return ngx_http_image_resize(r, ctx);
552    }
553
554    ctx->max_width = ngx_http_image_filter_get_value(r, conf->wcv, conf->width);
555    if (ctx->max_width == 0) {
556        return NULL;
557    }
558
559    ctx->max_height = ngx_http_image_filter_get_value(r, conf->hcv,
560                                                      conf->height);
561    if (ctx->max_height == 0) {
562        return NULL;
563    }
564
565    if (rc == NGX_OK
566        && ctx->width <= ctx->max_width
567        && ctx->height <= ctx->max_height
568        && ctx->angle == 0
569        && !ctx->force)
570    {
571        return ngx_http_image_asis(r, ctx);
572    }
573
574    return ngx_http_image_resize(r, ctx);
575}
576
577
578static ngx_buf_t *
579ngx_http_image_json(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
580{
581    size_t      len;
582    ngx_buf_t  *b;
583
584    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
585    if (b == NULL) {
586        return NULL;
587    }
588
589    b->memory = 1;
590    b->last_buf = 1;
591
592    ngx_http_clean_header(r);
593
594    r->headers_out.status = NGX_HTTP_OK;
595    r->headers_out.content_type_len = sizeof("application/json") - 1;
596    ngx_str_set(&r->headers_out.content_type, "application/json");
597    r->headers_out.content_type_lowcase = NULL;
598
599    if (ctx == NULL) {
600        b->pos = (u_char *) "{}" CRLF;
601        b->last = b->pos + sizeof("{}" CRLF) - 1;
602
603        ngx_http_image_length(r, b);
604
605        return b;
606    }
607
608    len = sizeof("{ \"img\" : "
609                 "{ \"width\": , \"height\": , \"type\": \"jpeg\" } }" CRLF) - 1
610          + 2 * NGX_SIZE_T_LEN;
611
612    b->pos = ngx_pnalloc(r->pool, len);
613    if (b->pos == NULL) {
614        return NULL;
615    }
616
617    b->last = ngx_sprintf(b->pos,
618                          "{ \"img\" : "
619                                       "{ \"width\": %uz,"
620                                        " \"height\": %uz,"
621                                        " \"type\": \"%s\" } }" CRLF,
622                          ctx->width, ctx->height,
623                          ngx_http_image_types[ctx->type - 1].data + 6);
624
625    ngx_http_image_length(r, b);
626
627    return b;
628}
629
630
631static ngx_buf_t *
632ngx_http_image_asis(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
633{
634    ngx_buf_t  *b;
635
636    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
637    if (b == NULL) {
638        return NULL;
639    }
640
641    b->pos = ctx->image;
642    b->last = ctx->last;
643    b->memory = 1;
644    b->last_buf = 1;
645
646    ngx_http_image_length(r, b);
647
648    return b;
649}
650
651
652static void
653ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b)
654{
655    r->headers_out.content_length_n = b->last - b->pos;
656
657    if (r->headers_out.content_length) {
658        r->headers_out.content_length->hash = 0;
659    }
660
661    r->headers_out.content_length = NULL;
662}
663
664
665static ngx_int_t
666ngx_http_image_size(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
667{
668    u_char      *p, *last;
669    size_t       len, app;
670    ngx_uint_t   width, height;
671
672    p = ctx->image;
673
674    switch (ctx->type) {
675
676    case NGX_HTTP_IMAGE_JPEG:
677
678        p += 2;
679        last = ctx->image + ctx->length - 10;
680        width = 0;
681        height = 0;
682        app = 0;
683
684        while (p < last) {
685
686            if (p[0] == 0xff && p[1] != 0xff) {
687
688                ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
689                               "JPEG: %02xd %02xd", p[0], p[1]);
690
691                p++;
692
693                if ((*p == 0xc0 || *p == 0xc1 || *p == 0xc2 || *p == 0xc3
694                     || *p == 0xc9 || *p == 0xca || *p == 0xcb)
695                    && (width == 0 || height == 0))
696                {
697                    width = p[6] * 256 + p[7];
698                    height = p[4] * 256 + p[5];
699                }
700
701                ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
702                               "JPEG: %02xd %02xd", p[1], p[2]);
703
704                len = p[1] * 256 + p[2];
705
706                if (*p >= 0xe1 && *p <= 0xef) {
707                    /* application data, e.g., EXIF, Adobe XMP, etc. */
708                    app += len;
709                }
710
711                p += len;
712
713                continue;
714            }
715
716            p++;
717        }
718
719        if (width == 0 || height == 0) {
720            return NGX_DECLINED;
721        }
722
723        if (ctx->length / 20 < app) {
724            /* force conversion if application data consume more than 5% */
725            ctx->force = 1;
726            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
727                           "app data size: %uz", app);
728        }
729
730        break;
731
732    case NGX_HTTP_IMAGE_GIF:
733
734        if (ctx->length < 10) {
735            return NGX_DECLINED;
736        }
737
738        width = p[7] * 256 + p[6];
739        height = p[9] * 256 + p[8];
740
741        break;
742
743    case NGX_HTTP_IMAGE_PNG:
744
745        if (ctx->length < 24) {
746            return NGX_DECLINED;
747        }
748
749        width = p[18] * 256 + p[19];
750        height = p[22] * 256 + p[23];
751
752        break;
753
754    case NGX_HTTP_IMAGE_WEBP:
755
756        if (ctx->length < 30) {
757            return NGX_DECLINED;
758        }
759
760        if (p[12] != 'V' || p[13] != 'P' || p[14] != '8') {
761            return NGX_DECLINED;
762        }
763
764        switch (p[15]) {
765
766        case ' ':
767            if (p[20] & 1) {
768                /* not a key frame */
769                return NGX_DECLINED;
770            }
771
772            if (p[23] != 0x9d || p[24] != 0x01 || p[25] != 0x2a) {
773                /* invalid start code */
774                return NGX_DECLINED;
775            }
776
777            width = (p[26] | p[27] << 8) & 0x3fff;
778            height = (p[28] | p[29] << 8) & 0x3fff;
779
780            break;
781
782        case 'L':
783            if (p[20] != 0x2f) {
784                /* invalid signature */
785                return NGX_DECLINED;
786            }
787
788            width = ((p[21] | p[22] << 8) & 0x3fff) + 1;
789            height = ((p[22] >> 6 | p[23] << 2 | p[24] << 10) & 0x3fff) + 1;
790
791            break;
792
793        case 'X':
794            width = (p[24] | p[25] << 8 | p[26] << 16) + 1;
795            height = (p[27] | p[28] << 8 | p[29] << 16) + 1;
796            break;
797
798        default:
799            return NGX_DECLINED;
800        }
801
802        break;
803
804    default:
805
806        return NGX_DECLINED;
807    }
808
809    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
810                   "image size: %d x %d", (int) width, (int) height);
811
812    ctx->width = width;
813    ctx->height = height;
814
815    return NGX_OK;
816}
817
818
819static ngx_buf_t *
820ngx_http_image_resize(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
821{
822    int                            sx, sy, dx, dy, ox, oy, ax, ay, size,
823                                   colors, palette, transparent, sharpen,
824                                   red, green, blue, t;
825    u_char                        *out;
826    ngx_buf_t                     *b;
827    ngx_uint_t                     resize;
828    gdImagePtr                     src, dst;
829    ngx_pool_cleanup_t            *cln;
830    ngx_http_image_filter_conf_t  *conf;
831
832    src = ngx_http_image_source(r, ctx);
833
834    if (src == NULL) {
835        return NULL;
836    }
837
838    sx = gdImageSX(src);
839    sy = gdImageSY(src);
840
841    conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
842
843    if (!ctx->force
844        && ctx->angle == 0
845        && (ngx_uint_t) sx <= ctx->max_width
846        && (ngx_uint_t) sy <= ctx->max_height)
847    {
848        gdImageDestroy(src);
849        return ngx_http_image_asis(r, ctx);
850    }
851
852    colors = gdImageColorsTotal(src);
853
854    if (colors && conf->transparency) {
855        transparent = gdImageGetTransparent(src);
856
857        if (transparent != -1) {
858            palette = colors;
859            red = gdImageRed(src, transparent);
860            green = gdImageGreen(src, transparent);
861            blue = gdImageBlue(src, transparent);
862
863            goto transparent;
864        }
865    }
866
867    palette = 0;
868    transparent = -1;
869    red = 0;
870    green = 0;
871    blue = 0;
872
873transparent:
874
875    gdImageColorTransparent(src, -1);
876
877    dx = sx;
878    dy = sy;
879
880    if (conf->filter == NGX_HTTP_IMAGE_RESIZE) {
881
882        if ((ngx_uint_t) dx > ctx->max_width) {
883            dy = dy * ctx->max_width / dx;
884            dy = dy ? dy : 1;
885            dx = ctx->max_width;
886        }
887
888        if ((ngx_uint_t) dy > ctx->max_height) {
889            dx = dx * ctx->max_height / dy;
890            dx = dx ? dx : 1;
891            dy = ctx->max_height;
892        }
893
894        resize = 1;
895
896    } else if (conf->filter == NGX_HTTP_IMAGE_ROTATE) {
897
898        resize = 0;
899
900    } else { /* NGX_HTTP_IMAGE_CROP */
901
902        resize = 0;
903
904        if ((double) dx / dy < (double) ctx->max_width / ctx->max_height) {
905            if ((ngx_uint_t) dx > ctx->max_width) {
906                dy = dy * ctx->max_width / dx;
907                dy = dy ? dy : 1;
908                dx = ctx->max_width;
909                resize = 1;
910            }
911
912        } else {
913            if ((ngx_uint_t) dy > ctx->max_height) {
914                dx = dx * ctx->max_height / dy;
915                dx = dx ? dx : 1;
916                dy = ctx->max_height;
917                resize = 1;
918            }
919        }
920    }
921
922    if (resize) {
923        dst = ngx_http_image_new(r, dx, dy, palette);
924        if (dst == NULL) {
925            gdImageDestroy(src);
926            return NULL;
927        }
928
929        if (colors == 0) {
930            gdImageSaveAlpha(dst, 1);
931            gdImageAlphaBlending(dst, 0);
932        }
933
934        gdImageCopyResampled(dst, src, 0, 0, 0, 0, dx, dy, sx, sy);
935
936        if (colors) {
937            gdImageTrueColorToPalette(dst, 1, 256);
938        }
939
940        gdImageDestroy(src);
941
942    } else {
943        dst = src;
944    }
945
946    if (ctx->angle) {
947        src = dst;
948
949        ax = (dx % 2 == 0) ? 1 : 0;
950        ay = (dy % 2 == 0) ? 1 : 0;
951
952        switch (ctx->angle) {
953
954        case 90:
955        case 270:
956            dst = ngx_http_image_new(r, dy, dx, palette);
957            if (dst == NULL) {
958                gdImageDestroy(src);
959                return NULL;
960            }
961            if (ctx->angle == 90) {
962                ox = dy / 2 + ay;
963                oy = dx / 2 - ax;
964
965            } else {
966                ox = dy / 2 - ay;
967                oy = dx / 2 + ax;
968            }
969
970            gdImageCopyRotated(dst, src, ox, oy, 0, 0,
971                               dx + ax, dy + ay, ctx->angle);
972            gdImageDestroy(src);
973
974            t = dx;
975            dx = dy;
976            dy = t;
977            break;
978
979        case 180:
980            dst = ngx_http_image_new(r, dx, dy, palette);
981            if (dst == NULL) {
982                gdImageDestroy(src);
983                return NULL;
984            }
985            gdImageCopyRotated(dst, src, dx / 2 - ax, dy / 2 - ay, 0, 0,
986                               dx + ax, dy + ay, ctx->angle);
987            gdImageDestroy(src);
988            break;
989        }
990    }
991
992    if (conf->filter == NGX_HTTP_IMAGE_CROP) {
993
994        src = dst;
995
996        if ((ngx_uint_t) dx > ctx->max_width) {
997            ox = dx - ctx->max_width;
998
999        } else {
1000            ox = 0;
1001        }
1002
1003        if ((ngx_uint_t) dy > ctx->max_height) {
1004            oy = dy - ctx->max_height;
1005
1006        } else {
1007            oy = 0;
1008        }
1009
1010        if (ox || oy) {
1011
1012            dst = ngx_http_image_new(r, dx - ox, dy - oy, colors);
1013
1014            if (dst == NULL) {
1015                gdImageDestroy(src);
1016                return NULL;
1017            }
1018
1019            ox /= 2;
1020            oy /= 2;
1021
1022            ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1023                           "image crop: %d x %d @ %d x %d",
1024                           dx, dy, ox, oy);
1025
1026            if (colors == 0) {
1027                gdImageSaveAlpha(dst, 1);
1028                gdImageAlphaBlending(dst, 0);
1029            }
1030
1031            gdImageCopy(dst, src, 0, 0, ox, oy, dx - ox, dy - oy);
1032
1033            if (colors) {
1034                gdImageTrueColorToPalette(dst, 1, 256);
1035            }
1036
1037            gdImageDestroy(src);
1038        }
1039    }
1040
1041    if (transparent != -1 && colors) {
1042        gdImageColorTransparent(dst, gdImageColorExact(dst, red, green, blue));
1043    }
1044
1045    sharpen = ngx_http_image_filter_get_value(r, conf->shcv, conf->sharpen);
1046    if (sharpen > 0) {
1047        gdImageSharpen(dst, sharpen);
1048    }
1049
1050    gdImageInterlace(dst, (int) conf->interlace);
1051
1052    out = ngx_http_image_out(r, ctx->type, dst, &size);
1053
1054    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1055                   "image: %d x %d %d", sx, sy, colors);
1056
1057    gdImageDestroy(dst);
1058    ngx_pfree(r->pool, ctx->image);
1059
1060    if (out == NULL) {
1061        return NULL;
1062    }
1063
1064    cln = ngx_pool_cleanup_add(r->pool, 0);
1065    if (cln == NULL) {
1066        gdFree(out);
1067        return NULL;
1068    }
1069
1070    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
1071    if (b == NULL) {
1072        gdFree(out);
1073        return NULL;
1074    }
1075
1076    cln->handler = ngx_http_image_cleanup;
1077    cln->data = out;
1078
1079    b->pos = out;
1080    b->last = out + size;
1081    b->memory = 1;
1082    b->last_buf = 1;
1083
1084    ngx_http_image_length(r, b);
1085    ngx_http_weak_etag(r);
1086
1087    return b;
1088}
1089
1090
1091static gdImagePtr
1092ngx_http_image_source(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
1093{
1094    char        *failed;
1095    gdImagePtr   img;
1096
1097    img = NULL;
1098
1099    switch (ctx->type) {
1100
1101    case NGX_HTTP_IMAGE_JPEG:
1102        img = gdImageCreateFromJpegPtr(ctx->length, ctx->image);
1103        failed = "gdImageCreateFromJpegPtr() failed";
1104        break;
1105
1106    case NGX_HTTP_IMAGE_GIF:
1107        img = gdImageCreateFromGifPtr(ctx->length, ctx->image);
1108        failed = "gdImageCreateFromGifPtr() failed";
1109        break;
1110
1111    case NGX_HTTP_IMAGE_PNG:
1112        img = gdImageCreateFromPngPtr(ctx->length, ctx->image);
1113        failed = "gdImageCreateFromPngPtr() failed";
1114        break;
1115
1116    case NGX_HTTP_IMAGE_WEBP:
1117#if (NGX_HAVE_GD_WEBP)
1118        img = gdImageCreateFromWebpPtr(ctx->length, ctx->image);
1119        failed = "gdImageCreateFromWebpPtr() failed";
1120#else
1121        failed = "nginx was built without GD WebP support";
1122#endif
1123        break;
1124
1125    default:
1126        failed = "unknown image type";
1127        break;
1128    }
1129
1130    if (img == NULL) {
1131        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed);
1132    }
1133
1134    return img;
1135}
1136
1137
1138static gdImagePtr
1139ngx_http_image_new(ngx_http_request_t *r, int w, int h, int colors)
1140{
1141    gdImagePtr  img;
1142
1143    if (colors == 0) {
1144        img = gdImageCreateTrueColor(w, h);
1145
1146        if (img == NULL) {
1147            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
1148                          "gdImageCreateTrueColor() failed");
1149            return NULL;
1150        }
1151
1152    } else {
1153        img = gdImageCreate(w, h);
1154
1155        if (img == NULL) {
1156            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
1157                          "gdImageCreate() failed");
1158            return NULL;
1159        }
1160    }
1161
1162    return img;
1163}
1164
1165
1166static u_char *
1167ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, gdImagePtr img,
1168    int *size)
1169{
1170    char                          *failed;
1171    u_char                        *out;
1172    ngx_int_t                      q;
1173    ngx_http_image_filter_conf_t  *conf;
1174
1175    out = NULL;
1176
1177    switch (type) {
1178
1179    case NGX_HTTP_IMAGE_JPEG:
1180        conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
1181
1182        q = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality);
1183        if (q <= 0) {
1184            return NULL;
1185        }
1186
1187        out = gdImageJpegPtr(img, size, q);
1188        failed = "gdImageJpegPtr() failed";
1189        break;
1190
1191    case NGX_HTTP_IMAGE_GIF:
1192        out = gdImageGifPtr(img, size);
1193        failed = "gdImageGifPtr() failed";
1194        break;
1195
1196    case NGX_HTTP_IMAGE_PNG:
1197        out = gdImagePngPtr(img, size);
1198        failed = "gdImagePngPtr() failed";
1199        break;
1200
1201    case NGX_HTTP_IMAGE_WEBP:
1202#if (NGX_HAVE_GD_WEBP)
1203        conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
1204
1205        q = ngx_http_image_filter_get_value(r, conf->wqcv, conf->webp_quality);
1206        if (q <= 0) {
1207            return NULL;
1208        }
1209
1210        out = gdImageWebpPtrEx(img, size, q);
1211        failed = "gdImageWebpPtrEx() failed";
1212#else
1213        failed = "nginx was built without GD WebP support";
1214#endif
1215        break;
1216
1217    default:
1218        failed = "unknown image type";
1219        break;
1220    }
1221
1222    if (out == NULL) {
1223        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, failed);
1224    }
1225
1226    return out;
1227}
1228
1229
1230static void
1231ngx_http_image_cleanup(void *data)
1232{
1233    gdFree(data);
1234}
1235
1236
1237static ngx_uint_t
1238ngx_http_image_filter_get_value(ngx_http_request_t *r,
1239    ngx_http_complex_value_t *cv, ngx_uint_t v)
1240{
1241    ngx_str_t  val;
1242
1243    if (cv == NULL) {
1244        return v;
1245    }
1246
1247    if (ngx_http_complex_value(r, cv, &val) != NGX_OK) {
1248        return 0;
1249    }
1250
1251    return ngx_http_image_filter_value(&val);
1252}
1253
1254
1255static ngx_uint_t
1256ngx_http_image_filter_value(ngx_str_t *value)
1257{
1258    ngx_int_t  n;
1259
1260    if (value->len == 1 && value->data[0] == '-') {
1261        return (ngx_uint_t) -1;
1262    }
1263
1264    n = ngx_atoi(value->data, value->len);
1265
1266    if (n > 0) {
1267        return (ngx_uint_t) n;
1268    }
1269
1270    return 0;
1271}
1272
1273
1274static void *
1275ngx_http_image_filter_create_conf(ngx_conf_t *cf)
1276{
1277    ngx_http_image_filter_conf_t  *conf;
1278
1279    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_image_filter_conf_t));
1280    if (conf == NULL) {
1281        return NULL;
1282    }
1283
1284    /*
1285     * set by ngx_pcalloc():
1286     *
1287     *     conf->width = 0;
1288     *     conf->height = 0;
1289     *     conf->angle = 0;
1290     *     conf->wcv = NULL;
1291     *     conf->hcv = NULL;
1292     *     conf->acv = NULL;
1293     *     conf->jqcv = NULL;
1294     *     conf->wqcv = NULL;
1295     *     conf->shcv = NULL;
1296     */
1297
1298    conf->filter = NGX_CONF_UNSET_UINT;
1299    conf->jpeg_quality = NGX_CONF_UNSET_UINT;
1300    conf->webp_quality = NGX_CONF_UNSET_UINT;
1301    conf->sharpen = NGX_CONF_UNSET_UINT;
1302    conf->transparency = NGX_CONF_UNSET;
1303    conf->interlace = NGX_CONF_UNSET;
1304    conf->buffer_size = NGX_CONF_UNSET_SIZE;
1305
1306    return conf;
1307}
1308
1309
1310static char *
1311ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
1312{
1313    ngx_http_image_filter_conf_t *prev = parent;
1314    ngx_http_image_filter_conf_t *conf = child;
1315
1316    if (conf->filter == NGX_CONF_UNSET_UINT) {
1317
1318        if (prev->filter == NGX_CONF_UNSET_UINT) {
1319            conf->filter = NGX_HTTP_IMAGE_OFF;
1320
1321        } else {
1322            conf->filter = prev->filter;
1323            conf->width = prev->width;
1324            conf->height = prev->height;
1325            conf->angle = prev->angle;
1326            conf->wcv = prev->wcv;
1327            conf->hcv = prev->hcv;
1328            conf->acv = prev->acv;
1329        }
1330    }
1331
1332    if (conf->jpeg_quality == NGX_CONF_UNSET_UINT) {
1333
1334        /* 75 is libjpeg default quality */
1335        ngx_conf_merge_uint_value(conf->jpeg_quality, prev->jpeg_quality, 75);
1336
1337        if (conf->jqcv == NULL) {
1338            conf->jqcv = prev->jqcv;
1339        }
1340    }
1341
1342    if (conf->webp_quality == NGX_CONF_UNSET_UINT) {
1343
1344        /* 80 is libwebp default quality */
1345        ngx_conf_merge_uint_value(conf->webp_quality, prev->webp_quality, 80);
1346
1347        if (conf->wqcv == NULL) {
1348            conf->wqcv = prev->wqcv;
1349        }
1350    }
1351
1352    if (conf->sharpen == NGX_CONF_UNSET_UINT) {
1353        ngx_conf_merge_uint_value(conf->sharpen, prev->sharpen, 0);
1354
1355        if (conf->shcv == NULL) {
1356            conf->shcv = prev->shcv;
1357        }
1358    }
1359
1360    ngx_conf_merge_value(conf->transparency, prev->transparency, 1);
1361
1362    ngx_conf_merge_value(conf->interlace, prev->interlace, 0);
1363
1364    ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size,
1365                              1 * 1024 * 1024);
1366
1367    return NGX_CONF_OK;
1368}
1369
1370
1371static char *
1372ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
1373{
1374    ngx_http_image_filter_conf_t *imcf = conf;
1375
1376    ngx_str_t                         *value;
1377    ngx_int_t                          n;
1378    ngx_uint_t                         i;
1379    ngx_http_complex_value_t           cv;
1380    ngx_http_compile_complex_value_t   ccv;
1381
1382    value = cf->args->elts;
1383
1384    i = 1;
1385
1386    if (cf->args->nelts == 2) {
1387        if (ngx_strcmp(value[i].data, "off") == 0) {
1388            imcf->filter = NGX_HTTP_IMAGE_OFF;
1389
1390        } else if (ngx_strcmp(value[i].data, "test") == 0) {
1391            imcf->filter = NGX_HTTP_IMAGE_TEST;
1392
1393        } else if (ngx_strcmp(value[i].data, "size") == 0) {
1394            imcf->filter = NGX_HTTP_IMAGE_SIZE;
1395
1396        } else {
1397            goto failed;
1398        }
1399
1400        return NGX_CONF_OK;
1401
1402    } else if (cf->args->nelts == 3) {
1403
1404        if (ngx_strcmp(value[i].data, "rotate") == 0) {
1405            if (imcf->filter != NGX_HTTP_IMAGE_RESIZE
1406                && imcf->filter != NGX_HTTP_IMAGE_CROP)
1407            {
1408                imcf->filter = NGX_HTTP_IMAGE_ROTATE;
1409            }
1410
1411            ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1412
1413            ccv.cf = cf;
1414            ccv.value = &value[++i];
1415            ccv.complex_value = &cv;
1416
1417            if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1418                return NGX_CONF_ERROR;
1419            }
1420
1421            if (cv.lengths == NULL) {
1422                n = ngx_http_image_filter_value(&value[i]);
1423
1424                if (n != 90 && n != 180 && n != 270) {
1425                    goto failed;
1426                }
1427
1428                imcf->angle = (ngx_uint_t) n;
1429
1430            } else {
1431                imcf->acv = ngx_palloc(cf->pool,
1432                                       sizeof(ngx_http_complex_value_t));
1433                if (imcf->acv == NULL) {
1434                    return NGX_CONF_ERROR;
1435                }
1436
1437                *imcf->acv = cv;
1438            }
1439
1440            return NGX_CONF_OK;
1441
1442        } else {
1443            goto failed;
1444        }
1445    }
1446
1447    if (ngx_strcmp(value[i].data, "resize") == 0) {
1448        imcf->filter = NGX_HTTP_IMAGE_RESIZE;
1449
1450    } else if (ngx_strcmp(value[i].data, "crop") == 0) {
1451        imcf->filter = NGX_HTTP_IMAGE_CROP;
1452
1453    } else {
1454        goto failed;
1455    }
1456
1457    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1458
1459    ccv.cf = cf;
1460    ccv.value = &value[++i];
1461    ccv.complex_value = &cv;
1462
1463    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1464        return NGX_CONF_ERROR;
1465    }
1466
1467    if (cv.lengths == NULL) {
1468        n = ngx_http_image_filter_value(&value[i]);
1469
1470        if (n == 0) {
1471            goto failed;
1472        }
1473
1474        imcf->width = (ngx_uint_t) n;
1475
1476    } else {
1477        imcf->wcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
1478        if (imcf->wcv == NULL) {
1479            return NGX_CONF_ERROR;
1480        }
1481
1482        *imcf->wcv = cv;
1483    }
1484
1485    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1486
1487    ccv.cf = cf;
1488    ccv.value = &value[++i];
1489    ccv.complex_value = &cv;
1490
1491    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1492        return NGX_CONF_ERROR;
1493    }
1494
1495    if (cv.lengths == NULL) {
1496        n = ngx_http_image_filter_value(&value[i]);
1497
1498        if (n == 0) {
1499            goto failed;
1500        }
1501
1502        imcf->height = (ngx_uint_t) n;
1503
1504    } else {
1505        imcf->hcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
1506        if (imcf->hcv == NULL) {
1507            return NGX_CONF_ERROR;
1508        }
1509
1510        *imcf->hcv = cv;
1511    }
1512
1513    return NGX_CONF_OK;
1514
1515failed:
1516
1517    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"",
1518                       &value[i]);
1519
1520    return NGX_CONF_ERROR;
1521}
1522
1523
1524static char *
1525ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf, ngx_command_t *cmd,
1526    void *conf)
1527{
1528    ngx_http_image_filter_conf_t *imcf = conf;
1529
1530    ngx_str_t                         *value;
1531    ngx_int_t                          n;
1532    ngx_http_complex_value_t           cv;
1533    ngx_http_compile_complex_value_t   ccv;
1534
1535    value = cf->args->elts;
1536
1537    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1538
1539    ccv.cf = cf;
1540    ccv.value = &value[1];
1541    ccv.complex_value = &cv;
1542
1543    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1544        return NGX_CONF_ERROR;
1545    }
1546
1547    if (cv.lengths == NULL) {
1548        n = ngx_http_image_filter_value(&value[1]);
1549
1550        if (n <= 0) {
1551            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1552                               "invalid value \"%V\"", &value[1]);
1553            return NGX_CONF_ERROR;
1554        }
1555
1556        imcf->jpeg_quality = (ngx_uint_t) n;
1557
1558    } else {
1559        imcf->jqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
1560        if (imcf->jqcv == NULL) {
1561            return NGX_CONF_ERROR;
1562        }
1563
1564        *imcf->jqcv = cv;
1565    }
1566
1567    return NGX_CONF_OK;
1568}
1569
1570
1571static char *
1572ngx_http_image_filter_webp_quality(ngx_conf_t *cf, ngx_command_t *cmd,
1573    void *conf)
1574{
1575    ngx_http_image_filter_conf_t *imcf = conf;
1576
1577    ngx_str_t                         *value;
1578    ngx_int_t                          n;
1579    ngx_http_complex_value_t           cv;
1580    ngx_http_compile_complex_value_t   ccv;
1581
1582    value = cf->args->elts;
1583
1584    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1585
1586    ccv.cf = cf;
1587    ccv.value = &value[1];
1588    ccv.complex_value = &cv;
1589
1590    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1591        return NGX_CONF_ERROR;
1592    }
1593
1594    if (cv.lengths == NULL) {
1595        n = ngx_http_image_filter_value(&value[1]);
1596
1597        if (n <= 0) {
1598            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1599                               "invalid value \"%V\"", &value[1]);
1600            return NGX_CONF_ERROR;
1601        }
1602
1603        imcf->webp_quality = (ngx_uint_t) n;
1604
1605    } else {
1606        imcf->wqcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
1607        if (imcf->wqcv == NULL) {
1608            return NGX_CONF_ERROR;
1609        }
1610
1611        *imcf->wqcv = cv;
1612    }
1613
1614    return NGX_CONF_OK;
1615}
1616
1617
1618static char *
1619ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd,
1620    void *conf)
1621{
1622    ngx_http_image_filter_conf_t *imcf = conf;
1623
1624    ngx_str_t                         *value;
1625    ngx_int_t                          n;
1626    ngx_http_complex_value_t           cv;
1627    ngx_http_compile_complex_value_t   ccv;
1628
1629    value = cf->args->elts;
1630
1631    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1632
1633    ccv.cf = cf;
1634    ccv.value = &value[1];
1635    ccv.complex_value = &cv;
1636
1637    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1638        return NGX_CONF_ERROR;
1639    }
1640
1641    if (cv.lengths == NULL) {
1642        n = ngx_http_image_filter_value(&value[1]);
1643
1644        if (n < 0) {
1645            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
1646                               "invalid value \"%V\"", &value[1]);
1647            return NGX_CONF_ERROR;
1648        }
1649
1650        imcf->sharpen = (ngx_uint_t) n;
1651
1652    } else {
1653        imcf->shcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
1654        if (imcf->shcv == NULL) {
1655            return NGX_CONF_ERROR;
1656        }
1657
1658        *imcf->shcv = cv;
1659    }
1660
1661    return NGX_CONF_OK;
1662}
1663
1664
1665static ngx_int_t
1666ngx_http_image_filter_init(ngx_conf_t *cf)
1667{
1668    ngx_http_next_header_filter = ngx_http_top_header_filter;
1669    ngx_http_top_header_filter = ngx_http_image_header_filter;
1670
1671    ngx_http_next_body_filter = ngx_http_top_body_filter;
1672    ngx_http_top_body_filter = ngx_http_image_body_filter;
1673
1674    return NGX_OK;
1675}
1676