Back to home page

Nginx displayed by LXR

Source navigation ]
Diff markup ]
Identifier search ]
general search ]
 
 
Version: nginx-1.13.12 ]​[ nginx-1.12.2 ]​

0001 
0002 /*
0003  * Copyright (C) Igor Sysoev
0004  * Copyright (C) Nginx, Inc.
0005  */
0006 
0007 
0008 #include <ngx_config.h>
0009 #include <ngx_core.h>
0010 #include <ngx_http.h>
0011 
0012 #include <gd.h>
0013 
0014 
0015 #define NGX_HTTP_IMAGE_OFF       0
0016 #define NGX_HTTP_IMAGE_TEST      1
0017 #define NGX_HTTP_IMAGE_SIZE      2
0018 #define NGX_HTTP_IMAGE_RESIZE    3
0019 #define NGX_HTTP_IMAGE_CROP      4
0020 #define NGX_HTTP_IMAGE_ROTATE    5
0021 
0022 
0023 #define NGX_HTTP_IMAGE_START     0
0024 #define NGX_HTTP_IMAGE_READ      1
0025 #define NGX_HTTP_IMAGE_PROCESS   2
0026 #define NGX_HTTP_IMAGE_PASS      3
0027 #define NGX_HTTP_IMAGE_DONE      4
0028 
0029 
0030 #define NGX_HTTP_IMAGE_NONE      0
0031 #define NGX_HTTP_IMAGE_JPEG      1
0032 #define NGX_HTTP_IMAGE_GIF       2
0033 #define NGX_HTTP_IMAGE_PNG       3
0034 #define NGX_HTTP_IMAGE_WEBP      4
0035 
0036 
0037 #define NGX_HTTP_IMAGE_BUFFERED  0x08
0038 
0039 
0040 typedef struct {
0041     ngx_uint_t                   filter;
0042     ngx_uint_t                   width;
0043     ngx_uint_t                   height;
0044     ngx_uint_t                   angle;
0045     ngx_uint_t                   jpeg_quality;
0046     ngx_uint_t                   webp_quality;
0047     ngx_uint_t                   sharpen;
0048 
0049     ngx_flag_t                   transparency;
0050     ngx_flag_t                   interlace;
0051 
0052     ngx_http_complex_value_t    *wcv;
0053     ngx_http_complex_value_t    *hcv;
0054     ngx_http_complex_value_t    *acv;
0055     ngx_http_complex_value_t    *jqcv;
0056     ngx_http_complex_value_t    *wqcv;
0057     ngx_http_complex_value_t    *shcv;
0058 
0059     size_t                       buffer_size;
0060 } ngx_http_image_filter_conf_t;
0061 
0062 
0063 typedef struct {
0064     u_char                      *image;
0065     u_char                      *last;
0066 
0067     size_t                       length;
0068 
0069     ngx_uint_t                   width;
0070     ngx_uint_t                   height;
0071     ngx_uint_t                   max_width;
0072     ngx_uint_t                   max_height;
0073     ngx_uint_t                   angle;
0074 
0075     ngx_uint_t                   phase;
0076     ngx_uint_t                   type;
0077     ngx_uint_t                   force;
0078 } ngx_http_image_filter_ctx_t;
0079 
0080 
0081 static ngx_int_t ngx_http_image_send(ngx_http_request_t *r,
0082     ngx_http_image_filter_ctx_t *ctx, ngx_chain_t *in);
0083 static ngx_uint_t ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in);
0084 static ngx_int_t ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in);
0085 static ngx_buf_t *ngx_http_image_process(ngx_http_request_t *r);
0086 static ngx_buf_t *ngx_http_image_json(ngx_http_request_t *r,
0087     ngx_http_image_filter_ctx_t *ctx);
0088 static ngx_buf_t *ngx_http_image_asis(ngx_http_request_t *r,
0089     ngx_http_image_filter_ctx_t *ctx);
0090 static void ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b);
0091 static ngx_int_t ngx_http_image_size(ngx_http_request_t *r,
0092     ngx_http_image_filter_ctx_t *ctx);
0093 
0094 static ngx_buf_t *ngx_http_image_resize(ngx_http_request_t *r,
0095     ngx_http_image_filter_ctx_t *ctx);
0096 static gdImagePtr ngx_http_image_source(ngx_http_request_t *r,
0097     ngx_http_image_filter_ctx_t *ctx);
0098 static gdImagePtr ngx_http_image_new(ngx_http_request_t *r, int w, int h,
0099     int colors);
0100 static u_char *ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type,
0101     gdImagePtr img, int *size);
0102 static void ngx_http_image_cleanup(void *data);
0103 static ngx_uint_t ngx_http_image_filter_get_value(ngx_http_request_t *r,
0104     ngx_http_complex_value_t *cv, ngx_uint_t v);
0105 static ngx_uint_t ngx_http_image_filter_value(ngx_str_t *value);
0106 
0107 
0108 static void *ngx_http_image_filter_create_conf(ngx_conf_t *cf);
0109 static char *ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent,
0110     void *child);
0111 static char *ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd,
0112     void *conf);
0113 static char *ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf,
0114     ngx_command_t *cmd, void *conf);
0115 static char *ngx_http_image_filter_webp_quality(ngx_conf_t *cf,
0116     ngx_command_t *cmd, void *conf);
0117 static char *ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd,
0118     void *conf);
0119 static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf);
0120 
0121 
0122 static ngx_command_t  ngx_http_image_filter_commands[] = {
0123 
0124     { ngx_string("image_filter"),
0125       NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
0126       ngx_http_image_filter,
0127       NGX_HTTP_LOC_CONF_OFFSET,
0128       0,
0129       NULL },
0130 
0131     { ngx_string("image_filter_jpeg_quality"),
0132       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
0133       ngx_http_image_filter_jpeg_quality,
0134       NGX_HTTP_LOC_CONF_OFFSET,
0135       0,
0136       NULL },
0137 
0138     { ngx_string("image_filter_webp_quality"),
0139       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
0140       ngx_http_image_filter_webp_quality,
0141       NGX_HTTP_LOC_CONF_OFFSET,
0142       0,
0143       NULL },
0144 
0145     { ngx_string("image_filter_sharpen"),
0146       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
0147       ngx_http_image_filter_sharpen,
0148       NGX_HTTP_LOC_CONF_OFFSET,
0149       0,
0150       NULL },
0151 
0152     { ngx_string("image_filter_transparency"),
0153       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
0154       ngx_conf_set_flag_slot,
0155       NGX_HTTP_LOC_CONF_OFFSET,
0156       offsetof(ngx_http_image_filter_conf_t, transparency),
0157       NULL },
0158 
0159     { ngx_string("image_filter_interlace"),
0160       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
0161       ngx_conf_set_flag_slot,
0162       NGX_HTTP_LOC_CONF_OFFSET,
0163       offsetof(ngx_http_image_filter_conf_t, interlace),
0164       NULL },
0165 
0166     { ngx_string("image_filter_buffer"),
0167       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
0168       ngx_conf_set_size_slot,
0169       NGX_HTTP_LOC_CONF_OFFSET,
0170       offsetof(ngx_http_image_filter_conf_t, buffer_size),
0171       NULL },
0172 
0173       ngx_null_command
0174 };
0175 
0176 
0177 static ngx_http_module_t  ngx_http_image_filter_module_ctx = {
0178     NULL,                                  /* preconfiguration */
0179     ngx_http_image_filter_init,            /* postconfiguration */
0180 
0181     NULL,                                  /* create main configuration */
0182     NULL,                                  /* init main configuration */
0183 
0184     NULL,                                  /* create server configuration */
0185     NULL,                                  /* merge server configuration */
0186 
0187     ngx_http_image_filter_create_conf,     /* create location configuration */
0188     ngx_http_image_filter_merge_conf       /* merge location configuration */
0189 };
0190 
0191 
0192 ngx_module_t  ngx_http_image_filter_module = {
0193     NGX_MODULE_V1,
0194     &ngx_http_image_filter_module_ctx,     /* module context */
0195     ngx_http_image_filter_commands,        /* module directives */
0196     NGX_HTTP_MODULE,                       /* module type */
0197     NULL,                                  /* init master */
0198     NULL,                                  /* init module */
0199     NULL,                                  /* init process */
0200     NULL,                                  /* init thread */
0201     NULL,                                  /* exit thread */
0202     NULL,                                  /* exit process */
0203     NULL,                                  /* exit master */
0204     NGX_MODULE_V1_PADDING
0205 };
0206 
0207 
0208 static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
0209 static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
0210 
0211 
0212 static ngx_str_t  ngx_http_image_types[] = {
0213     ngx_string("image/jpeg"),
0214     ngx_string("image/gif"),
0215     ngx_string("image/png"),
0216     ngx_string("image/webp")
0217 };
0218 
0219 
0220 static ngx_int_t
0221 ngx_http_image_header_filter(ngx_http_request_t *r)
0222 {
0223     off_t                          len;
0224     ngx_http_image_filter_ctx_t   *ctx;
0225     ngx_http_image_filter_conf_t  *conf;
0226 
0227     if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
0228         return ngx_http_next_header_filter(r);
0229     }
0230 
0231     ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
0232 
0233     if (ctx) {
0234         ngx_http_set_ctx(r, NULL, ngx_http_image_filter_module);
0235         return ngx_http_next_header_filter(r);
0236     }
0237 
0238     conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
0239 
0240     if (conf->filter == NGX_HTTP_IMAGE_OFF) {
0241         return ngx_http_next_header_filter(r);
0242     }
0243 
0244     if (r->headers_out.content_type.len
0245             >= sizeof("multipart/x-mixed-replace") - 1
0246         && ngx_strncasecmp(r->headers_out.content_type.data,
0247                            (u_char *) "multipart/x-mixed-replace",
0248                            sizeof("multipart/x-mixed-replace") - 1)
0249            == 0)
0250     {
0251         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
0252                       "image filter: multipart/x-mixed-replace response");
0253 
0254         return NGX_ERROR;
0255     }
0256 
0257     ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_image_filter_ctx_t));
0258     if (ctx == NULL) {
0259         return NGX_ERROR;
0260     }
0261 
0262     ngx_http_set_ctx(r, ctx, ngx_http_image_filter_module);
0263 
0264     len = r->headers_out.content_length_n;
0265 
0266     if (len != -1 && len > (off_t) conf->buffer_size) {
0267         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
0268                       "image filter: too big response: %O", len);
0269 
0270         return NGX_HTTP_UNSUPPORTED_MEDIA_TYPE;
0271     }
0272 
0273     if (len == -1) {
0274         ctx->length = conf->buffer_size;
0275 
0276     } else {
0277         ctx->length = (size_t) len;
0278     }
0279 
0280     if (r->headers_out.refresh) {
0281         r->headers_out.refresh->hash = 0;
0282     }
0283 
0284     r->main_filter_need_in_memory = 1;
0285     r->allow_ranges = 0;
0286 
0287     return NGX_OK;
0288 }
0289 
0290 
0291 static ngx_int_t
0292 ngx_http_image_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
0293 {
0294     ngx_int_t                      rc;
0295     ngx_str_t                     *ct;
0296     ngx_chain_t                    out;
0297     ngx_http_image_filter_ctx_t   *ctx;
0298     ngx_http_image_filter_conf_t  *conf;
0299 
0300     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image filter");
0301 
0302     if (in == NULL) {
0303         return ngx_http_next_body_filter(r, in);
0304     }
0305 
0306     ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
0307 
0308     if (ctx == NULL) {
0309         return ngx_http_next_body_filter(r, in);
0310     }
0311 
0312     switch (ctx->phase) {
0313 
0314     case NGX_HTTP_IMAGE_START:
0315 
0316         ctx->type = ngx_http_image_test(r, in);
0317 
0318         conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
0319 
0320         if (ctx->type == NGX_HTTP_IMAGE_NONE) {
0321 
0322             if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
0323                 out.buf = ngx_http_image_json(r, NULL);
0324 
0325                 if (out.buf) {
0326                     out.next = NULL;
0327                     ctx->phase = NGX_HTTP_IMAGE_DONE;
0328 
0329                     return ngx_http_image_send(r, ctx, &out);
0330                 }
0331             }
0332 
0333             return ngx_http_filter_finalize_request(r,
0334                                               &ngx_http_image_filter_module,
0335                                               NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
0336         }
0337 
0338         /* override content type */
0339 
0340         ct = &ngx_http_image_types[ctx->type - 1];
0341         r->headers_out.content_type_len = ct->len;
0342         r->headers_out.content_type = *ct;
0343         r->headers_out.content_type_lowcase = NULL;
0344 
0345         if (conf->filter == NGX_HTTP_IMAGE_TEST) {
0346             ctx->phase = NGX_HTTP_IMAGE_PASS;
0347 
0348             return ngx_http_image_send(r, ctx, in);
0349         }
0350 
0351         ctx->phase = NGX_HTTP_IMAGE_READ;
0352 
0353         /* fall through */
0354 
0355     case NGX_HTTP_IMAGE_READ:
0356 
0357         rc = ngx_http_image_read(r, in);
0358 
0359         if (rc == NGX_AGAIN) {
0360             return NGX_OK;
0361         }
0362 
0363         if (rc == NGX_ERROR) {
0364             return ngx_http_filter_finalize_request(r,
0365                                               &ngx_http_image_filter_module,
0366                                               NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
0367         }
0368 
0369         /* fall through */
0370 
0371     case NGX_HTTP_IMAGE_PROCESS:
0372 
0373         out.buf = ngx_http_image_process(r);
0374 
0375         if (out.buf == NULL) {
0376             return ngx_http_filter_finalize_request(r,
0377                                               &ngx_http_image_filter_module,
0378                                               NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
0379         }
0380 
0381         out.next = NULL;
0382         ctx->phase = NGX_HTTP_IMAGE_PASS;
0383 
0384         return ngx_http_image_send(r, ctx, &out);
0385 
0386     case NGX_HTTP_IMAGE_PASS:
0387 
0388         return ngx_http_next_body_filter(r, in);
0389 
0390     default: /* NGX_HTTP_IMAGE_DONE */
0391 
0392         rc = ngx_http_next_body_filter(r, NULL);
0393 
0394         /* NGX_ERROR resets any pending data */
0395         return (rc == NGX_OK) ? NGX_ERROR : rc;
0396     }
0397 }
0398 
0399 
0400 static ngx_int_t
0401 ngx_http_image_send(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx,
0402     ngx_chain_t *in)
0403 {
0404     ngx_int_t  rc;
0405 
0406     rc = ngx_http_next_header_filter(r);
0407 
0408     if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
0409         return NGX_ERROR;
0410     }
0411 
0412     rc = ngx_http_next_body_filter(r, in);
0413 
0414     if (ctx->phase == NGX_HTTP_IMAGE_DONE) {
0415         /* NGX_ERROR resets any pending data */
0416         return (rc == NGX_OK) ? NGX_ERROR : rc;
0417     }
0418 
0419     return rc;
0420 }
0421 
0422 
0423 static ngx_uint_t
0424 ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in)
0425 {
0426     u_char  *p;
0427 
0428     p = in->buf->pos;
0429 
0430     if (in->buf->last - p < 16) {
0431         return NGX_HTTP_IMAGE_NONE;
0432     }
0433 
0434     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0435                    "image filter: \"%c%c\"", p[0], p[1]);
0436 
0437     if (p[0] == 0xff && p[1] == 0xd8) {
0438 
0439         /* JPEG */
0440 
0441         return NGX_HTTP_IMAGE_JPEG;
0442 
0443     } else if (p[0] == 'G' && p[1] == 'I' && p[2] == 'F' && p[3] == '8'
0444                && p[5] == 'a')
0445     {
0446         if (p[4] == '9' || p[4] == '7') {
0447             /* GIF */
0448             return NGX_HTTP_IMAGE_GIF;
0449         }
0450 
0451     } else if (p[0] == 0x89 && p[1] == 'P' && p[2] == 'N' && p[3] == 'G'
0452                && p[4] == 0x0d && p[5] == 0x0a && p[6] == 0x1a && p[7] == 0x0a)
0453     {
0454         /* PNG */
0455 
0456         return NGX_HTTP_IMAGE_PNG;
0457 
0458     } else if (p[0] == 'R' && p[1] == 'I' && p[2] == 'F' && p[3] == 'F'
0459                && p[8] == 'W' && p[9] == 'E' && p[10] == 'B' && p[11] == 'P')
0460     {
0461         /* WebP */
0462 
0463         return NGX_HTTP_IMAGE_WEBP;
0464     }
0465 
0466     return NGX_HTTP_IMAGE_NONE;
0467 }
0468 
0469 
0470 static ngx_int_t
0471 ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in)
0472 {
0473     u_char                       *p;
0474     size_t                        size, rest;
0475     ngx_buf_t                    *b;
0476     ngx_chain_t                  *cl;
0477     ngx_http_image_filter_ctx_t  *ctx;
0478 
0479     ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
0480 
0481     if (ctx->image == NULL) {
0482         ctx->image = ngx_palloc(r->pool, ctx->length);
0483         if (ctx->image == NULL) {
0484             return NGX_ERROR;
0485         }
0486 
0487         ctx->last = ctx->image;
0488     }
0489 
0490     p = ctx->last;
0491 
0492     for (cl = in; cl; cl = cl->next) {
0493 
0494         b = cl->buf;
0495         size = b->last - b->pos;
0496 
0497         ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0498                        "image buf: %uz", size);
0499 
0500         rest = ctx->image + ctx->length - p;
0501 
0502         if (size > rest) {
0503             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
0504                           "image filter: too big response");
0505             return NGX_ERROR;
0506         }
0507 
0508         p = ngx_cpymem(p, b->pos, size);
0509         b->pos += size;
0510 
0511         if (b->last_buf) {
0512             ctx->last = p;
0513             return NGX_OK;
0514         }
0515     }
0516 
0517     ctx->last = p;
0518     r->connection->buffered |= NGX_HTTP_IMAGE_BUFFERED;
0519 
0520     return NGX_AGAIN;
0521 }
0522 
0523 
0524 static ngx_buf_t *
0525 ngx_http_image_process(ngx_http_request_t *r)
0526 {
0527     ngx_int_t                      rc;
0528     ngx_http_image_filter_ctx_t   *ctx;
0529     ngx_http_image_filter_conf_t  *conf;
0530 
0531     r->connection->buffered &= ~NGX_HTTP_IMAGE_BUFFERED;
0532 
0533     ctx = ngx_http_get_module_ctx(r, ngx_http_image_filter_module);
0534 
0535     rc = ngx_http_image_size(r, ctx);
0536 
0537     conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
0538 
0539     if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
0540         return ngx_http_image_json(r, rc == NGX_OK ? ctx : NULL);
0541     }
0542 
0543     ctx->angle = ngx_http_image_filter_get_value(r, conf->acv, conf->angle);
0544 
0545     if (conf->filter == NGX_HTTP_IMAGE_ROTATE) {
0546 
0547         if (ctx->angle != 90 && ctx->angle != 180 && ctx->angle != 270) {
0548             return NULL;
0549         }
0550 
0551         return ngx_http_image_resize(r, ctx);
0552     }
0553 
0554     ctx->max_width = ngx_http_image_filter_get_value(r, conf->wcv, conf->width);
0555     if (ctx->max_width == 0) {
0556         return NULL;
0557     }
0558 
0559     ctx->max_height = ngx_http_image_filter_get_value(r, conf->hcv,
0560                                                       conf->height);
0561     if (ctx->max_height == 0) {
0562         return NULL;
0563     }
0564 
0565     if (rc == NGX_OK
0566         && ctx->width <= ctx->max_width
0567         && ctx->height <= ctx->max_height
0568         && ctx->angle == 0
0569         && !ctx->force)
0570     {
0571         return ngx_http_image_asis(r, ctx);
0572     }
0573 
0574     return ngx_http_image_resize(r, ctx);
0575 }
0576 
0577 
0578 static ngx_buf_t *
0579 ngx_http_image_json(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
0580 {
0581     size_t      len;
0582     ngx_buf_t  *b;
0583 
0584     b = ngx_calloc_buf(r->pool);
0585     if (b == NULL) {
0586         return NULL;
0587     }
0588 
0589     b->memory = 1;
0590     b->last_buf = 1;
0591 
0592     ngx_http_clean_header(r);
0593 
0594     r->headers_out.status = NGX_HTTP_OK;
0595     r->headers_out.content_type_len = sizeof("application/json") - 1;
0596     ngx_str_set(&r->headers_out.content_type, "application/json");
0597     r->headers_out.content_type_lowcase = NULL;
0598 
0599     if (ctx == NULL) {
0600         b->pos = (u_char *) "{}" CRLF;
0601         b->last = b->pos + sizeof("{}" CRLF) - 1;
0602 
0603         ngx_http_image_length(r, b);
0604 
0605         return b;
0606     }
0607 
0608     len = sizeof("{ \"img\" : "
0609                  "{ \"width\": , \"height\": , \"type\": \"jpeg\" } }" CRLF) - 1
0610           + 2 * NGX_SIZE_T_LEN;
0611 
0612     b->pos = ngx_pnalloc(r->pool, len);
0613     if (b->pos == NULL) {
0614         return NULL;
0615     }
0616 
0617     b->last = ngx_sprintf(b->pos,
0618                           "{ \"img\" : "
0619                                        "{ \"width\": %uz,"
0620                                         " \"height\": %uz,"
0621                                         " \"type\": \"%s\" } }" CRLF,
0622                           ctx->width, ctx->height,
0623                           ngx_http_image_types[ctx->type - 1].data + 6);
0624 
0625     ngx_http_image_length(r, b);
0626 
0627     return b;
0628 }
0629 
0630 
0631 static ngx_buf_t *
0632 ngx_http_image_asis(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
0633 {
0634     ngx_buf_t  *b;
0635 
0636     b = ngx_calloc_buf(r->pool);
0637     if (b == NULL) {
0638         return NULL;
0639     }
0640 
0641     b->pos = ctx->image;
0642     b->last = ctx->last;
0643     b->memory = 1;
0644     b->last_buf = 1;
0645 
0646     ngx_http_image_length(r, b);
0647 
0648     return b;
0649 }
0650 
0651 
0652 static void
0653 ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b)
0654 {
0655     r->headers_out.content_length_n = b->last - b->pos;
0656 
0657     if (r->headers_out.content_length) {
0658         r->headers_out.content_length->hash = 0;
0659     }
0660 
0661     r->headers_out.content_length = NULL;
0662 }
0663 
0664 
0665 static ngx_int_t
0666 ngx_http_image_size(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
0667 {
0668     u_char      *p, *last;
0669     size_t       len, app;
0670     ngx_uint_t   width, height;
0671 
0672     p = ctx->image;
0673 
0674     switch (ctx->type) {
0675 
0676     case NGX_HTTP_IMAGE_JPEG:
0677 
0678         p += 2;
0679         last = ctx->image + ctx->length - 10;
0680         width = 0;
0681         height = 0;
0682         app = 0;
0683 
0684         while (p < last) {
0685 
0686             if (p[0] == 0xff && p[1] != 0xff) {
0687 
0688                 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0689                                "JPEG: %02xd %02xd", p[0], p[1]);
0690 
0691                 p++;
0692 
0693                 if ((*p == 0xc0 || *p == 0xc1 || *p == 0xc2 || *p == 0xc3
0694                      || *p == 0xc9 || *p == 0xca || *p == 0xcb)
0695                     && (width == 0 || height == 0))
0696                 {
0697                     width = p[6] * 256 + p[7];
0698                     height = p[4] * 256 + p[5];
0699                 }
0700 
0701                 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0702                                "JPEG: %02xd %02xd", p[1], p[2]);
0703 
0704                 len = p[1] * 256 + p[2];
0705 
0706                 if (*p >= 0xe1 && *p <= 0xef) {
0707                     /* application data, e.g., EXIF, Adobe XMP, etc. */
0708                     app += len;
0709                 }
0710 
0711                 p += len;
0712 
0713                 continue;
0714             }
0715 
0716             p++;
0717         }
0718 
0719         if (width == 0 || height == 0) {
0720             return NGX_DECLINED;
0721         }
0722 
0723         if (ctx->length / 20 < app) {
0724             /* force conversion if application data consume more than 5% */
0725             ctx->force = 1;
0726             ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0727                            "app data size: %uz", app);
0728         }
0729 
0730         break;
0731 
0732     case NGX_HTTP_IMAGE_GIF:
0733 
0734         if (ctx->length < 10) {
0735             return NGX_DECLINED;
0736         }
0737 
0738         width = p[7] * 256 + p[6];
0739         height = p[9] * 256 + p[8];
0740 
0741         break;
0742 
0743     case NGX_HTTP_IMAGE_PNG:
0744 
0745         if (ctx->length < 24) {
0746             return NGX_DECLINED;
0747         }
0748 
0749         width = p[18] * 256 + p[19];
0750         height = p[22] * 256 + p[23];
0751 
0752         break;
0753 
0754     case NGX_HTTP_IMAGE_WEBP:
0755 
0756         if (ctx->length < 30) {
0757             return NGX_DECLINED;
0758         }
0759 
0760         if (p[12] != 'V' || p[13] != 'P' || p[14] != '8') {
0761             return NGX_DECLINED;
0762         }
0763 
0764         switch (p[15]) {
0765 
0766         case ' ':
0767             if (p[20] & 1) {
0768                 /* not a key frame */
0769                 return NGX_DECLINED;
0770             }
0771 
0772             if (p[23] != 0x9d || p[24] != 0x01 || p[25] != 0x2a) {
0773                 /* invalid start code */
0774                 return NGX_DECLINED;
0775             }
0776 
0777             width = (p[26] | p[27] << 8) & 0x3fff;
0778             height = (p[28] | p[29] << 8) & 0x3fff;
0779 
0780             break;
0781 
0782         case 'L':
0783             if (p[20] != 0x2f) {
0784                 /* invalid signature */
0785                 return NGX_DECLINED;
0786             }
0787 
0788             width = ((p[21] | p[22] << 8) & 0x3fff) + 1;
0789             height = ((p[22] >> 6 | p[23] << 2 | p[24] << 10) & 0x3fff) + 1;
0790 
0791             break;
0792 
0793         case 'X':
0794             width = (p[24] | p[25] << 8 | p[26] << 16) + 1;
0795             height = (p[27] | p[28] << 8 | p[29] << 16) + 1;
0796             break;
0797 
0798         default:
0799             return NGX_DECLINED;
0800         }
0801 
0802         break;
0803 
0804     default:
0805 
0806         return NGX_DECLINED;
0807     }
0808 
0809     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
0810                    "image size: %d x %d", (int) width, (int) height);
0811 
0812     ctx->width = width;
0813     ctx->height = height;
0814 
0815     return NGX_OK;
0816 }
0817 
0818 
0819 static ngx_buf_t *
0820 ngx_http_image_resize(ngx_http_request_t *r, ngx_http_image_filter_ctx_t *ctx)
0821 {
0822     int                            sx, sy, dx, dy, ox, oy, ax, ay, size,
0823                                    colors, palette, transparent, sharpen,
0824                                    red, green, blue, t;
0825     u_char                        *out;
0826     ngx_buf_t                     *b;
0827     ngx_uint_t                     resize;
0828     gdImagePtr                     src, dst;
0829     ngx_pool_cleanup_t            *cln;
0830     ngx_http_image_filter_conf_t  *conf;
0831 
0832     src = ngx_http_image_source(r, ctx);
0833 
0834     if (src == NULL) {
0835         return NULL;
0836     }
0837 
0838     sx = gdImageSX(src);
0839     sy = gdImageSY(src);
0840 
0841     conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);
0842 
0843     if (!ctx->force
0844         && ctx->angle == 0
0845         && (ngx_uint_t) sx <= ctx->max_width
0846         && (ngx_uint_t) sy <= ctx->max_height)
0847     {
0848         gdImageDestroy(src);
0849         return ngx_http_image_asis(r, ctx);
0850     }
0851 
0852     colors = gdImageColorsTotal(src);
0853 
0854     if (colors && conf->transparency) {
0855         transparent = gdImageGetTransparent(src);
0856 
0857         if (transparent != -1) {
0858             palette = colors;
0859             red = gdImageRed(src, transparent);
0860             green = gdImageGreen(src, transparent);
0861             blue = gdImageBlue(src, transparent);
0862 
0863             goto transparent;
0864         }
0865     }
0866 
0867     palette = 0;
0868     transparent = -1;
0869     red = 0;
0870     green = 0;
0871     blue = 0;
0872 
0873 transparent:
0874 
0875     gdImageColorTransparent(src, -1);
0876 
0877     dx = sx;
0878     dy = sy;
0879 
0880     if (conf->filter == NGX_HTTP_IMAGE_RESIZE) {
0881 
0882         if ((ngx_uint_t) dx > ctx->max_width) {
0883             dy = dy * ctx->max_width / dx;
0884             dy = dy ? dy : 1;
0885             dx = ctx->max_width;
0886         }
0887 
0888         if ((ngx_uint_t) dy > ctx->max_height) {
0889             dx = dx * ctx->max_height / dy;
0890             dx = dx ? dx : 1;
0891             dy = ctx->max_height;
0892         }
0893 
0894         resize = 1;
0895 
0896     } else if (conf->filter == NGX_HTTP_IMAGE_ROTATE) {
0897 
0898         resize = 0;
0899 
0900     } else { /* NGX_HTTP_IMAGE_CROP */
0901 
0902         resize = 0;
0903 
0904         if ((double) dx / dy < (double) ctx->max_width / ctx->max_height) {
0905             if ((ngx_uint_t) dx > ctx->max_width) {
0906                 dy = dy * ctx->max_width / dx;
0907                 dy = dy ? dy : 1;
0908                 dx = ctx->max_width;
0909                 resize = 1;
0910             }
0911 
0912         } else {
0913             if ((ngx_uint_t) dy > ctx->max_height) {
0914                 dx = dx * ctx->max_height / dy;
0915                 dx = dx ? dx : 1;
0916                 dy = ctx->max_height;
0917                 resize = 1;
0918             }
0919         }
0920     }
0921 
0922     if (resize) {
0923         dst = ngx_http_image_new(r, dx, dy, palette);
0924         if (dst == NULL) {
0925             gdImageDestroy(src);
0926             return NULL;
0927         }
0928 
0929         if (colors == 0) {
0930             gdImageSaveAlpha(dst, 1);
0931             gdImageAlphaBlending(dst, 0);
0932         }
0933 
0934         gdImageCopyResampled(dst, src, 0, 0, 0, 0, dx, dy, sx, sy);
0935 
0936         if (colors) {
0937             gdImageTrueColorToPalette(dst, 1, 256);
0938         }
0939 
0940         gdImageDestroy(src);
0941 
0942     } else {
0943         dst = src;
0944     }
0945 
0946     if (ctx->angle) {
0947         src = dst;
0948 
0949         ax = (dx % 2 == 0) ? 1 : 0;
0950         ay = (dy % 2 == 0) ? 1 : 0;
0951 
0952         switch (ctx->angle) {
0953 
0954         case 90:
0955         case 270:
0956             dst = ngx_http_image_new(r, dy, dx, palette);
0957             if (dst == NULL) {
0958                 gdImageDestroy(src);
0959                 return NULL;
0960             }
0961             if (ctx->angle == 90) {
0962                 ox = dy / 2 + ay;
0963                 oy = dx / 2 - ax;
0964 
0965             } else {
0966                 ox = dy / 2 - ay;
0967                 oy = dx / 2 + ax;
0968             }
0969 
0970             gdImageCopyRotated(dst, src, ox, oy, 0, 0,
0971                                dx + ax, dy + ay, ctx->angle);
0972             gdImageDestroy(src);
0973 
0974             t = dx;
0975             dx = dy;
0976             dy = t;
0977             break;
0978 
0979         case 180:
0980             dst = ngx_http_image_new(r, dx, dy, palette);
0981             if (dst == NULL) {
0982                 gdImageDestroy(src);
0983                 return NULL;
0984             }
0985             gdImageCopyRotated(dst, src, dx / 2 - ax, dy / 2 - ay, 0, 0,
0986                                dx + ax, dy + ay, ctx->angle);
0987             gdImageDestroy(src);
0988             break;
0989         }
0990     }
0991 
0992     if (conf->filter == NGX_HTTP_IMAGE_CROP) {
0993 
0994         src = dst;
0995 
0996         if ((ngx_uint_t) dx > ctx->max_width) {
0997             ox = dx - ctx->max_width;
0998 
0999         } 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_calloc_buf(r->pool);
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 
1091 static gdImagePtr
1092 ngx_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 
1138 static gdImagePtr
1139 ngx_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 
1166 static u_char *
1167 ngx_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 
1230 static void
1231 ngx_http_image_cleanup(void *data)
1232 {
1233     gdFree(data);
1234 }
1235 
1236 
1237 static ngx_uint_t
1238 ngx_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 
1255 static ngx_uint_t
1256 ngx_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 
1274 static void *
1275 ngx_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 
1310 static char *
1311 ngx_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 
1371 static char *
1372 ngx_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 
1515 failed:
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 
1524 static char *
1525 ngx_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 
1571 static char *
1572 ngx_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 
1618 static char *
1619 ngx_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 
1665 static ngx_int_t
1666 ngx_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 }