├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── build ├── image_filter.patch ├── ngx_http_image_filter_module.c ├── src │ └── http │ │ └── modules │ │ └── ngx_http_image_filter_module.c └── ubuntu.sh ├── docker-compose.yml └── files ├── etc └── nginx │ ├── cdn-ips.conf │ ├── cdn │ ├── cdn-bunny.conf │ ├── cdn-bunny.py │ ├── cdn-cloudflare.conf │ ├── cdn-cloudflare.py │ ├── cdn-fastly.conf │ └── cdn-fastly.py │ ├── geoipme.conf │ ├── geolite2.conf │ ├── geolite2 │ └── geoip2-download.sh │ ├── include │ ├── assets.conf │ ├── block-exploits.inc │ ├── force-ssl.conf │ ├── generic.common │ ├── letsencrypt-acme-challenge.conf │ ├── proxy-hide-headers.common │ ├── proxy.conf │ ├── resolvers.conf │ └── ssl-ciphers.conf │ ├── mime.types │ ├── nginx.new │ └── sites-enabled │ ├── 1-geoip.conf │ └── server-conf.example ├── root └── bin │ ├── dummycert.sh │ └── my-startup.sh ├── sbin └── my_init └── usr └── share └── nginx └── html └── index.html /.dockerignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules 3 | build/src/http/ 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | .DS_Store 35 | 36 | app/ 37 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 AS buildstep 2 | ENV TERM=xterm container=docker DEBIAN_FRONTEND=noninteractive \ 3 | NGINX_DEVEL_KIT_VERSION=0.3.3 NGINX_SET_MISC_MODULE_VERSION=0.33 \ 4 | NGINX_VERSION=1.26.2 5 | ADD ./build/ /tmp/ 6 | RUN bash /tmp/ubuntu.sh 7 | 8 | 9 | FROM ubuntu:24.04 10 | LABEL maintainer="noogen " 11 | ENV TERM=xterm container=docker DEBIAN_FRONTEND=noninteractive \ 12 | NGINX_VERSION=_1.26.2-1~noble_arm64.deb 13 | 14 | COPY --from=buildstep /usr/src/nginx/nginx${NGINX_VERSION} /tmp 15 | 16 | RUN cd /tmp \ 17 | && echo "\n\n* soft nofile 800000\n* hard nofile 800000\n\n" >> /etc/security/limits.conf \ 18 | && apt-get update -y && apt-get upgrade -y --no-install-recommends --no-install-suggests \ 19 | && apt-get install -y --no-install-recommends --no-install-suggests curl gpg-agent nano \ 20 | libgd3 gettext-base unzip rsync cron apt-transport-https software-properties-common \ 21 | ca-certificates libmaxminddb0 libmaxminddb-dev mmdb-bin python3-pip git \ 22 | && dpkg --configure -a \ 23 | && touch /var/log/cron.log \ 24 | && curl -fsSL https://nginx.org/keys/nginx_signing.key | apt-key add - \ 25 | && cp /etc/apt/sources.list /etc/apt/sources.list.bak \ 26 | && echo "deb http://nginx.org/packages/ubuntu/ noble nginx" | tee -a /etc/apt/sources.list \ 27 | && echo "deb-src http://nginx.org/packages/ubuntu/ noble nginx" | tee -a /etc/apt/sources.list \ 28 | && apt-get update -y \ 29 | && dpkg -i nginx${NGINX_VERSION} \ 30 | && apt-get install --no-install-recommends --no-install-suggests -y nginx-module-njs gettext-base \ 31 | && rm -rf /etc/nginx/conf.d/default.conf \ 32 | && mkdir -p /var/log/nginx /etc/nginx/ssl \ 33 | && ln -sf /dev/stdout /var/log/nginx/access.log \ 34 | && ln -sf /dev/stderr /var/log/nginx/error.log \ 35 | && service nginx stop && update-rc.d -f nginx disable \ 36 | && pip3 install requests boto3 --break-system-packages \ 37 | && apt-get clean -y && apt-get autoclean -y \ 38 | && apt-get autoremove --purge -y \ 39 | && rm -rf /var/lib/apt/lists/* /var/lib/log/* /tmp/* /var/tmp/* 40 | 41 | ADD ./files/etc/ /etc/ 42 | ADD ./files/root/ /root/ 43 | ADD ./files/sbin/ /sbin/ 44 | 45 | RUN bash /root/bin/dummycert.sh \ 46 | && mkdir -p /app-start/etc \ 47 | && mv /etc/nginx /app-start/etc/nginx \ 48 | && rm -rf /etc/nginx \ 49 | && cd /app-start/etc/nginx/geolite2 \ 50 | && bash geoip2-download.sh \ 51 | && ln -s /app/etc/nginx /etc/nginx \ 52 | && mkdir -p /app-start/var/log \ 53 | && mv /var/log/nginx /app-start/var/log/nginx \ 54 | && rm -rf /var/log/nginx \ 55 | && ln -s /app/var/log/nginx /var/log/nginx 56 | 57 | EXPOSE 80 443 58 | 59 | VOLUME ["/app"] 60 | 61 | CMD ["/sbin/my_init"] 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 friends@niiknow.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nginx image proxy 2 | >High Performance and Low Resource Utilization Microservice 3 | 4 | image cropping with gravity, resize and compress on the fly with nginx **image_filter** module. A tiny docker container to build your own Cloudinary-like service. 5 | 6 | Nginx module - https://github.com/niiknow/docker-nginx-image-proxy/blob/master/build/ngx_http_image_filter_module.c 7 | 8 | Original File - https://github.com/niiknow/docker-nginx-image-proxy/blob/master/build/src/http/modules/ngx_http_image_filter_module.c 9 | 10 | Patch creation: `diff -u build/src/http/modules/ngx_http_image_filter_module.c build/ngx_http_image_filter_module.c > build/image_filter.patch` 11 | 12 | Patch apply with: `patch src/http/modules/ngx_http_image_filter_module.c image_filter.patch` 13 | 14 | Features: 15 | - [x] image crop offset, credit: https://github.com/bobrik/nginx_image_filter 16 | - [x] /healthcheck endpoint 17 | - [x] empty gif on other errors: 403, 404, 415, 500, 502, 503, 504 18 | - [x] convert/force output to another format, support formats: bmp, jpg, png, gif, webp, and tiff 19 | - [x] use custom ssl and saved config when you mount '/app' volume. nginx logs has also been redirect so you can backup, such as aws s3 sync. Just delete the default redirect to stdout/access.log and stderr/error.log files. 20 | - [x] support international characters in URL 21 | - [x] automatically follow redirect at origin 22 | - [x] overridable nginx config - easily add secure link or additional nginx config 23 | - [x] watermark with 24 | ```shell 25 | # file must be png 26 | image_filter_water_image /path/to/file/watermark.png; 27 | 28 | # optional watermark positioning 29 | image_filter_water_pos [ top-left | top-right | center | bottom-left | bottom-right (default)]; 30 | ``` 31 | > TIP: The implementation of watermark feature, at the moment, is a very naive one. Due to limited functionality, it add watermark after the image has been resized. It work best when resize to smaller image/thumbnail and use in combination of a smaller watermark image. We may expand on the functionality in the future, if we have time. 32 | 33 | - [x] resize support image scale (enlarge image) 34 | ```shell 35 | # optional scale max ratio, default 1 36 | # becareful not to set this too high or it will use too much memory. 37 | # 38 | # For example (during testing) a 200KB JPEG file (1024x768) will take up 4MB of memory 39 | # when loaded; but when resampled to twice (2x) of image size, the memory use jumps to 20.1MB 40 | # Mathematically make sense: 4MB * 2(Dimensions xy) = 4MB * 2 * 2 = 16MB (new) + 4MB (original) = 20MB 41 | image_filter_scale_max 3; 42 | 43 | ``` 44 | 45 | # What does this solve? 46 | You have a huge repository of images that need dynamic resize and cropping. Cloudinary can be expensive and redundant if you run your own CDN in front of this microservice. 47 | 48 | # And what it doesn't? 49 | Unlike other libraries, this does not try to do every image transformation and/or caching. 50 | 51 | 1. For more advanced features such as: animated gif, face detection, auto image optimization, and others; we recommend using Cloudinary or similar service. 52 | 2. Outside of disk cache, there is no plan for other Caching methods. We recommend putting a CDN in front, such as (MaxCDN/StackPath/KeyCDN), to provide caching and easy SSL. We love StackPath/MaxCDN EdgeRules™. 53 | 3. If you want thumbnail caching to s3, just write a lambda function and use this server to generate your thumbnail. Upload the result to s3 with the same function. 54 | 55 | # build 56 | To achieve smaller/tiny microservice, this container utilize multi-stage build introduced in Docker 17.06; therefore, Docker 17.06+ is required to build. 57 | 58 | ``` 59 | docker build -t nginx-image-proxy . 60 | ``` 61 | 62 | # run 63 | docker run -d --restart=always -p 80:80 niiknow/nginx-image-proxy 64 | --env SERVER_CONF='https://gist.githubusercontent.com/...' 65 | 66 | # web 67 | Example: http://imageproxy.yourdomain.com/rx/url-options/http://remote-host.com/image-path/image.jpg 68 | 69 | or http as protocol default: http://imageproxy.yourdomain.com/rx/url-options/remote-host.com/image-path/image.jpg 70 | 71 | Option Keys: 72 | ------------- 73 | 74 | ```yml 75 | code: name - valid values - default 76 | q: quality - 1..100 - 96 (default to best in case it's previously optimized) 77 | w: width - uint - null 78 | h: height - uint - null 79 | c: crop - null, 1 - null 80 | rz: resize - null, 1 - 1 81 | g: gravity - NorthWest, North, NorthEast, West, Center, East, SouthWest, South, SouthEast *case-sensitive* - NorthWest 82 | e: sharpen - 1..100 - 0 83 | r: rotate - 0, 90, 180, 270 - 0 84 | ofmt: bmp, jpg, jpeg, png, gif, webp - force output format 85 | ``` 86 | 87 | Options Usages: 88 | ---------------- 89 | 90 | Options are a subset of Cloudinary. It's also very flexible and customizable. 91 | 92 | * Like Cloudinary with underscore (_) as separator: OptionKey_OptionValue - g_Center, w_100, h_100 93 | * Or without any separator: OptionKeyOptionValue - gCenter, w100, h100 94 | * Or in a QueryString: ?g=Center&w=100&h=100 95 | 96 | And if that doesn't work, you can always use your custom nginx config by passing the config url into docker environment variable: SERVER_CONF 97 | 98 | # Example 99 | * Original Image - https://octodex.github.com/images/codercat.jpg - 896x896 100 | * Dynamic Height - http://imageproxy.niiknow.org/rx/50/https://octodex.github.com/images/codercat.jpg 101 | ![Dynamic Height](http://imageproxy.niiknow.org/rx/50/https://octodex.github.com/images/codercat.jpg?asdf) 102 | 103 | * Dynamic Width - http://imageproxy.niiknow.org/rx/x100/https://octodex.github.com/images/codercat.jpg 104 | ![Dynamic Width](http://imageproxy.niiknow.org/rx/x100/https://octodex.github.com/images/codercat.jpg?asdf) 105 | 106 | * Fix Width and Height, fit - http://imageproxy.niiknow.org/rx/200x500/https://static.pexels.com/photos/104827/cat-pet-animal-domestic-104827.jpeg 107 | ![Fix Width and Height - fit](http://imageproxy.niiknow.org/rx/200x500/https://static.pexels.com/photos/104827/cat-pet-animal-domestic-104827.jpeg?asdf) 108 | 109 | * Resize with rotate, sharpen - http://imageproxy.niiknow.org/rx/100,r_90,e_50/https://static.pexels.com/photos/104827/cat-pet-animal-domestic-104827.jpeg 110 | ![Resize with rotate, sharpen](http://imageproxy.niiknow.org/rx/100,r_90,e_50/https://static.pexels.com/photos/104827/cat-pet-animal-domestic-104827.jpeg?asdf) 111 | 112 | * Crop with gravity - http://imageproxy.niiknow.org/rx/100x100,c_1,g_Center/https://static.pexels.com/photos/104827/cat-pet-animal-domestic-104827.jpeg 113 | ![Crop with gravity](http://imageproxy.niiknow.org/rx/100x100,c_1,g_Center/https://static.pexels.com/photos/104827/cat-pet-animal-domestic-104827.jpeg?asdf) 114 | 115 | * Scale with watermark - http://imageproxy.niiknow.org/rx/2000,water_1/https://octodex.github.com/images/codercat.jpg 116 | ![Scale with watermark](http://imageproxy.niiknow.org/rx/2000,water_1/https://octodex.github.com/images/codercat.jpg) 117 | 118 | # December 2021 Update 119 | We're still using this project so maintenance and security update continues until further notice, but we're no longer adding new feature. We're also taking down example server so demo images above may not work in the future. 120 | 121 | # Point of Interest 122 | * [images.weserv.nl](https://github.com/weserv/images) is another great project to look at if you need additional features with image resizing. The original purpose of this library (nginx-image-proxy) is to provide high performance and low resource utilization image private microservice. We searched high and low but did not find a good solution. At that time, we saw great potential with images.weserv.nl, but was held back because it was using php. Since July 2018, it was rewritten with lua and direct c binding; as a result, it has became the next best solution and continue to improve. The authors also generiously provide free endpoint for public use. This help prove their implementation to be well battle-tested for use in any production environment. 123 | > Update, as of September 2019, images.weserv.nl was rewritten again as C++ so it has now became the better/best choice. We are now sun-setting this project, only maintenance and security support - no new feature, for the future date of December 2021 or 2 years from December 2019. 124 | 125 | # MIT 126 | -------------------------------------------------------------------------------- /build/image_filter.patch: -------------------------------------------------------------------------------- 1 | --- build/src/http/modules/ngx_http_image_filter_module.c 2019-09-24 15:35:32.000000000 -0500 2 | +++ build/ngx_http_image_filter_module.c 2022-01-11 12:32:00.000000000 -0600 3 | @@ -10,7 +10,7 @@ 4 | #include 5 | 6 | #include 7 | - 8 | +#include 9 | 10 | #define NGX_HTTP_IMAGE_OFF 0 11 | #define NGX_HTTP_IMAGE_TEST 1 12 | @@ -18,6 +18,8 @@ 13 | #define NGX_HTTP_IMAGE_RESIZE 3 14 | #define NGX_HTTP_IMAGE_CROP 4 15 | #define NGX_HTTP_IMAGE_ROTATE 5 16 | +#define NGX_HTTP_IMAGE_CROP_KEEPX 6 17 | +#define NGX_HTTP_IMAGE_CROP_KEEPY 7 18 | 19 | 20 | #define NGX_HTTP_IMAGE_START 0 21 | @@ -32,10 +34,16 @@ 22 | #define NGX_HTTP_IMAGE_GIF 2 23 | #define NGX_HTTP_IMAGE_PNG 3 24 | #define NGX_HTTP_IMAGE_WEBP 4 25 | - 26 | +#define NGX_HTTP_IMAGE_BMP 5 27 | +#define NGX_HTTP_IMAGE_TIFF 6 28 | 29 | #define NGX_HTTP_IMAGE_BUFFERED 0x08 30 | 31 | +#define NGX_HTTP_IMAGE_OFFSET_CENTER 0 32 | +#define NGX_HTTP_IMAGE_OFFSET_LEFT 1 33 | +#define NGX_HTTP_IMAGE_OFFSET_RIGHT 2 34 | +#define NGX_HTTP_IMAGE_OFFSET_TOP 3 35 | +#define NGX_HTTP_IMAGE_OFFSET_BOTTOM 4 36 | 37 | typedef struct { 38 | ngx_uint_t filter; 39 | @@ -45,12 +53,20 @@ 40 | ngx_uint_t jpeg_quality; 41 | ngx_uint_t webp_quality; 42 | ngx_uint_t sharpen; 43 | + ngx_uint_t offset_x; 44 | + ngx_uint_t offset_y; 45 | + ngx_uint_t scale_max; 46 | 47 | ngx_flag_t transparency; 48 | ngx_flag_t interlace; 49 | + ngx_str_t water_image; 50 | + ngx_str_t water_pos; 51 | + ngx_http_complex_value_t *output; 52 | 53 | ngx_http_complex_value_t *wcv; 54 | ngx_http_complex_value_t *hcv; 55 | + ngx_http_complex_value_t *oxcv; 56 | + ngx_http_complex_value_t *oycv; 57 | ngx_http_complex_value_t *acv; 58 | ngx_http_complex_value_t *jqcv; 59 | ngx_http_complex_value_t *wqcv; 60 | @@ -70,7 +86,10 @@ 61 | ngx_uint_t height; 62 | ngx_uint_t max_width; 63 | ngx_uint_t max_height; 64 | + ngx_uint_t offset_x; 65 | + ngx_uint_t offset_y; 66 | ngx_uint_t angle; 67 | + ngx_uint_t scale_max; 68 | 69 | ngx_uint_t phase; 70 | ngx_uint_t type; 71 | @@ -116,60 +135,100 @@ 72 | ngx_command_t *cmd, void *conf); 73 | static char *ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd, 74 | void *conf); 75 | +static char *ngx_http_image_filter_offset(ngx_conf_t *cf, ngx_command_t *cmd, 76 | + void *conf); 77 | static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf); 78 | - 79 | +static void ngx_http_image_watermark(ngx_http_request_t *r, 80 | + ngx_http_image_filter_conf_t *conf, gdImagePtr src); 81 | +static int my_offset(int calculatedLength, int requestedLength); 82 | +static void my_resize(gdImagePtr original, gdImagePtr destination); 83 | 84 | static ngx_command_t ngx_http_image_filter_commands[] = { 85 | 86 | { ngx_string("image_filter"), 87 | - NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123, 88 | + NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE123, 89 | ngx_http_image_filter, 90 | NGX_HTTP_LOC_CONF_OFFSET, 91 | 0, 92 | NULL }, 93 | 94 | { ngx_string("image_filter_jpeg_quality"), 95 | - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 96 | + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, 97 | ngx_http_image_filter_jpeg_quality, 98 | NGX_HTTP_LOC_CONF_OFFSET, 99 | 0, 100 | NULL }, 101 | 102 | { ngx_string("image_filter_webp_quality"), 103 | - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 104 | + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, 105 | ngx_http_image_filter_webp_quality, 106 | NGX_HTTP_LOC_CONF_OFFSET, 107 | 0, 108 | NULL }, 109 | 110 | { ngx_string("image_filter_sharpen"), 111 | - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 112 | + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, 113 | ngx_http_image_filter_sharpen, 114 | NGX_HTTP_LOC_CONF_OFFSET, 115 | 0, 116 | NULL }, 117 | 118 | { ngx_string("image_filter_transparency"), 119 | - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 120 | + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_FLAG, 121 | ngx_conf_set_flag_slot, 122 | NGX_HTTP_LOC_CONF_OFFSET, 123 | offsetof(ngx_http_image_filter_conf_t, transparency), 124 | NULL }, 125 | 126 | { ngx_string("image_filter_interlace"), 127 | - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 128 | + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_FLAG, 129 | ngx_conf_set_flag_slot, 130 | NGX_HTTP_LOC_CONF_OFFSET, 131 | offsetof(ngx_http_image_filter_conf_t, interlace), 132 | NULL }, 133 | 134 | { ngx_string("image_filter_buffer"), 135 | - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 136 | + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, 137 | ngx_conf_set_size_slot, 138 | NGX_HTTP_LOC_CONF_OFFSET, 139 | offsetof(ngx_http_image_filter_conf_t, buffer_size), 140 | NULL }, 141 | 142 | + { ngx_string("image_filter_output"), 143 | + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, 144 | + ngx_http_set_complex_value_slot, 145 | + NGX_HTTP_LOC_CONF_OFFSET, 146 | + offsetof(ngx_http_image_filter_conf_t, output), 147 | + NULL }, 148 | + 149 | + { ngx_string("image_filter_crop_offset"), 150 | + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE2, 151 | + ngx_http_image_filter_offset, 152 | + NGX_HTTP_LOC_CONF_OFFSET, 153 | + 0, 154 | + NULL }, 155 | + 156 | + { ngx_string("image_filter_scale_max"), 157 | + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, 158 | + ngx_conf_set_num_slot, 159 | + NGX_HTTP_LOC_CONF_OFFSET, 160 | + offsetof(ngx_http_image_filter_conf_t, scale_max), 161 | + NULL }, 162 | + 163 | + { ngx_string("image_filter_water_image"), 164 | + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, 165 | + ngx_conf_set_str_slot, 166 | + NGX_HTTP_LOC_CONF_OFFSET, 167 | + offsetof(ngx_http_image_filter_conf_t, water_image), 168 | + NULL }, 169 | + 170 | + { ngx_string("image_filter_water_pos"), 171 | + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, 172 | + ngx_conf_set_str_slot, 173 | + NGX_HTTP_LOC_CONF_OFFSET, 174 | + offsetof(ngx_http_image_filter_conf_t, water_pos), 175 | + NULL }, 176 | + 177 | ngx_null_command 178 | }; 179 | 180 | @@ -213,7 +272,9 @@ 181 | ngx_string("image/jpeg"), 182 | ngx_string("image/gif"), 183 | ngx_string("image/png"), 184 | - ngx_string("image/webp") 185 | + ngx_string("image/webp"), 186 | + ngx_string("image/bmp"), 187 | + ngx_string("image/tiff") 188 | }; 189 | 190 | 191 | @@ -296,6 +357,7 @@ 192 | ngx_chain_t out; 193 | ngx_http_image_filter_ctx_t *ctx; 194 | ngx_http_image_filter_conf_t *conf; 195 | + ngx_str_t ofmt = ngx_null_string; 196 | 197 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "image filter"); 198 | 199 | @@ -338,6 +400,28 @@ 200 | /* override content type */ 201 | 202 | ct = &ngx_http_image_types[ctx->type - 1]; 203 | + if (conf->output != NULL && ngx_http_complex_value(r, conf->output, &ofmt) == NGX_OK) { 204 | + if (ngx_strncmp(ofmt.data, "jpg", 3) == 0 || ngx_strncmp(ofmt.data, "jpeg", 4) == 0) { 205 | + ct = &ngx_http_image_types[NGX_HTTP_IMAGE_JPEG - 1]; 206 | + 207 | + } else if (ngx_strncmp(ofmt.data, "gif", 3) == 0){ 208 | + ct = &ngx_http_image_types[NGX_HTTP_IMAGE_GIF - 1]; 209 | + 210 | + } else if (ngx_strncmp(ofmt.data, "png", 3) == 0){ 211 | + ct = &ngx_http_image_types[NGX_HTTP_IMAGE_PNG - 1]; 212 | + 213 | + } else if (ngx_strncmp(ofmt.data, "webp", 4) == 0){ 214 | + ct = &ngx_http_image_types[NGX_HTTP_IMAGE_WEBP - 1]; 215 | + 216 | + } else if (ngx_strncmp(ofmt.data, "bmp", 3) == 0){ 217 | + ct = &ngx_http_image_types[NGX_HTTP_IMAGE_BMP - 1]; 218 | + 219 | + } else if (ngx_strncmp(ofmt.data, "tiff", 4) == 0){ 220 | + ct = &ngx_http_image_types[NGX_HTTP_IMAGE_TIFF - 1]; 221 | + 222 | + } 223 | + } 224 | + 225 | r->headers_out.content_type_len = ct->len; 226 | r->headers_out.content_type = *ct; 227 | r->headers_out.content_type_lowcase = NULL; 228 | @@ -461,6 +545,17 @@ 229 | /* WebP */ 230 | 231 | return NGX_HTTP_IMAGE_WEBP; 232 | + } else if (p[0] == 'B' && p[1] == 'M') { 233 | + /* BMP */ 234 | + 235 | + return NGX_HTTP_IMAGE_BMP; 236 | + 237 | + } else if ((p[0] == 'I' && p[1] == 'I' && p[2] == 42) || (p[0] == 'M' && p[1] == 'M' && p[3] == 42)) 238 | + { 239 | + /* TIFF */ 240 | + 241 | + return NGX_HTTP_IMAGE_TIFF; 242 | + 243 | } 244 | 245 | return NGX_HTTP_IMAGE_NONE; 246 | @@ -527,6 +622,7 @@ 247 | ngx_int_t rc; 248 | ngx_http_image_filter_ctx_t *ctx; 249 | ngx_http_image_filter_conf_t *conf; 250 | + ngx_str_t ofmt = ngx_null_string; 251 | 252 | r->connection->buffered &= ~NGX_HTTP_IMAGE_BUFFERED; 253 | 254 | @@ -536,6 +632,14 @@ 255 | 256 | conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); 257 | 258 | + /* always transform when convert */ 259 | + if (conf->output != NULL && ngx_http_complex_value(r, conf->output, &ofmt) == NGX_OK) { 260 | + if (ofmt.len > 2) { 261 | + ctx->force = 1; 262 | + } 263 | + } 264 | + 265 | + 266 | if (conf->filter == NGX_HTTP_IMAGE_SIZE) { 267 | return ngx_http_image_json(r, rc == NGX_OK ? ctx : NULL); 268 | } 269 | @@ -562,6 +666,11 @@ 270 | return NULL; 271 | } 272 | 273 | + // scale would force to resize image 274 | + if (conf->scale_max > 1) { 275 | + ctx->force = 1; 276 | + } 277 | + 278 | if (rc == NGX_OK 279 | && ctx->width <= ctx->max_width 280 | && ctx->height <= ctx->max_height 281 | @@ -801,6 +910,23 @@ 282 | 283 | break; 284 | 285 | + case NGX_HTTP_IMAGE_BMP: 286 | + if (ctx->length < 24) { 287 | + return NGX_DECLINED; 288 | + } 289 | + 290 | + width = p[18] * 256 + p[19]; 291 | + height = p[22] * 256 + p[23]; 292 | + 293 | + break; 294 | + 295 | + case NGX_HTTP_IMAGE_TIFF: 296 | + /* todo: figure out image size calculation for tiff */ 297 | + width = 0; 298 | + height = 0; 299 | + 300 | + break; 301 | + 302 | default: 303 | 304 | return NGX_DECLINED; 305 | @@ -821,8 +947,10 @@ 306 | { 307 | int sx, sy, dx, dy, ox, oy, ax, ay, size, 308 | colors, palette, transparent, sharpen, 309 | - red, green, blue, t; 310 | + red, green, blue, t, scale_max, 311 | + offset_x, offset_y; 312 | u_char *out; 313 | + double ratio, ratio_h; 314 | ngx_buf_t *b; 315 | ngx_uint_t resize; 316 | gdImagePtr src, dst; 317 | @@ -840,6 +968,9 @@ 318 | 319 | conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); 320 | 321 | + scale_max = (int) conf->scale_max; 322 | + ratio = 1; 323 | + 324 | if (!ctx->force 325 | && ctx->angle == 0 326 | && (ngx_uint_t) sx <= ctx->max_width 327 | @@ -872,6 +1003,65 @@ 328 | 329 | transparent: 330 | 331 | + if ((int)ctx->max_width > 0) { 332 | + ratio = ((double) ctx->max_width / (double) sx); 333 | + } 334 | + 335 | + if ((int)ctx->max_height > 0) { 336 | + ratio_h = ((double) ctx->max_height / (double) sy); 337 | + if (ratio_h > ratio) { 338 | + ratio = ratio_h; 339 | + } 340 | + } 341 | + 342 | + // pre-resize if using scale and required a larger image 343 | + if (scale_max > 1) { 344 | + if (ratio > 1) { 345 | + // ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "scale max = %d, %d \n", scale_max, scale_max); 346 | + // ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "scale ratio = %d, %d \n", ratio, ratio); 347 | + 348 | + if (ratio > (double) scale_max) { 349 | + ratio = (double) scale_max; 350 | + } 351 | + 352 | + /* 353 | + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "width max = %d, %d \n", ctx->max_width, ctx->max_width); 354 | + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "width img = %d, %d \n", sx, sx); 355 | + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "height max = %d, %d \n", ctx->max_height, ctx->max_height); 356 | + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "height img = %d, %d \n", sy, sy); 357 | + */ 358 | + 359 | + // ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "scale %d, %d \n", ratio, ratio); 360 | + dst = ngx_http_image_new(r, sx * ratio, sy * ratio, palette); 361 | + 362 | + if (dst == NULL) { 363 | + gdImageDestroy(src); 364 | + return NULL; 365 | + } 366 | + 367 | + if (transparent == -1) { 368 | + gdImageSaveAlpha(src, 1); 369 | + gdImageColorTransparent(src, -1); 370 | + 371 | + if(colors == 0) { 372 | + gdImageAlphaBlending(dst, 0); 373 | + gdImageSaveAlpha(dst, 1); 374 | + } else { 375 | + gdImageTrueColorToPalette(dst, 1, 256); 376 | + } 377 | + } 378 | + 379 | + // white background 380 | + // gdImageFill(dst, 0, 0, gdImageColorAllocate(dst, 255, 255, 255)); 381 | + my_resize(src, dst); 382 | + // set the new original 383 | + gdImageDestroy(src); 384 | + src = dst; 385 | + sx = gdImageSX(src); 386 | + sy = gdImageSY(src); 387 | + } 388 | + } 389 | + 390 | gdImageColorTransparent(src, -1); 391 | 392 | dx = sx; 393 | @@ -901,7 +1091,23 @@ 394 | 395 | resize = 0; 396 | 397 | - if ((double) dx / dy < (double) ctx->max_width / ctx->max_height) { 398 | + if (conf->filter == NGX_HTTP_IMAGE_CROP_KEEPX) { 399 | + if ((ngx_uint_t) dx > ctx->max_width) { 400 | + dy = dy * ctx->max_width / dx; 401 | + dy = dy ? dy : 1; 402 | + dx = ctx->max_width; 403 | + resize = 1; 404 | + } 405 | + 406 | + } else if (conf->filter == NGX_HTTP_IMAGE_CROP_KEEPY) { 407 | + if ((ngx_uint_t) dy > ctx->max_height) { 408 | + dx = dx * ctx->max_height / dy; 409 | + dx = dx ? dx : 1; 410 | + dy = ctx->max_height; 411 | + resize = 1; 412 | + } 413 | + 414 | + } else if ((double) dx / dy < (double) ctx->max_width / ctx->max_height) { 415 | if ((ngx_uint_t) dx > ctx->max_width) { 416 | dy = dy * ctx->max_width / dx; 417 | dy = dy ? dy : 1; 418 | @@ -931,7 +1137,9 @@ 419 | gdImageAlphaBlending(dst, 0); 420 | } 421 | 422 | - gdImageCopyResampled(dst, src, 0, 0, 0, 0, dx, dy, sx, sy); 423 | + // gdImageFill(dst, 0, 0, gdImageColorAllocate(dst, 255, 255, 255)); 424 | + 425 | + gdImageCopyResampled(dst, src, 0, 0, 0, 0, ceil(dx), ceil(dy), sx, sy); 426 | 427 | if (colors) { 428 | gdImageTrueColorToPalette(dst, 1, 256); 429 | @@ -989,7 +1197,9 @@ 430 | } 431 | } 432 | 433 | - if (conf->filter == NGX_HTTP_IMAGE_CROP) { 434 | + if (conf->filter == NGX_HTTP_IMAGE_CROP 435 | + || conf->filter == NGX_HTTP_IMAGE_CROP_KEEPX 436 | + || conf->filter == NGX_HTTP_IMAGE_CROP_KEEPY) { 437 | 438 | src = dst; 439 | 440 | @@ -1016,8 +1226,24 @@ 441 | return NULL; 442 | } 443 | 444 | - ox /= 2; 445 | - oy /= 2; 446 | + offset_x = ngx_http_image_filter_get_value(r, conf->oxcv, 447 | + conf->offset_x); 448 | + offset_y = ngx_http_image_filter_get_value(r, conf->oycv, 449 | + conf->offset_y); 450 | + 451 | + if (offset_x == NGX_HTTP_IMAGE_OFFSET_LEFT) { 452 | + ox = 0; 453 | + 454 | + } else if (offset_x == NGX_HTTP_IMAGE_OFFSET_CENTER) { 455 | + ox /= 2; 456 | + } 457 | + 458 | + if (offset_y == NGX_HTTP_IMAGE_OFFSET_TOP) { 459 | + oy = 0; 460 | + 461 | + } else if (offset_y == NGX_HTTP_IMAGE_OFFSET_CENTER) { 462 | + oy /= 2; 463 | + } 464 | 465 | ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 466 | "image crop: %d x %d @ %d x %d", 467 | @@ -1048,6 +1274,7 @@ 468 | } 469 | 470 | gdImageInterlace(dst, (int) conf->interlace); 471 | + ngx_http_image_watermark(r, conf, dst); 472 | 473 | out = ngx_http_image_out(r, ctx->type, dst, &size); 474 | 475 | @@ -1122,6 +1349,20 @@ 476 | #endif 477 | break; 478 | 479 | + case NGX_HTTP_IMAGE_BMP: 480 | + img = gdImageCreateFromBmpPtr(ctx->length, ctx->image); 481 | + ctx->width = img->sx; 482 | + ctx->height = img->sy; 483 | + failed = "gdImageCreateFromBmpPtr() failed"; 484 | + break; 485 | + 486 | + case NGX_HTTP_IMAGE_TIFF: 487 | + img = gdImageCreateFromTiffPtr(ctx->length, ctx->image); 488 | + ctx->width = img->sx; 489 | + ctx->height = img->sy; 490 | + failed = "gdImageCreateFromTiffPtr() failed"; 491 | + break; 492 | + 493 | default: 494 | failed = "unknown image type"; 495 | break; 496 | @@ -1171,14 +1412,37 @@ 497 | u_char *out; 498 | ngx_int_t q; 499 | ngx_http_image_filter_conf_t *conf; 500 | + ngx_str_t ofmt = ngx_null_string; 501 | 502 | out = NULL; 503 | 504 | + conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); 505 | + 506 | + if (conf->output != NULL && ngx_http_complex_value(r, conf->output, &ofmt) == NGX_OK) { 507 | + if (ngx_strncmp(ofmt.data, "jpg", 3) == 0 || ngx_strncmp(ofmt.data, "jpeg", 4) == 0){ 508 | + type = NGX_HTTP_IMAGE_JPEG; 509 | + 510 | + } else if (ngx_strncmp(ofmt.data, "gif", 3) == 0){ 511 | + type = NGX_HTTP_IMAGE_GIF; 512 | + 513 | + } else if (ngx_strncmp(ofmt.data, "png", 3) == 0){ 514 | + type = NGX_HTTP_IMAGE_PNG; 515 | + 516 | + } else if (ngx_strncmp(ofmt.data, "webp", 4) == 0){ 517 | + type = NGX_HTTP_IMAGE_WEBP; 518 | + 519 | + } else if (ngx_strncmp(ofmt.data, "bmp", 3) == 0){ 520 | + type = NGX_HTTP_IMAGE_BMP; 521 | + 522 | + } else if (ngx_strncmp(ofmt.data, "tiff", 4) == 0){ 523 | + type = NGX_HTTP_IMAGE_TIFF; 524 | + 525 | + } 526 | + } 527 | + 528 | switch (type) { 529 | 530 | case NGX_HTTP_IMAGE_JPEG: 531 | - conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); 532 | - 533 | q = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality); 534 | if (q <= 0) { 535 | return NULL; 536 | @@ -1195,13 +1459,11 @@ 537 | 538 | case NGX_HTTP_IMAGE_PNG: 539 | out = gdImagePngPtr(img, size); 540 | - failed = "gdImagePngPtr() failed"; 541 | + failed = "gdImagePngPtr() failed"; 542 | break; 543 | 544 | case NGX_HTTP_IMAGE_WEBP: 545 | #if (NGX_HAVE_GD_WEBP) 546 | - conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module); 547 | - 548 | q = ngx_http_image_filter_get_value(r, conf->wqcv, conf->webp_quality); 549 | if (q <= 0) { 550 | return NULL; 551 | @@ -1214,6 +1476,22 @@ 552 | #endif 553 | break; 554 | 555 | + case NGX_HTTP_IMAGE_BMP: 556 | + /* reuse jpeg quality value */ 557 | + q = ngx_http_image_filter_get_value(r, conf->jqcv, conf->jpeg_quality); 558 | + if (q <= 0) { 559 | + return NULL; 560 | + } 561 | + 562 | + out = gdImageBmpPtr(img, size, q); 563 | + failed = "gdImageBmpPtr() failed"; 564 | + break; 565 | + 566 | + case NGX_HTTP_IMAGE_TIFF: 567 | + out = gdImageTiffPtr(img, size); 568 | + failed = "gdImageTiffPtr() failed"; 569 | + break; 570 | + 571 | default: 572 | failed = "unknown image type"; 573 | break; 574 | @@ -1263,7 +1541,27 @@ 575 | 576 | n = ngx_atoi(value->data, value->len); 577 | 578 | - if (n > 0) { 579 | + if (n == NGX_ERROR) { 580 | + if (value->len == sizeof("left") - 1 581 | + && ngx_strncmp(value->data, "left", value->len) == 0) 582 | + { 583 | + return NGX_HTTP_IMAGE_OFFSET_LEFT; 584 | + } else if (value->len == sizeof("right") - 1 585 | + && ngx_strncmp(value->data, "right", sizeof("right") - 1) == 0) 586 | + { 587 | + return NGX_HTTP_IMAGE_OFFSET_RIGHT; 588 | + } else if (value->len == sizeof("top") - 1 589 | + && ngx_strncmp(value->data, "top", sizeof("top") - 1) == 0) 590 | + { 591 | + return NGX_HTTP_IMAGE_OFFSET_TOP; 592 | + } else if (value->len == sizeof("bottom") - 1 593 | + && ngx_strncmp(value->data, "bottom", sizeof("bottom") - 1) == 0) 594 | + { 595 | + return NGX_HTTP_IMAGE_OFFSET_BOTTOM; 596 | + } else { 597 | + return NGX_HTTP_IMAGE_OFFSET_CENTER; 598 | + } 599 | + } else if (n > 0) { 600 | return (ngx_uint_t) n; 601 | } 602 | 603 | @@ -1299,9 +1597,13 @@ 604 | conf->jpeg_quality = NGX_CONF_UNSET_UINT; 605 | conf->webp_quality = NGX_CONF_UNSET_UINT; 606 | conf->sharpen = NGX_CONF_UNSET_UINT; 607 | + conf->angle = NGX_CONF_UNSET_UINT; 608 | conf->transparency = NGX_CONF_UNSET; 609 | conf->interlace = NGX_CONF_UNSET; 610 | conf->buffer_size = NGX_CONF_UNSET_SIZE; 611 | + conf->offset_x = NGX_CONF_UNSET_UINT; 612 | + conf->offset_y = NGX_CONF_UNSET_UINT; 613 | + conf->scale_max = NGX_CONF_UNSET_UINT; 614 | 615 | return conf; 616 | } 617 | @@ -1364,6 +1666,37 @@ 618 | ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 619 | 1 * 1024 * 1024); 620 | 621 | + ngx_conf_merge_str_value(conf->water_image, prev->water_image, ""); 622 | + 623 | + ngx_conf_merge_str_value(conf->water_pos, prev->water_pos, "bottom-right"); 624 | + 625 | + if (conf->offset_x == NGX_CONF_UNSET_UINT) { 626 | + ngx_conf_merge_uint_value(conf->offset_x, prev->offset_x, 627 | + NGX_HTTP_IMAGE_OFFSET_CENTER); 628 | + 629 | + if (conf->oxcv == NULL) { 630 | + conf->oxcv = prev->oxcv; 631 | + } 632 | + } 633 | + 634 | + if (conf->offset_y == NGX_CONF_UNSET_UINT) { 635 | + ngx_conf_merge_uint_value(conf->offset_y, prev->offset_y, 636 | + NGX_HTTP_IMAGE_OFFSET_CENTER); 637 | + 638 | + if (conf->oycv == NULL) { 639 | + conf->oycv = prev->oycv; 640 | + } 641 | + } 642 | + 643 | + if (conf->scale_max == NGX_CONF_UNSET_UINT) { 644 | + /* 2 is the default max ratio */ 645 | + ngx_conf_merge_uint_value(conf->scale_max, prev->scale_max, 1); 646 | + } 647 | + 648 | + if (conf->output == NULL) { 649 | + conf->output = prev->output; 650 | + } 651 | + 652 | return NGX_CONF_OK; 653 | } 654 | 655 | @@ -1662,6 +1995,66 @@ 656 | } 657 | 658 | 659 | +static char * 660 | +ngx_http_image_filter_offset(ngx_conf_t *cf, ngx_command_t *cmd, 661 | + void *conf) 662 | +{ 663 | + ngx_http_image_filter_conf_t *imcf = conf; 664 | + 665 | + ngx_str_t *value; 666 | + ngx_http_complex_value_t cv; 667 | + ngx_http_compile_complex_value_t ccv; 668 | + 669 | + value = cf->args->elts; 670 | + 671 | + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); 672 | + 673 | + ccv.cf = cf; 674 | + ccv.value = &value[1]; 675 | + ccv.complex_value = &cv; 676 | + 677 | + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { 678 | + return NGX_CONF_ERROR; 679 | + } 680 | + 681 | + if (cv.lengths == NULL) { 682 | + imcf->offset_x = ngx_http_image_filter_value(&value[1]); 683 | + 684 | + } else { 685 | + imcf->oxcv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); 686 | + if (imcf->oxcv == NULL) { 687 | + return NGX_CONF_ERROR; 688 | + } 689 | + 690 | + *imcf->oxcv = cv; 691 | + } 692 | + 693 | + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); 694 | + 695 | + ccv.cf = cf; 696 | + ccv.value = &value[2]; 697 | + ccv.complex_value = &cv; 698 | + 699 | + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { 700 | + return NGX_CONF_ERROR; 701 | + } 702 | + 703 | + if (cv.lengths == NULL) { 704 | + imcf->offset_y = ngx_http_image_filter_value(&value[2]); 705 | + 706 | + } else { 707 | + imcf->oycv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); 708 | + if (imcf->oycv == NULL) { 709 | + return NGX_CONF_ERROR; 710 | + } 711 | + 712 | + *imcf->oycv = cv; 713 | + } 714 | + 715 | + return NGX_CONF_OK; 716 | +} 717 | + 718 | + 719 | static ngx_int_t 720 | ngx_http_image_filter_init(ngx_conf_t *cf) 721 | { 722 | @@ -1673,3 +2066,113 @@ 723 | 724 | return NGX_OK; 725 | } 726 | + 727 | + 728 | +static void 729 | +ngx_http_image_watermark(ngx_http_request_t *r, ngx_http_image_filter_conf_t *conf, gdImagePtr src) 730 | +{ 731 | + // apply watermark 732 | + if (ngx_strcmp(conf->water_image.data, "") == 0) { 733 | + return; 734 | + } 735 | + 736 | + int dx, dy; 737 | + 738 | + dx = gdImageSX(src); 739 | + dy = gdImageSY(src); 740 | + 741 | + FILE *watermark_file = fopen((const char *)conf->water_image.data, "r"); 742 | + 743 | + if (watermark_file) { 744 | + gdImagePtr watermark, watermark_mix; 745 | + ngx_int_t wdx = 0, wdy = 0; 746 | + 747 | + watermark = gdImageCreateFromPng(watermark_file); 748 | + fclose(watermark_file); 749 | + 750 | + if(watermark != NULL) { 751 | + watermark_mix = gdImageCreateTrueColor(watermark->sx, watermark->sy); 752 | + 753 | + if (ngx_strcmp(conf->water_pos.data, "top-left") == 0) { 754 | + wdx = wdy = 10; 755 | + } else if (ngx_strcmp(conf->water_pos.data, "top-right") == 0) { 756 | + wdx = dx - watermark->sx - 10; 757 | + wdy = 10; 758 | + } else if (ngx_strcmp(conf->water_pos.data, "center") == 0) { 759 | + wdx = dx / 2 - watermark->sx / 2; 760 | + wdy = dy / 2 - watermark->sy / 2; 761 | + } else if (ngx_strcmp(conf->water_pos.data, "bottom-left") == 0) { 762 | + wdx = 10; 763 | + wdy = dy - watermark->sy - 10; 764 | + } else { // default bottom-right 765 | + wdx = dx - watermark->sx - 10; 766 | + wdy = dy - watermark->sy - 10; 767 | + } 768 | + 769 | + // if watermark is larger than image set start to 0 770 | + if (wdx < 0) { 771 | + wdx = 0; 772 | + } 773 | + if (wdy < 0) { 774 | + wdy = 0; 775 | + } 776 | + 777 | + gdImageCopy(watermark_mix, src, 0, 0, wdx, wdy, watermark->sx, watermark->sy); 778 | + gdImageCopy(watermark_mix, watermark, 0, 0, 0, 0, watermark->sx, watermark->sy); 779 | + gdImageCopyMerge(src, watermark_mix, wdx, wdy, 0, 0, watermark->sx, watermark->sy, 75); 780 | + gdImageDestroy(watermark); 781 | + gdImageDestroy(watermark_mix); 782 | + } else { 783 | + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "watermark file '%s' is not PNG", conf->water_image.data); 784 | + } 785 | + } else { 786 | + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "watermark file '%s' not found", conf->water_image.data); 787 | + } 788 | +} 789 | + 790 | + 791 | +static int my_offset(int calculatedLength, int requestedLength) { 792 | + int offset = 0; 793 | + 794 | + if (calculatedLength > requestedLength) { 795 | + offset = (int)((calculatedLength - requestedLength) / 2); 796 | + } 797 | + else { 798 | + offset = (int)((requestedLength - calculatedLength) / 2); 799 | + } 800 | + 801 | + if (offset < 0) { 802 | + offset = 0; 803 | + } 804 | + 805 | + return (offset); 806 | +} 807 | + 808 | + 809 | +static void my_resize (gdImagePtr original, gdImagePtr destination) { 810 | + float originalRatio = (float)original->sx / original->sy; 811 | + float destinationRatio = (float)destination->sx / destination->sy; 812 | + 813 | + int destinationX = destination->sx; 814 | + int destinationY = destination->sy; 815 | + 816 | + if (destinationRatio > originalRatio) { 817 | + destinationX = ceil(destination->sy * originalRatio); 818 | + } 819 | + else { 820 | + destinationY = ceil(destination->sx / originalRatio); 821 | + } 822 | + 823 | + gdImageCopyResampled( 824 | + destination, 825 | + original, 826 | + my_offset(destinationX, destination->sx), 827 | + my_offset(destinationY, destination->sy), 828 | + 0, 829 | + 0, 830 | + destinationX, 831 | + destinationY, 832 | + original->sx, 833 | + original->sy 834 | + ); 835 | +} 836 | -------------------------------------------------------------------------------- /build/src/http/modules/ngx_http_image_filter_module.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Igor Sysoev 4 | * Copyright (C) Nginx, Inc. 5 | */ 6 | 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 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 | 40 | typedef 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 | 63 | typedef 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 | 81 | static ngx_int_t ngx_http_image_send(ngx_http_request_t *r, 82 | ngx_http_image_filter_ctx_t *ctx, ngx_chain_t *in); 83 | static ngx_uint_t ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in); 84 | static ngx_int_t ngx_http_image_read(ngx_http_request_t *r, ngx_chain_t *in); 85 | static ngx_buf_t *ngx_http_image_process(ngx_http_request_t *r); 86 | static ngx_buf_t *ngx_http_image_json(ngx_http_request_t *r, 87 | ngx_http_image_filter_ctx_t *ctx); 88 | static ngx_buf_t *ngx_http_image_asis(ngx_http_request_t *r, 89 | ngx_http_image_filter_ctx_t *ctx); 90 | static void ngx_http_image_length(ngx_http_request_t *r, ngx_buf_t *b); 91 | static ngx_int_t ngx_http_image_size(ngx_http_request_t *r, 92 | ngx_http_image_filter_ctx_t *ctx); 93 | 94 | static ngx_buf_t *ngx_http_image_resize(ngx_http_request_t *r, 95 | ngx_http_image_filter_ctx_t *ctx); 96 | static gdImagePtr ngx_http_image_source(ngx_http_request_t *r, 97 | ngx_http_image_filter_ctx_t *ctx); 98 | static gdImagePtr ngx_http_image_new(ngx_http_request_t *r, int w, int h, 99 | int colors); 100 | static u_char *ngx_http_image_out(ngx_http_request_t *r, ngx_uint_t type, 101 | gdImagePtr img, int *size); 102 | static void ngx_http_image_cleanup(void *data); 103 | static 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); 105 | static ngx_uint_t ngx_http_image_filter_value(ngx_str_t *value); 106 | 107 | 108 | static void *ngx_http_image_filter_create_conf(ngx_conf_t *cf); 109 | static char *ngx_http_image_filter_merge_conf(ngx_conf_t *cf, void *parent, 110 | void *child); 111 | static char *ngx_http_image_filter(ngx_conf_t *cf, ngx_command_t *cmd, 112 | void *conf); 113 | static char *ngx_http_image_filter_jpeg_quality(ngx_conf_t *cf, 114 | ngx_command_t *cmd, void *conf); 115 | static char *ngx_http_image_filter_webp_quality(ngx_conf_t *cf, 116 | ngx_command_t *cmd, void *conf); 117 | static char *ngx_http_image_filter_sharpen(ngx_conf_t *cf, ngx_command_t *cmd, 118 | void *conf); 119 | static ngx_int_t ngx_http_image_filter_init(ngx_conf_t *cf); 120 | 121 | 122 | static 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 | 177 | static 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 | 192 | ngx_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 | 208 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter; 209 | static ngx_http_output_body_filter_pt ngx_http_next_body_filter; 210 | 211 | 212 | static 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 | 220 | static ngx_int_t 221 | ngx_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 | 291 | static ngx_int_t 292 | ngx_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 | 400 | static ngx_int_t 401 | ngx_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 | 423 | static ngx_uint_t 424 | ngx_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 | 470 | static ngx_int_t 471 | ngx_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 | 524 | static ngx_buf_t * 525 | ngx_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 | 578 | static ngx_buf_t * 579 | ngx_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_calloc_buf(r->pool); 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 | 631 | static ngx_buf_t * 632 | ngx_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_calloc_buf(r->pool); 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 | 652 | static void 653 | ngx_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 | 665 | static ngx_int_t 666 | ngx_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 | 819 | static ngx_buf_t * 820 | ngx_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 | 873 | transparent: 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_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 | } 1676 | -------------------------------------------------------------------------------- /build/ubuntu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export NGINX_BUILD_DIR=/usr/src/nginx/nginx-${NGINX_VERSION} 4 | cd /tmp 5 | 6 | apt-get update 7 | apt-get install -y --no-install-recommends --no-install-suggests curl apt-transport-https \ 8 | apt-utils software-properties-common build-essential ca-certificates libssl-dev \ 9 | zlib1g zlib1g-dev dpkg-dev libpcre3 libpcre3-dev libgd-dev gpg-agent git 10 | 11 | add-apt-repository ppa:maxmind/ppa 12 | apt-get install -y libmaxminddb0 libmaxminddb-dev mmdb-bin 13 | 14 | dpkg --configure -a 15 | 16 | curl -sL "https://github.com/vision5/ngx_devel_kit/archive/v$NGINX_DEVEL_KIT_VERSION.tar.gz" -o dev-kit.tar.gz 17 | mkdir -p /usr/src/nginx/ngx_devel_kit 18 | tar -xof dev-kit.tar.gz -C /usr/src/nginx/ngx_devel_kit --strip-components=1 19 | rm dev-kit.tar.gz 20 | 21 | curl -sL "https://github.com/openresty/set-misc-nginx-module/archive/v$NGINX_SET_MISC_MODULE_VERSION.tar.gz" -o ngx-misc.tar.gz 22 | mkdir -p /usr/src/nginx/set-misc-nginx-module 23 | tar -xof ngx-misc.tar.gz -C /usr/src/nginx/set-misc-nginx-module --strip-components=1 24 | rm ngx-misc.tar.gz 25 | 26 | curl -fsSL https://nginx.org/keys/nginx_signing.key | apt-key add - 27 | cp /etc/apt/sources.list /etc/apt/sources.list.bak 28 | echo "deb http://nginx.org/packages/ubuntu/ noble nginx" | tee -a /etc/apt/sources.list 29 | echo "deb-src http://nginx.org/packages/ubuntu/ noble nginx" | tee -a /etc/apt/sources.list 30 | 31 | apt-get update && apt-get upgrade -y --no-install-recommends --no-install-suggests 32 | 33 | mkdir -p /usr/src/nginx 34 | 35 | cd /usr/src/nginx 36 | git clone https://github.com/leev/ngx_http_geoip2_module ngx_http_geoip2_module 37 | curl -sL https://github.com/leev/ngx_http_geoip2_module/archive/master.tar.gz -o ngx_http_geoip2_module.tar.gz 38 | tar zxvf ngx_http_geoip2_module.tar.gz 39 | mv ngx_http_geoip2_module-master ngx_http_geoip2_module 40 | apt-get source nginx=${NGINX_VERSION} -y 41 | 42 | pwd 43 | ls -la 44 | 45 | cd ${NGINX_BUILD_DIR} 46 | patch src/http/modules/ngx_http_image_filter_module.c /tmp/image_filter.patch 47 | 48 | sed -i "s/--with-http_ssl_module/--with-http_ssl_module --with-http_image_filter_module --add-module=\/usr\/src\/nginx\/ngx_http_geoip2_module --add-module=\/usr\/src\/nginx\/ngx_devel_kit --add-module=\/usr\/src\/nginx\/set-misc-nginx-module /g" \ 49 | ${NGINX_BUILD_DIR}/debian/rules 50 | 51 | cd /usr/src/nginx 52 | apt-get build-dep nginx -y 53 | cd ${NGINX_BUILD_DIR} 54 | dpkg-buildpackage -uc -us -b 55 | 56 | cd /usr/src/nginx 57 | pwd 58 | ls -la 59 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # docker-compose example 2 | version: "3.3" 3 | 4 | services: 5 | 6 | nginx: 7 | image: niiknow/nginx-image-proxy 8 | container_name: nginx-image-proxy 9 | ports: 10 | - "80:80" 11 | - "443:443" 12 | volumes: 13 | # - "./data:/app:rw" 14 | - "./sites-enabled:/app/etc/nginx/sites-enabled:rw" 15 | ulimits: 16 | nproc: 65535 17 | nofile: 18 | soft: 200000 19 | hard: 400000 20 | sysctls: 21 | net.core.somaxconn: '2048' 22 | labels: 23 | - "Nginx Image Proxy" 24 | restart: unless-stopped 25 | logging: 26 | options: 27 | max-size: "10m" 28 | max-file: "10" 29 | -------------------------------------------------------------------------------- /files/etc/nginx/cdn-ips.conf: -------------------------------------------------------------------------------- 1 | # ref your cdn here, example bunnycdn below 2 | include /etc/nginx/cdn/cdn-bunny.conf; 3 | 4 | # Local cache/proxy 5 | set_real_ip_from 0.0.0.0/0; 6 | set_real_ip_from ::/0; 7 | set_real_ip_from 127.0.0.1/32; 8 | set_real_ip_from 192.168.0.0/16; 9 | set_real_ip_from 10.0.0.0/8; 10 | set_real_ip_from 172.0.0.0/24; 11 | 12 | real_ip_header X-Forwarded-For; 13 | real_ip_recursive on; 14 | 15 | # map realip to use with geolite2.conf 16 | map $http_x_forwarded_for $forwarded_ip { 17 | default $http_x_forwarded_for; 18 | '' $realip_remote_addr; 19 | } 20 | 21 | # simply grab the first IP if there are multiples 22 | map $forwarded_ip $realip {"~(?[^,]*),*.*" $IP;} 23 | 24 | 25 | include /etc/nginx/geolite2.conf; -------------------------------------------------------------------------------- /files/etc/nginx/cdn/cdn-bunny.conf: -------------------------------------------------------------------------------- 1 | set_real_ip_from 89.187.188.227; 2 | set_real_ip_from 89.187.188.228; 3 | set_real_ip_from 185.93.1.241; 4 | set_real_ip_from 195.181.163.193; 5 | set_real_ip_from 89.187.162.244; 6 | set_real_ip_from 139.180.134.196; 7 | set_real_ip_from 89.38.96.158; 8 | set_real_ip_from 89.187.162.249; 9 | set_real_ip_from 89.187.162.242; 10 | set_real_ip_from 185.102.217.65; 11 | set_real_ip_from 185.93.1.243; 12 | set_real_ip_from 156.146.40.49; 13 | set_real_ip_from 185.59.220.199; 14 | set_real_ip_from 185.59.220.198; 15 | set_real_ip_from 195.181.166.158; 16 | set_real_ip_from 185.180.12.68; 17 | set_real_ip_from 138.199.24.209; 18 | set_real_ip_from 138.199.24.211; 19 | set_real_ip_from 89.187.169.3; 20 | set_real_ip_from 89.187.169.39; 21 | set_real_ip_from 89.187.169.47; 22 | set_real_ip_from 138.199.24.218; 23 | set_real_ip_from 138.199.24.219; 24 | set_real_ip_from 138.199.46.65; 25 | set_real_ip_from 185.40.106.117; 26 | set_real_ip_from 200.25.45.4; 27 | set_real_ip_from 200.25.57.5; 28 | set_real_ip_from 193.162.131.1; 29 | set_real_ip_from 200.25.11.8; 30 | set_real_ip_from 200.25.53.5; 31 | set_real_ip_from 193.162.131.1; 32 | set_real_ip_from 200.25.13.98; 33 | set_real_ip_from 41.242.2.18; 34 | set_real_ip_from 200.25.62.5; 35 | set_real_ip_from 200.25.38.69; 36 | set_real_ip_from 200.25.42.70; 37 | set_real_ip_from 193.162.131.1; 38 | set_real_ip_from 200.25.36.166; 39 | set_real_ip_from 193.162.131.1; 40 | set_real_ip_from 195.206.229.106; 41 | set_real_ip_from 194.242.11.186; 42 | set_real_ip_from 185.164.35.8; 43 | set_real_ip_from 94.20.154.22; 44 | set_real_ip_from 185.93.1.244; 45 | set_real_ip_from 156.59.145.154; 46 | set_real_ip_from 143.244.49.177; 47 | set_real_ip_from 138.199.46.66; 48 | set_real_ip_from 138.199.37.227; 49 | set_real_ip_from 138.199.37.231; 50 | set_real_ip_from 138.199.37.230; 51 | set_real_ip_from 138.199.37.229; 52 | set_real_ip_from 138.199.46.69; 53 | set_real_ip_from 138.199.46.68; 54 | set_real_ip_from 138.199.46.67; 55 | set_real_ip_from 185.93.1.246; 56 | set_real_ip_from 138.199.37.232; 57 | set_real_ip_from 195.181.163.196; 58 | set_real_ip_from 107.182.163.162; 59 | set_real_ip_from 195.181.163.195; 60 | set_real_ip_from 84.17.46.53; 61 | set_real_ip_from 212.102.40.114; 62 | set_real_ip_from 84.17.46.54; 63 | set_real_ip_from 138.199.40.58; 64 | set_real_ip_from 143.244.38.134; 65 | set_real_ip_from 143.244.38.136; 66 | set_real_ip_from 185.152.64.17; 67 | set_real_ip_from 84.17.59.115; 68 | set_real_ip_from 89.187.165.194; 69 | set_real_ip_from 138.199.15.193; 70 | set_real_ip_from 89.35.237.170; 71 | set_real_ip_from 37.19.216.130; 72 | set_real_ip_from 185.93.1.247; 73 | set_real_ip_from 185.93.3.244; 74 | set_real_ip_from 143.244.49.179; 75 | set_real_ip_from 143.244.49.180; 76 | set_real_ip_from 138.199.9.104; 77 | set_real_ip_from 185.152.66.243; 78 | set_real_ip_from 143.244.49.178; 79 | set_real_ip_from 169.150.221.147; 80 | set_real_ip_from 146.59.68.188; 81 | set_real_ip_from 200.25.18.73; 82 | set_real_ip_from 84.17.63.178; 83 | set_real_ip_from 200.25.32.131; 84 | set_real_ip_from 37.19.207.34; 85 | set_real_ip_from 208.83.234.216; 86 | set_real_ip_from 192.189.65.146; 87 | set_real_ip_from 143.244.45.177; 88 | set_real_ip_from 185.93.1.249; 89 | set_real_ip_from 185.93.1.250; 90 | set_real_ip_from 169.150.215.115; 91 | set_real_ip_from 209.177.87.197; 92 | set_real_ip_from 156.146.56.162; 93 | set_real_ip_from 156.146.56.161; 94 | set_real_ip_from 185.93.2.246; 95 | set_real_ip_from 185.93.2.245; 96 | set_real_ip_from 212.102.50.58; 97 | set_real_ip_from 212.102.40.113; 98 | set_real_ip_from 185.93.2.244; 99 | set_real_ip_from 143.244.50.82; 100 | set_real_ip_from 143.244.50.83; 101 | set_real_ip_from 156.146.56.163; 102 | set_real_ip_from 129.227.9.2; 103 | set_real_ip_from 185.135.85.154; 104 | set_real_ip_from 185.165.170.74; 105 | set_real_ip_from 129.227.217.178; 106 | set_real_ip_from 129.227.217.179; 107 | set_real_ip_from 200.25.69.94; 108 | set_real_ip_from 128.1.52.179; 109 | set_real_ip_from 200.25.16.103; 110 | set_real_ip_from 193.162.131.1; 111 | set_real_ip_from 15.235.54.226; 112 | set_real_ip_from 102.67.138.155; 113 | set_real_ip_from 156.59.126.78; 114 | set_real_ip_from 156.146.43.65; 115 | set_real_ip_from 195.181.163.203; 116 | set_real_ip_from 195.181.163.202; 117 | set_real_ip_from 156.146.56.169; 118 | set_real_ip_from 156.146.56.170; 119 | set_real_ip_from 156.146.56.166; 120 | set_real_ip_from 156.146.56.171; 121 | set_real_ip_from 169.150.207.210; 122 | set_real_ip_from 156.146.56.167; 123 | set_real_ip_from 143.244.50.84; 124 | set_real_ip_from 143.244.50.85; 125 | set_real_ip_from 143.244.50.86; 126 | set_real_ip_from 143.244.50.87; 127 | set_real_ip_from 156.146.56.168; 128 | set_real_ip_from 169.150.207.211; 129 | set_real_ip_from 212.102.50.59; 130 | set_real_ip_from 146.185.248.15; 131 | set_real_ip_from 143.244.50.90; 132 | set_real_ip_from 143.244.50.91; 133 | set_real_ip_from 143.244.50.88; 134 | set_real_ip_from 143.244.50.209; 135 | set_real_ip_from 143.244.50.213; 136 | set_real_ip_from 143.244.50.214; 137 | set_real_ip_from 143.244.49.183; 138 | set_real_ip_from 143.244.50.89; 139 | set_real_ip_from 143.244.50.210; 140 | set_real_ip_from 143.244.50.211; 141 | set_real_ip_from 143.244.50.212; 142 | set_real_ip_from 5.42.206.66; 143 | set_real_ip_from 94.46.175.183; 144 | set_real_ip_from 169.150.207.213; 145 | set_real_ip_from 169.150.207.214; 146 | set_real_ip_from 169.150.207.215; 147 | set_real_ip_from 169.150.207.212; 148 | set_real_ip_from 169.150.219.114; 149 | set_real_ip_from 169.150.202.210; 150 | set_real_ip_from 169.150.242.193; 151 | set_real_ip_from 185.93.1.251; 152 | set_real_ip_from 169.150.207.216; 153 | set_real_ip_from 169.150.207.217; 154 | set_real_ip_from 169.150.238.19; 155 | set_real_ip_from 102.219.126.20; 156 | set_real_ip_from 138.199.36.4; 157 | set_real_ip_from 138.199.36.5; 158 | set_real_ip_from 156.59.67.118; 159 | set_real_ip_from 122.10.251.130; 160 | set_real_ip_from 185.24.11.18; 161 | set_real_ip_from 138.199.36.7; 162 | set_real_ip_from 138.199.36.8; 163 | set_real_ip_from 138.199.36.9; 164 | set_real_ip_from 138.199.36.10; 165 | set_real_ip_from 138.199.36.11; 166 | set_real_ip_from 138.199.37.225; 167 | set_real_ip_from 84.17.46.49; 168 | set_real_ip_from 84.17.37.217; 169 | set_real_ip_from 169.150.225.35; 170 | set_real_ip_from 169.150.225.36; 171 | set_real_ip_from 169.150.225.37; 172 | set_real_ip_from 169.150.225.38; 173 | set_real_ip_from 169.150.225.39; 174 | set_real_ip_from 169.150.225.34; 175 | set_real_ip_from 169.150.236.97; 176 | set_real_ip_from 169.150.236.98; 177 | set_real_ip_from 169.150.236.99; 178 | set_real_ip_from 169.150.236.100; 179 | set_real_ip_from 93.189.63.149; 180 | set_real_ip_from 93.189.63.149; 181 | set_real_ip_from 143.244.56.49; 182 | set_real_ip_from 143.244.56.50; 183 | set_real_ip_from 143.244.56.51; 184 | set_real_ip_from 169.150.247.40; 185 | set_real_ip_from 169.150.247.33; 186 | set_real_ip_from 169.150.247.34; 187 | set_real_ip_from 169.150.247.35; 188 | set_real_ip_from 169.150.247.36; 189 | set_real_ip_from 169.150.247.37; 190 | set_real_ip_from 169.150.247.38; 191 | set_real_ip_from 169.150.247.39; 192 | set_real_ip_from 38.142.94.218; 193 | set_real_ip_from 87.249.137.52; 194 | set_real_ip_from 38.104.169.186; 195 | set_real_ip_from 66.181.163.74; 196 | set_real_ip_from 84.17.38.227; 197 | set_real_ip_from 84.17.38.228; 198 | set_real_ip_from 84.17.38.229; 199 | set_real_ip_from 84.17.38.230; 200 | set_real_ip_from 84.17.38.231; 201 | set_real_ip_from 84.17.38.232; 202 | set_real_ip_from 169.150.225.41; 203 | set_real_ip_from 169.150.225.42; 204 | set_real_ip_from 169.150.249.162; 205 | set_real_ip_from 169.150.249.163; 206 | set_real_ip_from 169.150.249.164; 207 | set_real_ip_from 169.150.249.165; 208 | set_real_ip_from 169.150.249.166; 209 | set_real_ip_from 169.150.249.167; 210 | set_real_ip_from 169.150.249.168; 211 | set_real_ip_from 169.150.249.169; 212 | set_real_ip_from 185.131.64.124; 213 | set_real_ip_from 185.131.64.124; 214 | set_real_ip_from 156.247.205.114; 215 | set_real_ip_from 37.236.234.2; 216 | set_real_ip_from 169.150.252.209; 217 | set_real_ip_from 212.102.46.118; 218 | set_real_ip_from 192.169.120.162; 219 | set_real_ip_from 93.180.217.214; 220 | set_real_ip_from 37.19.203.178; 221 | set_real_ip_from 107.155.47.146; 222 | set_real_ip_from 104.166.144.106; 223 | set_real_ip_from 154.47.16.177; 224 | set_real_ip_from 193.201.190.174; 225 | set_real_ip_from 156.59.95.218; 226 | set_real_ip_from 213.170.143.139; 227 | set_real_ip_from 129.227.186.154; 228 | set_real_ip_from 195.238.127.98; 229 | set_real_ip_from 200.25.22.6; 230 | set_real_ip_from 204.16.244.92; 231 | set_real_ip_from 200.25.70.101; 232 | set_real_ip_from 200.25.66.100; 233 | set_real_ip_from 139.180.209.182; 234 | set_real_ip_from 103.108.231.41; 235 | set_real_ip_from 103.108.229.5; 236 | set_real_ip_from 103.216.220.9; 237 | set_real_ip_from 103.75.11.45; 238 | set_real_ip_from 169.150.225.40; 239 | set_real_ip_from 212.102.50.49; 240 | set_real_ip_from 212.102.50.52; 241 | set_real_ip_from 109.61.83.242; 242 | set_real_ip_from 109.61.83.243; 243 | set_real_ip_from 212.102.50.50; 244 | set_real_ip_from 169.150.225.43; 245 | set_real_ip_from 45.125.247.57; 246 | set_real_ip_from 103.235.199.170; 247 | set_real_ip_from 128.1.35.170; 248 | set_real_ip_from 38.32.110.58; 249 | set_real_ip_from 169.150.220.228; 250 | set_real_ip_from 169.150.220.229; 251 | set_real_ip_from 169.150.220.230; 252 | set_real_ip_from 169.150.220.231; 253 | set_real_ip_from 138.199.4.179; 254 | set_real_ip_from 207.211.214.145; 255 | set_real_ip_from 109.61.86.193; 256 | set_real_ip_from 38.54.3.97; 257 | set_real_ip_from 103.152.98.207; 258 | set_real_ip_from 103.214.20.95; 259 | set_real_ip_from 178.175.134.51; 260 | set_real_ip_from 138.199.4.178; 261 | set_real_ip_from 172.255.253.140; 262 | set_real_ip_from 185.24.11.19; 263 | set_real_ip_from 109.61.83.244; 264 | set_real_ip_from 109.61.83.245; 265 | set_real_ip_from 84.17.38.250; 266 | set_real_ip_from 84.17.38.251; 267 | set_real_ip_from 146.59.69.202; 268 | set_real_ip_from 146.70.80.218; 269 | set_real_ip_from 154.93.50.48; 270 | set_real_ip_from 200.25.80.74; 271 | set_real_ip_from 79.127.213.214; 272 | set_real_ip_from 79.127.213.215; 273 | set_real_ip_from 79.127.213.216; 274 | set_real_ip_from 79.127.213.217; 275 | set_real_ip_from 195.69.140.112; 276 | set_real_ip_from 109.61.83.247; 277 | set_real_ip_from 109.61.83.246; 278 | set_real_ip_from 185.93.2.251; 279 | set_real_ip_from 185.93.2.248; 280 | set_real_ip_from 109.61.83.249; 281 | set_real_ip_from 109.61.83.250; 282 | set_real_ip_from 109.61.83.251; 283 | set_real_ip_from 46.199.75.115; 284 | set_real_ip_from 141.164.35.160; 285 | set_real_ip_from 109.61.83.97; 286 | set_real_ip_from 109.61.83.98; 287 | set_real_ip_from 109.61.83.99; 288 | set_real_ip_from 129.227.179.18; 289 | set_real_ip_from 185.180.14.250; 290 | set_real_ip_from 152.89.160.26; 291 | set_real_ip_from 5.189.202.62; 292 | set_real_ip_from 98.98.242.142; 293 | set_real_ip_from 156.59.92.126; 294 | set_real_ip_from 84.17.59.117; 295 | set_real_ip_from 79.127.216.66; 296 | set_real_ip_from 79.127.204.113; 297 | set_real_ip_from 79.127.237.132; 298 | set_real_ip_from 169.150.236.104; 299 | set_real_ip_from 169.150.236.105; 300 | set_real_ip_from 37.27.135.61; 301 | set_real_ip_from 158.51.123.205; 302 | set_real_ip_from 116.202.155.146; 303 | set_real_ip_from 116.202.193.178; 304 | set_real_ip_from 116.202.224.168; 305 | set_real_ip_from 188.40.126.227; 306 | set_real_ip_from 88.99.26.189; 307 | set_real_ip_from 168.119.39.238; 308 | set_real_ip_from 88.99.26.97; 309 | set_real_ip_from 168.119.12.188; 310 | set_real_ip_from 176.9.139.55; 311 | set_real_ip_from 176.9.139.94; 312 | set_real_ip_from 142.132.223.79; 313 | set_real_ip_from 142.132.223.80; 314 | set_real_ip_from 142.132.223.81; 315 | set_real_ip_from 5.161.88.97; 316 | set_real_ip_from 5.161.90.228; 317 | set_real_ip_from 5.161.85.161; 318 | set_real_ip_from 5.161.84.169; 319 | set_real_ip_from 5.161.92.86; 320 | set_real_ip_from 5.161.92.85; 321 | set_real_ip_from 5.161.92.84; 322 | set_real_ip_from 5.161.72.83; 323 | set_real_ip_from 5.161.70.244; 324 | set_real_ip_from 5.161.71.198; 325 | set_real_ip_from 5.161.49.93; 326 | set_real_ip_from 5.161.72.89; 327 | set_real_ip_from 5.161.72.135; 328 | set_real_ip_from 5.161.72.194; 329 | set_real_ip_from 5.161.72.200; 330 | set_real_ip_from 5.161.70.230; 331 | set_real_ip_from 5.161.60.80; 332 | set_real_ip_from 104.237.58.186; 333 | set_real_ip_from 143.244.50.81; 334 | set_real_ip_from 46.4.116.17; 335 | set_real_ip_from 46.4.119.81; 336 | set_real_ip_from 167.235.114.167; 337 | set_real_ip_from 159.69.68.171; 338 | set_real_ip_from 178.63.21.52; 339 | set_real_ip_from 46.4.120.152; 340 | set_real_ip_from 116.202.80.247; 341 | set_real_ip_from 5.9.71.119; 342 | set_real_ip_from 195.201.11.156; 343 | set_real_ip_from 78.46.123.17; 344 | set_real_ip_from 143.244.50.153; 345 | set_real_ip_from 138.199.9.98; 346 | set_real_ip_from 46.4.113.143; 347 | set_real_ip_from 5.161.43.226; 348 | set_real_ip_from 5.161.223.161; 349 | set_real_ip_from 5.161.89.223; 350 | set_real_ip_from 5.161.98.9; 351 | set_real_ip_from 5.161.61.85; 352 | set_real_ip_from 5.161.71.0; 353 | set_real_ip_from 136.243.2.236; 354 | set_real_ip_from 195.201.81.217; 355 | set_real_ip_from 148.251.42.123; 356 | set_real_ip_from 94.130.68.122; 357 | set_real_ip_from 88.198.22.103; 358 | set_real_ip_from 46.4.102.90; 359 | set_real_ip_from 157.90.180.205; 360 | set_real_ip_from 162.55.135.11; 361 | set_real_ip_from 195.201.109.59; 362 | set_real_ip_from 148.251.41.244; 363 | set_real_ip_from 116.202.235.16; 364 | set_real_ip_from 128.140.70.141; 365 | set_real_ip_from 143.244.50.154; 366 | set_real_ip_from 143.244.49.187; 367 | set_real_ip_from 109.248.43.116; 368 | set_real_ip_from 109.248.43.117; 369 | set_real_ip_from 109.248.43.162; 370 | set_real_ip_from 109.248.43.163; 371 | set_real_ip_from 109.248.43.164; 372 | set_real_ip_from 109.248.43.165; 373 | set_real_ip_from 49.12.71.27; 374 | set_real_ip_from 49.12.0.158; 375 | set_real_ip_from 78.47.94.156; 376 | set_real_ip_from 109.248.43.159; 377 | set_real_ip_from 109.248.43.160; 378 | set_real_ip_from 109.248.43.208; 379 | set_real_ip_from 109.248.43.179; 380 | set_real_ip_from 109.248.43.232; 381 | set_real_ip_from 109.248.43.231; 382 | set_real_ip_from 109.248.43.241; 383 | set_real_ip_from 109.248.43.236; 384 | set_real_ip_from 109.248.43.240; 385 | set_real_ip_from 116.202.118.194; 386 | set_real_ip_from 116.202.80.29; 387 | set_real_ip_from 159.69.57.80; 388 | set_real_ip_from 139.180.129.216; 389 | set_real_ip_from 139.99.174.7; 390 | set_real_ip_from 89.187.169.18; 391 | set_real_ip_from 138.199.40.50; 392 | set_real_ip_from 138.199.40.51; 393 | set_real_ip_from 138.199.9.105; 394 | set_real_ip_from 143.244.38.133; 395 | set_real_ip_from 143.244.49.181; 396 | set_real_ip_from 89.187.179.7; 397 | set_real_ip_from 84.17.35.196; 398 | set_real_ip_from 143.244.51.70; 399 | set_real_ip_from 143.244.51.71; 400 | set_real_ip_from 143.244.51.69; 401 | set_real_ip_from 212.102.43.85; 402 | set_real_ip_from 212.102.43.86; 403 | set_real_ip_from 143.244.62.213; 404 | set_real_ip_from 143.244.51.74; 405 | set_real_ip_from 185.93.3.246; 406 | set_real_ip_from 195.181.163.198; 407 | set_real_ip_from 185.152.64.19; 408 | set_real_ip_from 84.17.37.211; 409 | set_real_ip_from 212.102.50.54; 410 | set_real_ip_from 138.199.4.133; 411 | set_real_ip_from 138.199.4.132; 412 | set_real_ip_from 212.102.46.115; 413 | set_real_ip_from 84.17.35.199; 414 | set_real_ip_from 143.244.38.135; 415 | set_real_ip_from 84.17.35.218; 416 | set_real_ip_from 89.187.185.21; 417 | set_real_ip_from 169.150.238.21; 418 | set_real_ip_from 169.150.238.22; 419 | set_real_ip_from 169.150.207.51; 420 | set_real_ip_from 169.150.207.49; 421 | set_real_ip_from 84.17.38.226; 422 | set_real_ip_from 84.17.38.225; 423 | set_real_ip_from 37.19.222.248; 424 | set_real_ip_from 37.19.222.249; 425 | set_real_ip_from 169.150.247.139; 426 | set_real_ip_from 169.150.247.177; 427 | set_real_ip_from 169.150.213.49; 428 | set_real_ip_from 212.102.46.119; 429 | set_real_ip_from 169.150.247.179; 430 | set_real_ip_from 169.150.247.180; 431 | set_real_ip_from 169.150.247.181; 432 | set_real_ip_from 169.150.247.182; 433 | set_real_ip_from 109.61.89.53; 434 | set_real_ip_from 109.61.89.54; 435 | set_real_ip_from 109.61.89.55; 436 | set_real_ip_from 212.102.43.88; 437 | set_real_ip_from 89.187.169.26; 438 | set_real_ip_from 109.61.89.57; 439 | set_real_ip_from 109.61.89.58; 440 | set_real_ip_from 185.93.3.243; 441 | set_real_ip_from 138.199.33.57; 442 | 443 | set_real_ip_from 2400:52e0:1a02::625:1; 444 | set_real_ip_from 2400:52e0:1500::641:1; 445 | set_real_ip_from 2400:52e0:1500::714:1; 446 | set_real_ip_from 2400:52e0:1500::715:1; 447 | set_real_ip_from 2400:52e0:1a00::718:1; 448 | set_real_ip_from 2400:52e0:1e00::722:1; 449 | set_real_ip_from 2400:52e0:1e00::723:1; 450 | set_real_ip_from 2400:52e0:1500::747:1; 451 | set_real_ip_from 2400:52e0:1500::749:1; 452 | set_real_ip_from 2400:52e0:1500::782:1; 453 | set_real_ip_from 2400:52e0:1500::783:1; 454 | set_real_ip_from 2400:52e0:1500::784:1; 455 | set_real_ip_from 2400:52e0:1a00::845:1; 456 | set_real_ip_from 2400:52e0:1a01::852:1; 457 | set_real_ip_from 2400:52e0:1500::858:1; 458 | set_real_ip_from 2400:52e0:1e00::860:1; 459 | set_real_ip_from 2400:52e0:1e00::863:1; 460 | set_real_ip_from 2400:52e0:1e00::864:1; 461 | set_real_ip_from 2400:52e0:1e00::865:1; 462 | set_real_ip_from 2400:52e0:1500::867:1; 463 | set_real_ip_from 2400:52e0:1500::868:1; 464 | set_real_ip_from 2400:52e0:1500::869:1; 465 | set_real_ip_from 2400:52e0:1a00::871:1; 466 | set_real_ip_from 2400:52e0:1e00::874:1; 467 | set_real_ip_from 2400:52e0:1a02::876:1; 468 | set_real_ip_from 2400:52e0:1a02::878:1; 469 | set_real_ip_from 2400:52e0:1e01::879:1; 470 | set_real_ip_from 2400:52e0:1e01::883:1; 471 | set_real_ip_from 2a02:6ea0:c454::1; 472 | set_real_ip_from 2400:52e0:1e03::886:1; 473 | set_real_ip_from 2400:52e0:1e05::887:1; 474 | set_real_ip_from 2400:52e0:1e04::888:1; 475 | set_real_ip_from 2400:52e0:1e09::889:1; 476 | set_real_ip_from 2400:52e0:1a00::894:1; 477 | set_real_ip_from 2400:52e0:1e06::895:1; 478 | set_real_ip_from 2400:52e0:1a01::899:1; 479 | set_real_ip_from 2400:52e0:1a01::900:1; 480 | set_real_ip_from 2400:52e0:1a01::907:1; 481 | set_real_ip_from 2400:52e0:1a01::912:1; 482 | set_real_ip_from 2001:41d0:602:44bc::917:1; 483 | set_real_ip_from 2800:1e0:2410:1::9; 484 | set_real_ip_from 2400:52e0:1a00::940:1; 485 | set_real_ip_from 2400:52e0:1a00::941:1; 486 | set_real_ip_from 2400:52e0:1500::944:1; 487 | set_real_ip_from 2400:52e0:1500::945:1; 488 | set_real_ip_from 2400:52e0:1e02::946:1; 489 | set_real_ip_from 2400:52e0:1e02::947:1; 490 | set_real_ip_from 2400:52e0:1501::948:1; 491 | set_real_ip_from 2400:52e0:1e02::951:1; 492 | set_real_ip_from 2400:52e0:1a01::953:1; 493 | set_real_ip_from 2400:52e0:1a01::954:1; 494 | set_real_ip_from 2400:52e0:1500::955:1; 495 | set_real_ip_from 2800:1e0:2420:2::69; 496 | set_real_ip_from 2607:5300:203:a1e2::1; 497 | set_real_ip_from 2400:52e0:1a02::974:1; 498 | set_real_ip_from 2400:52e0:1a02::975:1; 499 | set_real_ip_from 2400:52e0:1a02::976:1; 500 | set_real_ip_from 2400:52e0:1500::977:1; 501 | set_real_ip_from 2400:52e0:1500::978:1; 502 | set_real_ip_from 2400:52e0:1500::979:1; 503 | set_real_ip_from 2400:52e0:1500::980:1; 504 | set_real_ip_from 2400:52e0:1500::981:1; 505 | set_real_ip_from 2400:52e0:1500::982:1; 506 | set_real_ip_from 2400:52e0:1a01::984:1; 507 | set_real_ip_from 2400:52e0:1a01::985:1; 508 | set_real_ip_from 2400:52e0:1a01::986:1; 509 | set_real_ip_from 2400:52e0:1a01::987:1; 510 | set_real_ip_from 2400:52e0:1500::988:1; 511 | set_real_ip_from 2400:52e0:1500::989:1; 512 | set_real_ip_from 2400:52e0:1501::990:1; 513 | set_real_ip_from 2400:52e0:1a01::992:1; 514 | set_real_ip_from 2400:52e0:1a01::993:1; 515 | set_real_ip_from 2400:52e0:1a01::994:1; 516 | set_real_ip_from 2400:52e0:1a01::995:1; 517 | set_real_ip_from 2400:52e0:1a01::996:1; 518 | set_real_ip_from 2400:52e0:1a01::997:1; 519 | set_real_ip_from 2400:52e0:1a01::998:1; 520 | set_real_ip_from 2400:52e0:1a01::999:1; 521 | set_real_ip_from 2400:52e0:1a01::1000:1; 522 | set_real_ip_from 2400:52e0:1a01::1001:1; 523 | set_real_ip_from 2400:52e0:1a01::1002:1; 524 | set_real_ip_from 2400:52e0:1500::1020:1; 525 | set_real_ip_from 2400:52e0:1500::1021:1; 526 | set_real_ip_from 2400:52e0:1500::1022:1; 527 | set_real_ip_from 2400:52e0:1500::1024:1; 528 | set_real_ip_from 2400:52e0:1a00::1029:1; 529 | set_real_ip_from 2400:52e0:1500::1030:1; 530 | set_real_ip_from 2400:52e0:1500::1031:1; 531 | set_real_ip_from 2400:52e0:1e08::1046:1; 532 | set_real_ip_from 2400:52e0:1e00::1047:1; 533 | set_real_ip_from 2400:52e0:1e00::1048:1; 534 | set_real_ip_from 2400:52e0:1e00::1049:1; 535 | set_real_ip_from 2400:52e0:1e00::1053:1; 536 | set_real_ip_from 2400:52e0:1e00::1054:1; 537 | set_real_ip_from 2400:52e0:1e00::1055:1; 538 | set_real_ip_from 2400:52e0:1e01::1056:1; 539 | set_real_ip_from 2400:52e0:1502::1059:1; 540 | set_real_ip_from 2400:52e0:1501::1061:1; 541 | set_real_ip_from 2400:52e0:1501::1062:1; 542 | set_real_ip_from 2400:52e0:1501::1063:1; 543 | set_real_ip_from 2400:52e0:1501::1064:1; 544 | set_real_ip_from 2400:52e0:1501::1065:1; 545 | set_real_ip_from 2400:52e0:1501::1066:1; 546 | set_real_ip_from 2400:52e0:1a00::1067:1; 547 | set_real_ip_from 2400:52e0:1a00::1068:1; 548 | set_real_ip_from 2400:52e0:1a00::1069:1; 549 | set_real_ip_from 2400:52e0:1a00::1070:1; 550 | set_real_ip_from 2400:52e0:1e02::1072:1; 551 | set_real_ip_from 2400:52e0:1e02::1073:1; 552 | set_real_ip_from 2400:52e0:1e02::1074:1; 553 | set_real_ip_from 2400:52e0:1e00::1075:1; 554 | set_real_ip_from 2400:52e0:1e00::1076:1; 555 | set_real_ip_from 2400:52e0:1e00::1077:1; 556 | set_real_ip_from 2400:52e0:1e00::1078:1; 557 | set_real_ip_from 2400:52e0:1e00::1079:1; 558 | set_real_ip_from 2400:52e0:1e00::1080:1; 559 | set_real_ip_from 2400:52e0:1e00::1081:1; 560 | set_real_ip_from 2400:52e0:1e00::1082:1; 561 | set_real_ip_from 2400:52e0:1500::1091:1; 562 | set_real_ip_from 2400:52e0:1500::1092:1; 563 | set_real_ip_from 2400:52e0:1500::1093:1; 564 | set_real_ip_from 2400:52e0:1500::1094:1; 565 | set_real_ip_from 2400:52e0:1500::1095:1; 566 | set_real_ip_from 2400:52e0:1500::1096:1; 567 | set_real_ip_from 2400:52e0:1501::1097:1; 568 | set_real_ip_from 2400:52e0:1501::1098:1; 569 | set_real_ip_from 2400:52e0:1a01::1108:1; 570 | set_real_ip_from 2400:52e0:1a01::1109:1; 571 | set_real_ip_from 2400:52e0:1a01::1110:1; 572 | set_real_ip_from 2400:52e0:1a01::1111:1; 573 | set_real_ip_from 2400:52e0:1a01::1112:1; 574 | set_real_ip_from 2400:52e0:1a01::1113:1; 575 | set_real_ip_from 2400:52e0:1a01::1114:1; 576 | set_real_ip_from 2400:52e0:1a01::1115:1; 577 | set_real_ip_from 2602:ffe4:c34:2093::ff; 578 | set_real_ip_from 2a0c:e082:11::d; 579 | set_real_ip_from 2607:fdc0:1:2d:f6ee:8ff:fe3e:a1cc; 580 | set_real_ip_from 2404:f780:0:2::d; 581 | set_real_ip_from 2404:f780:0:2::f; 582 | set_real_ip_from 2404:f780:0:2::11; 583 | set_real_ip_from 2404:f780:5:cafe::f; 584 | set_real_ip_from 2400:52e0:1501::1143:1; 585 | set_real_ip_from 2400:52e0:1501::1145:1; 586 | set_real_ip_from 2400:52e0:1501::1146:1; 587 | set_real_ip_from 2400:52e0:1501::1147:1; 588 | set_real_ip_from 2400:52e0:1501::1148:1; 589 | set_real_ip_from 2400:52e0:1501::1149:1; 590 | set_real_ip_from 2400:52e0:1501::1150:1; 591 | set_real_ip_from 2404:f780:0:2::13; 592 | set_real_ip_from 2602:ffe4:c09:106::1154; 593 | set_real_ip_from 2400:52e0:1690::1156:1; 594 | set_real_ip_from 2400:52e0:1690::1157:1; 595 | set_real_ip_from 2400:52e0:1690::1158:1; 596 | set_real_ip_from 2400:52e0:1690::1159:1; 597 | set_real_ip_from 2400:52e0:1690::1160:1; 598 | set_real_ip_from 2400:52e0:1e07::1161:1; 599 | set_real_ip_from 2a02:6ea0:f904::1163:1; 600 | set_real_ip_from 2404:f780:0:2::15; 601 | set_real_ip_from 2400:52e0:1690::1168:1; 602 | set_real_ip_from 2400:52e0:1501::1171:1; 603 | set_real_ip_from 2400:52e0:1501::1172:1; 604 | set_real_ip_from 2400:52e0:1500::1173:1; 605 | set_real_ip_from 2400:52e0:1500::1174:1; 606 | set_real_ip_from 2001:41d0:605:ca00::1175:1; 607 | set_real_ip_from 2400:52e0:1500::1179:1; 608 | set_real_ip_from 2400:52e0:1500::1180:1; 609 | set_real_ip_from 2400:52e0:1500::1181:1; 610 | set_real_ip_from 2400:52e0:1500::1182:1; 611 | set_real_ip_from 2400:52e0:1501::1184:1; 612 | set_real_ip_from 2400:52e0:1501::1185:1; 613 | set_real_ip_from 2400:52e0:1e02::1186:1; 614 | set_real_ip_from 2400:52e0:1e02::1187:1; 615 | set_real_ip_from 2400:52e0:1501::1188:1; 616 | set_real_ip_from 2400:52e0:1501::1189:1; 617 | set_real_ip_from 2400:52e0:1501::1190:1; 618 | set_real_ip_from 2401:c080:1c01:5a:ba3f:d2ff:fe0a:94b0; 619 | set_real_ip_from 2400:52e0:1501::1193:1; 620 | set_real_ip_from 2400:52e0:1501::1194:1; 621 | set_real_ip_from 2400:52e0:1501::1195:1; 622 | set_real_ip_from 2a0b:21c0:2000:208f::ff; 623 | set_real_ip_from 2602:ffe4:c54:277::ff; 624 | set_real_ip_from 2400:52e0:1e04::1202:1; 625 | set_real_ip_from 2400:52e0:1e05::1204:1; 626 | set_real_ip_from 2400:52e0:1e03::1205:1; 627 | set_real_ip_from 2400:52e0:1a00::1206:2; 628 | set_real_ip_from 2400:52e0:1a00::1207:2; 629 | set_real_ip_from 2a01:4f9:3070:3934::2; 630 | set_real_ip_from 2a04:ff07:d9:12::1; 631 | set_real_ip_from 2a04:ff07:d9:13::1; 632 | set_real_ip_from 2a04:ff07:d9:39::1; 633 | set_real_ip_from 2a04:ff07:d9:3::1; 634 | set_real_ip_from 2a04:ff07:d9:3a::1; 635 | set_real_ip_from 2a04:ff07:d9:3b::1; 636 | set_real_ip_from 2a04:ff07:d9:1::1; 637 | set_real_ip_from 2a04:ff07:d9:1b::1; 638 | set_real_ip_from 2a01:4f8:c17:aec0::1; 639 | set_real_ip_from 2a01:4f8:c17:fc7::1; 640 | set_real_ip_from 2a01:4f8:c17:20b2::1; 641 | -------------------------------------------------------------------------------- /files/etc/nginx/cdn/cdn-bunny.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | """ 3 | Util to grab BunnyCDN Inbound addresses 4 | """ 5 | 6 | import requests 7 | 8 | v4 = requests.get("https://bunnycdn.com/api/system/edgeserverlist").json() 9 | v6 = requests.get("https://bunnycdn.com/api/system/edgeserverlist/ipv6").json() 10 | config = "" 11 | 12 | for item in v4: 13 | directive = f"set_real_ip_from {item};\n" 14 | config += directive 15 | config += "\n" 16 | for item in v6: 17 | directive = f"set_real_ip_from {item};\n" 18 | config += directive 19 | 20 | with open("cdn-bunny.conf", "w")as f: 21 | f.write(config) -------------------------------------------------------------------------------- /files/etc/nginx/cdn/cdn-cloudflare.conf: -------------------------------------------------------------------------------- 1 | set_real_ip_from 173.245.48.0/20; 2 | set_real_ip_from 103.21.244.0/22; 3 | set_real_ip_from 103.22.200.0/22; 4 | set_real_ip_from 103.31.4.0/22; 5 | set_real_ip_from 141.101.64.0/18; 6 | set_real_ip_from 108.162.192.0/18; 7 | set_real_ip_from 190.93.240.0/20; 8 | set_real_ip_from 188.114.96.0/20; 9 | set_real_ip_from 197.234.240.0/22; 10 | set_real_ip_from 198.41.128.0/17; 11 | set_real_ip_from 162.158.0.0/15; 12 | set_real_ip_from 104.16.0.0/13; 13 | set_real_ip_from 104.24.0.0/14; 14 | set_real_ip_from 172.64.0.0/13; 15 | set_real_ip_from 131.0.72.0/22; 16 | 17 | set_real_ip_from 2400:cb00::/32; 18 | set_real_ip_from 2606:4700::/32; 19 | set_real_ip_from 2803:f800::/32; 20 | set_real_ip_from 2405:b500::/32; 21 | set_real_ip_from 2405:8100::/32; 22 | set_real_ip_from 2a06:98c0::/29; 23 | set_real_ip_from 2c0f:f248::/32; 24 | 25 | real_ip_header CF-Connecting-IP; -------------------------------------------------------------------------------- /files/etc/nginx/cdn/cdn-cloudflare.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | """ 3 | Util to grab Cloudflare Inbound addresses 4 | """ 5 | 6 | import requests 7 | 8 | v4 = requests.get("https://www.cloudflare.com/ips-v4").text 9 | v6 = requests.get("https://www.cloudflare.com/ips-v6").text 10 | config = "" 11 | 12 | for item in v4.split(): 13 | directive = f"set_real_ip_from {item};\n" 14 | config += directive 15 | config += "\n" 16 | for item in v6.split(): 17 | directive = f"set_real_ip_from {item};\n" 18 | config += directive 19 | 20 | bottom = "real_ip_header CF-Connecting-IP;" 21 | config += "\n" + bottom 22 | 23 | with open("cdn-cloudflare.conf", "w")as f: 24 | f.write(config) -------------------------------------------------------------------------------- /files/etc/nginx/cdn/cdn-fastly.conf: -------------------------------------------------------------------------------- 1 | set_real_ip_from 23.235.32.0/20 2 | set_real_ip_from 43.249.72.0/22 3 | set_real_ip_from 103.244.50.0/24 4 | set_real_ip_from 103.245.222.0/23 5 | set_real_ip_from 103.245.224.0/24 6 | set_real_ip_from 104.156.80.0/20 7 | set_real_ip_from 140.248.64.0/18 8 | set_real_ip_from 140.248.128.0/17 9 | set_real_ip_from 146.75.0.0/17 10 | set_real_ip_from 151.101.0.0/16 11 | set_real_ip_from 157.52.64.0/18 12 | set_real_ip_from 167.82.0.0/17 13 | set_real_ip_from 167.82.128.0/20 14 | set_real_ip_from 167.82.160.0/20 15 | set_real_ip_from 167.82.224.0/20 16 | set_real_ip_from 172.111.64.0/18 17 | set_real_ip_from 185.31.16.0/22 18 | set_real_ip_from 199.27.72.0/21 19 | set_real_ip_from 199.232.0.0/16 20 | 21 | real_ip_header Fastly-Client-IP; -------------------------------------------------------------------------------- /files/etc/nginx/cdn/cdn-fastly.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | """ 3 | Util to grab Fastly Inbound addresses 4 | """ 5 | 6 | import requests 7 | 8 | url = 'https://api.fastly.com/public-ip-list' 9 | json = requests.get(url).json() 10 | 11 | config = "set_real_ip_from " 12 | config += "\nset_real_ip_from ".join(json["addresses"]) 13 | 14 | bottom = "real_ip_header Fastly-Client-IP;" 15 | config += "\n\n" + bottom 16 | 17 | with open("cdn-fastly.conf", "w")as f: 18 | f.write(config) -------------------------------------------------------------------------------- /files/etc/nginx/geoipme.conf: -------------------------------------------------------------------------------- 1 | # use this with sites-enabled/1-geoip.conf 2 | location ~ /(geoip|geoipme)+/(?.*) { 3 | if ($ip) { 4 | set $realip $ip; 5 | } 6 | 7 | proxy_set_header X-Real-IP $realip; 8 | proxy_set_header X-Forwarded-For $realip; 9 | proxy_set_header Host "127.0.0.1"; 10 | proxy_pass "http://127.0.0.1:9081"; 11 | 12 | # disable caching for geoip 13 | add_header Cache-Control 'no-store, no-cache'; 14 | expires off; 15 | } 16 | -------------------------------------------------------------------------------- /files/etc/nginx/geolite2.conf: -------------------------------------------------------------------------------- 1 | # $realip is map inside of cdn-ips.conf 2 | 3 | geoip2 /etc/nginx/geolite2/GeoLite2-Country.mmdb { 4 | auto_reload 60m; 5 | 6 | $geoip2_metadata_country_build metadata build_epoch; 7 | $geoip2_data_country source=$realip country names en; 8 | $geoip2_data_country_code source=$realip country iso_code; 9 | } 10 | 11 | geoip2 /etc/nginx/geolite2/GeoLite2-City.mmdb { 12 | auto_reload 60m; 13 | 14 | $geoip2_continent_code source=$realip continent code; 15 | $geoip2_country source=$realip country names en; 16 | $geoip2_country_code source=$realip country iso_code; 17 | $geoip2_region source=$realip subdivisions 0 names en; 18 | $geoip2_region_code source=$realip subdivisions 0 iso_code; 19 | $geoip2_city source=$realip city names en; 20 | $geoip2_postal_code source=$realip postal code; 21 | $geoip2_latitude source=$realip location latitude; 22 | $geoip2_longitude source=$realip location longitude; 23 | $geoip2_time_zone source=$realip location time_zone; 24 | $geoip2_dma_code source=$realip location metro_code; 25 | } 26 | 27 | geoip2 /etc/nginx/geolite2/GeoLite2-ASN.mmdb { 28 | auto_reload 60m; 29 | 30 | $geoip2_asn source=$realip autonomous_system_number; 31 | $geoip2_organization source=$realip autonomous_system_organization; 32 | } 33 | -------------------------------------------------------------------------------- /files/etc/nginx/geolite2/geoip2-download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | curl -sLo GeoLite2-ASN.mmdb https://git.io/GeoLite2-ASN.mmdb 4 | curl -sLo GeoLite2-City.mmdb https://git.io/GeoLite2-City.mmdb 5 | curl -sLo GeoLite2-Country.mmdb https://git.io/GeoLite2-Country.mmdb 6 | -------------------------------------------------------------------------------- /files/etc/nginx/include/assets.conf: -------------------------------------------------------------------------------- 1 | location ~* ^.*\.(css|js|jpe?g|gif|png|woff|eot|ttf|svg|ico|css\.map|js\.map)$ { 2 | if_modified_since off; 3 | 4 | # use the public cache 5 | proxy_cache public-cache; 6 | proxy_cache_key $host$request_uri; 7 | 8 | # ignore these headers for media 9 | proxy_ignore_headers Set-Cookie Cache-Control Expires X-Accel-Expires; 10 | 11 | # cache 200s and also 404s (not ideal but there are a few 404 images for some reason) 12 | proxy_cache_valid any 30m; 13 | proxy_cache_valid 404 1m; 14 | 15 | # strip this header to avoid If-Modified-Since requests 16 | proxy_hide_header Last-Modified; 17 | proxy_hide_header Cache-Control; 18 | proxy_hide_header Vary; 19 | 20 | proxy_cache_bypass 0; 21 | proxy_no_cache 0; 22 | 23 | proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_404; 24 | proxy_connect_timeout 5s; 25 | proxy_read_timeout 45s; 26 | 27 | expires @30m; 28 | access_log off; 29 | 30 | include /etc/nginx/include/proxy.conf; 31 | } -------------------------------------------------------------------------------- /files/etc/nginx/include/block-exploits.inc: -------------------------------------------------------------------------------- 1 | ## Block SQL injections 2 | set $block_sql_injections 0; 3 | if ($query_string ~ "union.*select.*\(") { 4 | set $block_sql_injections 1; 5 | } 6 | 7 | if ($query_string ~ "union.*all.*select.*") { 8 | set $block_sql_injections 1; 9 | } 10 | 11 | if ($query_string ~ "concat.*\(") { 12 | set $block_sql_injections 1; 13 | } 14 | 15 | if ($block_sql_injections = 1) { 16 | return 403; 17 | } 18 | 19 | 20 | ## Block file injections 21 | set $block_file_injections 0; 22 | if ($query_string ~ "[a-zA-Z0-9_]=http://") { 23 | set $block_file_injections 1; 24 | } 25 | 26 | if ($query_string ~ "[a-zA-Z0-9_]=(\.\.//?)+") { 27 | set $block_file_injections 1; 28 | } 29 | 30 | if ($query_string ~ "[a-zA-Z0-9_]=/([a-z0-9_.]//?)+") { 31 | set $block_file_injections 1; 32 | } 33 | 34 | if ($block_file_injections = 1) { 35 | return 403; 36 | } 37 | 38 | 39 | ## Block common exploits 40 | set $block_common_exploits 0; 41 | if ($query_string ~ "(<|%3C).*script.*(>|%3E)") { 42 | set $block_common_exploits 1; 43 | } 44 | 45 | if ($query_string ~ "GLOBALS(=|\[|\%[0-9A-Z]{0,2})") { 46 | set $block_common_exploits 1; 47 | } 48 | 49 | if ($query_string ~ "_REQUEST(=|\[|\%[0-9A-Z]{0,2})") { 50 | set $block_common_exploits 1; 51 | } 52 | 53 | if ($query_string ~ "proc/self/environ") { 54 | set $block_common_exploits 1; 55 | } 56 | 57 | if ($query_string ~ "mosConfig_[a-zA-Z_]{1,21}(=|\%3D)") { 58 | set $block_common_exploits 1; 59 | } 60 | 61 | if ($query_string ~ "base64_(en|de)code\(.*\)") { 62 | set $block_common_exploits 1; 63 | } 64 | 65 | if ($block_common_exploits = 1) { 66 | return 403; 67 | } 68 | 69 | 70 | ## Block spam 71 | set $block_spam 0; 72 | if ($query_string ~ "\b(ultram|unicauca|valium|viagra|vicodin|xanax|ypxaieo)\b") { 73 | set $block_spam 1; 74 | } 75 | 76 | if ($query_string ~ "\b(erections|hoodia|huronriveracres|impotence|levitra|libido)\b") { 77 | set $block_spam 1; 78 | } 79 | 80 | if ($query_string ~ "\b(ambien|blue\spill|cialis|cocaine|ejaculation|erectile)\b") { 81 | set $block_spam 1; 82 | } 83 | 84 | if ($query_string ~ "\b(lipitor|phentermin|pro[sz]ac|sandyauer|tramadol|troyhamby)\b") { 85 | set $block_spam 1; 86 | } 87 | 88 | if ($block_spam = 1) { 89 | return 403; 90 | } 91 | 92 | 93 | ## Block user agents 94 | set $block_user_agents 0; 95 | 96 | 97 | # Disable Akeeba Remote Control 2.5 and earlier 98 | if ($http_user_agent ~ "Indy Library") { 99 | set $block_user_agents 1; 100 | } 101 | 102 | # Common bandwidth hoggers and hacking tools. 103 | if ($http_user_agent ~ "libwww-perl") { 104 | set $block_user_agents 1; 105 | } 106 | 107 | if ($http_user_agent ~ "GetRight") { 108 | set $block_user_agents 1; 109 | } 110 | 111 | if ($http_user_agent ~ "GetWeb!") { 112 | set $block_user_agents 1; 113 | } 114 | 115 | if ($http_user_agent ~ "Go!Zilla") { 116 | set $block_user_agents 1; 117 | } 118 | 119 | if ($http_user_agent ~ "Download Demon") { 120 | set $block_user_agents 1; 121 | } 122 | 123 | if ($http_user_agent ~ "Go-Ahead-Got-It") { 124 | set $block_user_agents 1; 125 | } 126 | 127 | if ($http_user_agent ~ "TurnitinBot") { 128 | set $block_user_agents 1; 129 | } 130 | 131 | if ($http_user_agent ~ "GrabNet") { 132 | set $block_user_agents 1; 133 | } 134 | 135 | if ($block_user_agents = 1) { 136 | return 403; 137 | } 138 | 139 | -------------------------------------------------------------------------------- /files/etc/nginx/include/force-ssl.conf: -------------------------------------------------------------------------------- 1 | if ($scheme = "http") { 2 | return 301 https://$host$request_uri; 3 | } -------------------------------------------------------------------------------- /files/etc/nginx/include/generic.common: -------------------------------------------------------------------------------- 1 | include /etc/nginx/include/proxy-hide-headers.common; 2 | include /etc/nginx/include/block-exploits.inc; 3 | 4 | resolver 8.8.8.8 8.8.4.4 ipv6=off; 5 | 6 | add_header Access-Control-Allow-Origin *; 7 | proxy_set_header X-Forwarded-For $realip; 8 | proxy_ssl_verify off; 9 | proxy_ssl_server_name on; 10 | 11 | # Disable .htaccess and other hidden files 12 | location ~ /\.(?!well-known).* { 13 | deny all; 14 | access_log off; 15 | log_not_found off; 16 | return 444; 17 | } 18 | 19 | location /robots.txt { 20 | access_log off; 21 | default_type text/plain; 22 | return 200 "User-agent: *\nDisallow: /\n"; 23 | } 24 | 25 | location /healthcheck { 26 | access_log off; 27 | default_type text/plain; 28 | return 200 "OK"; 29 | } 30 | -------------------------------------------------------------------------------- /files/etc/nginx/include/letsencrypt-acme-challenge.conf: -------------------------------------------------------------------------------- 1 | # Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx) 2 | # We use ^~ here, so that we don't check other regexes (for speed-up). We actually MUST cancel 3 | # other regex checks, because in our other config files have regex rule that denies access to files with dotted names. 4 | location ^~ /.well-known/acme-challenge/ { 5 | # Since this is for letsencrypt authentication of a domain and they do not give IP ranges of their infrastructure 6 | # we need to open up access by turning off auth and IP ACL for this location. 7 | auth_basic off; 8 | auth_request off; 9 | allow all; 10 | 11 | # Set correct content type. According to this: 12 | # https://community.letsencrypt.org/t/using-the-webroot-domain-verification-method/1445/29 13 | # Current specification requires "text/plain" or no content header at all. 14 | # It seems that "text/plain" is a safe option. 15 | default_type "text/plain"; 16 | 17 | # This directory must be the same as in /etc/letsencrypt/cli.ini 18 | # as "webroot-path" parameter. Also don't forget to set "authenticator" parameter 19 | # there to "webroot". 20 | # Do NOT use alias, use root! Target directory is located here: 21 | # /var/www/common/letsencrypt/.well-known/acme-challenge/ 22 | root /app/letsencrypt-acme-challenge; 23 | } 24 | 25 | # Hide /acme-challenge subdirectory and return 404 on all requests. 26 | # It is somewhat more secure than letting Nginx return 403. 27 | # Ending slash is important! 28 | location = /.well-known/acme-challenge/ { 29 | return 404; 30 | } -------------------------------------------------------------------------------- /files/etc/nginx/include/proxy-hide-headers.common: -------------------------------------------------------------------------------- 1 | proxy_hide_header "X-Cache"; 2 | proxy_hide_header "Last-Modified"; 3 | proxy_hide_header "ETag"; 4 | proxy_hide_header "Content-Disposition"; 5 | proxy_hide_header "X-Amz-Id"; 6 | proxy_hide_header "X-Amz-Id-2"; 7 | proxy_hide_header "x-amz-meta-s3fox-filesize"; 8 | proxy_hide_header "x-amz-meta-s3fox-modifiedtime"; 9 | proxy_hide_header "X-Amz-Request-Id"; 10 | proxy_hide_header "X-Amz-Replication-Status"; 11 | proxy_hide_header "X-Amz-Expiration"; 12 | proxy_hide_header "X-Amz-Version-Id"; 13 | proxy_hide_header "X-Amz-Cf-Id"; 14 | proxy_hide_header "X-Amz-Cf-Pop"; 15 | proxy_hide_header "Via"; 16 | proxy_hide_header "Access-Control-Allow-Origin"; 17 | proxy_hide_header "x-amz-meta-s3b-last-modified"; 18 | proxy_hide_header "Set-Cookie"; 19 | proxy_hide_header "CF-Cache-Status"; 20 | proxy_hide_header "cf-ray"; 21 | proxy_hide_header "Server"; 22 | proxy_hide_header "X-Powered-By"; 23 | proxy_hide_header "X-AspNet-Version"; 24 | -------------------------------------------------------------------------------- /files/etc/nginx/include/proxy.conf: -------------------------------------------------------------------------------- 1 | add_header X-Served-By $host; 2 | proxy_set_header Host $host; 3 | proxy_set_header X-Forwarded-Scheme $scheme; 4 | proxy_set_header X-Forwarded-Proto $scheme; 5 | proxy_set_header X-Forwarded-For $remote_addr; 6 | proxy_set_header X-Real-IP $remote_addr; 7 | proxy_pass $forward_scheme://$server:$port$request_uri; 8 | -------------------------------------------------------------------------------- /files/etc/nginx/include/resolvers.conf: -------------------------------------------------------------------------------- 1 | # default resolver 2 | resolver 8.8.8.8 8.8.4.4 ipv6=off; -------------------------------------------------------------------------------- /files/etc/nginx/include/ssl-ciphers.conf: -------------------------------------------------------------------------------- 1 | ssl_session_timeout 5m; 2 | ssl_session_cache shared:SSL:50m; 3 | ssl_prefer_server_ciphers off; 4 | 5 | # intermediate configuration. tweak to your needs. 6 | ssl_protocols TLSv1.2 TLSv1.3; 7 | ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; 8 | -------------------------------------------------------------------------------- /files/etc/nginx/mime.types: -------------------------------------------------------------------------------- 1 | types { 2 | text/html html htm shtml; 3 | text/css css; 4 | text/xml xml; 5 | image/gif gif; 6 | image/jpeg jpeg jpg; 7 | application/javascript js; 8 | application/atom+xml atom; 9 | application/rss+xml rss; 10 | 11 | text/mathml mml; 12 | text/plain txt; 13 | text/vnd.sun.j2me.app-descriptor jad; 14 | text/vnd.wap.wml wml; 15 | text/x-component htc; 16 | 17 | image/png png; 18 | image/svg+xml svg svgz; 19 | image/tiff tif tiff; 20 | image/vnd.wap.wbmp wbmp; 21 | image/webp webp; 22 | image/x-icon ico; 23 | image/x-jng jng; 24 | image/x-ms-bmp bmp; 25 | 26 | font/woff woff; 27 | font/woff2 woff2; 28 | 29 | application/java-archive jar war ear; 30 | application/json json; 31 | application/mac-binhex40 hqx; 32 | application/msword doc; 33 | application/pdf pdf; 34 | application/postscript ps eps ai; 35 | application/rtf rtf; 36 | application/vnd.apple.mpegurl m3u8; 37 | application/vnd.google-earth.kml+xml kml; 38 | application/vnd.google-earth.kmz kmz; 39 | application/vnd.ms-excel xls; 40 | application/vnd.ms-fontobject eot; 41 | application/vnd.ms-powerpoint ppt; 42 | application/vnd.oasis.opendocument.graphics odg; 43 | application/vnd.oasis.opendocument.presentation odp; 44 | application/vnd.oasis.opendocument.spreadsheet ods; 45 | application/vnd.oasis.opendocument.text odt; 46 | application/vnd.openxmlformats-officedocument.presentationml.presentation 47 | pptx; 48 | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet 49 | xlsx; 50 | application/vnd.openxmlformats-officedocument.wordprocessingml.document 51 | docx; 52 | application/vnd.wap.wmlc wmlc; 53 | application/x-7z-compressed 7z; 54 | application/x-cocoa cco; 55 | application/x-java-archive-diff jardiff; 56 | application/x-java-jnlp-file jnlp; 57 | application/x-makeself run; 58 | application/x-perl pl pm; 59 | application/x-pilot prc pdb; 60 | application/x-rar-compressed rar; 61 | application/x-redhat-package-manager rpm; 62 | application/x-sea sea; 63 | application/x-shockwave-flash swf; 64 | application/x-stuffit sit; 65 | application/x-tcl tcl tk; 66 | application/x-x509-ca-cert der pem crt; 67 | application/x-xpinstall xpi; 68 | application/xhtml+xml xhtml; 69 | application/xspf+xml xspf; 70 | application/zip zip; 71 | 72 | application/octet-stream bin exe dll; 73 | application/octet-stream deb; 74 | application/octet-stream dmg; 75 | application/octet-stream iso img; 76 | application/octet-stream msi msp msm; 77 | 78 | audio/midi mid midi kar; 79 | audio/mpeg mp3; 80 | audio/ogg ogg; 81 | audio/x-m4a m4a; 82 | audio/x-realaudio ra; 83 | 84 | video/3gpp 3gpp 3gp; 85 | video/mp2t ts; 86 | video/mp4 mp4; 87 | video/mpeg mpeg mpg; 88 | video/quicktime mov; 89 | video/webm webm; 90 | video/x-flv flv; 91 | video/x-m4v m4v; 92 | video/x-mng mng; 93 | video/x-ms-asf asx asf; 94 | video/x-ms-wmv wmv; 95 | video/x-msvideo avi; 96 | } 97 | -------------------------------------------------------------------------------- /files/etc/nginx/nginx.new: -------------------------------------------------------------------------------- 1 | # Server globals 2 | user www-data; 3 | worker_processes auto; 4 | worker_rlimit_nofile 65535; 5 | error_log /var/log/nginx/error.log warn; 6 | pid /var/run/nginx.pid; 7 | 8 | 9 | # Worker config 10 | events { 11 | worker_connections 8192; 12 | use epoll; 13 | multi_accept on; 14 | } 15 | 16 | # Includes files with directives to load dynamic modules. 17 | include /etc/nginx/modules/*.conf; 18 | 19 | http { 20 | include /etc/nginx/mime.types; 21 | default_type application/octet-stream; 22 | sendfile on; 23 | server_tokens off; 24 | tcp_nopush on; 25 | tcp_nodelay on; 26 | client_body_temp_path /tmp/nginx/body 1 2; 27 | keepalive_timeout 90s; 28 | proxy_connect_timeout 90s; 29 | proxy_send_timeout 90s; 30 | proxy_read_timeout 90s; 31 | proxy_ignore_client_abort off; 32 | gzip on; 33 | client_max_body_size 2000m; 34 | server_names_hash_bucket_size 1024; 35 | proxy_http_version 1.1; 36 | proxy_set_header X-Forwarded-Scheme $scheme; 37 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 38 | proxy_set_header Accept-Encoding ""; 39 | proxy_cache off; 40 | proxy_cache_path /var/cache/nginx/public levels=1:2 keys_zone=public-cache:30m max_size=192m; 41 | proxy_cache_path /var/cache/nginx/private levels=1:2 keys_zone=private-cache:5m max_size=1024m; 42 | 43 | # Log formats 44 | # 45 | # # predefined combined format as example 46 | # log_format combined '$remote_addr - $remote_user [$time_local] ' 47 | # '"$request" $status $body_bytes_sent ' 48 | # '"$http_referer" "$http_user_agent"'; 49 | 50 | log_format main '[$time_iso8601] $remote_addr - $realip_remote_addr - $remote_user - $server_name ' 51 | '$host "$request" $status $body_bytes_sent "$http_referer" ' 52 | '"$http_user_agent" "$http_x_forwarded_for" "upstream - $upstream_addr"'; 53 | 54 | 55 | access_log /var/log/nginx/access.log main; 56 | 57 | # Dynamically generated resolvers file 58 | include /etc/nginx/include/resolvers.conf; 59 | 60 | # Default upstream scheme 61 | map $host $forward_scheme { 62 | default http; 63 | } 64 | 65 | include /etc/nginx/cdn-ips.conf; 66 | 67 | # Wildcard include 68 | include /etc/nginx/sites-enabled/*.conf; 69 | } 70 | -------------------------------------------------------------------------------- /files/etc/nginx/sites-enabled/1-geoip.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 127.0.0.1:9081; 3 | resolver 8.8.8.8 8.8.4.4 ipv6=off; 4 | access_log off; 5 | 6 | location / { 7 | charset utf-8; 8 | add_header Cache-Control no-cache; 9 | add_header access-control-allow-methods "OPTIONS,GET"; 10 | add_header access-control-allow-credentials "true"; 11 | add_header access-control-allow-headers "authorization,x-csrf-token,x-requested-with"; 12 | 13 | default_type application/json; 14 | set $mybody '{"ip":"$realip","country_code":"$geoip2_country_code","region_code":"$geoip2_region_code","region_name":"$geoip2_region","city":"$geoip2_city","zip_code":"$geoip2_postal_code","latitude":"$geoip2_latitude","longitude":"$geoip2_longitude","metro_code":"$geoip2_dma_code"}'; 15 | 16 | if ($arg_format = "xml") { 17 | add_header Content-Type application/xml; 18 | 19 | set $mybody '$realip$geoip2_country_code$geoip2_country$geoip2_region_code$geoip2_region$geoip2_city$geoip2_postal_code$geoip2_latitude$geoip2_longitude$geoip2_dma_code'; 20 | } 21 | 22 | if ($arg_format = "csv") { 23 | add_header Content-Type text/csv; 24 | set $mybody '$realip,$geoip2_country_code,$geoip2_region_code,$geoip2_region,$geoip2_city,$geoip2_postal_code,$geoip2_latitude,$geoip2_longitude,$geoip2_dma_code'; 25 | } 26 | 27 | if ($arg_callback) { 28 | add_header Content-Type application/javascript; 29 | return 200 '$arg_callback($mybody);'; 30 | } 31 | 32 | return 200 $mybody; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /files/etc/nginx/sites-enabled/server-conf.example: -------------------------------------------------------------------------------- 1 | proxy_cache_path /var/cache/nginx/imgproxy levels=1:2 keys_zone=imgproxy:10m max_size=1g inactive=45m; 2 | 3 | server { 4 | listen 80; 5 | listen [::]:80 ipv6only=on; 6 | 7 | listen 443 ssl http2; 8 | listen [::]:443 ipv6only=on ssl http2; 9 | 10 | include /etc/nginx/include/ssl-ciphers.conf; 11 | ssl_certificate /etc/nginx/ssl/dummycert.crt; 12 | ssl_certificate_key /etc/nginx/ssl/dummykey.key; 13 | 14 | set $width -; 15 | set $height -; 16 | set $rotate 0; 17 | set $quality 96; # default to best quality in case image previously optimized 18 | set $sharpen 0; 19 | set $debugkey "empty"; 20 | set $myhost ""; 21 | set $ofmt ""; 22 | set $debugcode ""; 23 | 24 | # image_filter_crop_offset {left,center,right} {top,center,bottom}; 25 | set $crop_offx left; 26 | set $crop_offy top; 27 | 28 | server_name default; 29 | root /usr/share/nginx/html; 30 | index index.html index.htm; 31 | error_page 301 302 307 = @handle_redirect; 32 | 33 | # begin image_filter stuff 34 | image_filter_buffer 20M; 35 | image_filter_interlace on; 36 | 37 | # needed to allow uri protocol slashes from being merged 38 | merge_slashes off; 39 | 40 | 41 | # proxy_redirect off; 42 | proxy_set_header X-Real-IP $realip_remote_addr; 43 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 44 | proxy_set_header User-Agent "$http_user_agent"; 45 | 46 | proxy_connect_timeout 30s; 47 | proxy_send_timeout 30s; 48 | proxy_read_timeout 30s; 49 | proxy_temp_path /var/cache/nginx/temp; 50 | 51 | 52 | # prevent client headers from going to origin 53 | proxy_pass_request_headers off; 54 | 55 | proxy_ignore_headers Vary Expires Set-Cookie Cache-Control; 56 | proxy_pass_header P3P; 57 | proxy_cache_min_uses 2; 58 | proxy_cache imgproxy; 59 | proxy_ssl_server_name on; 60 | proxy_intercept_errors on; 61 | proxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504; 62 | proxy_cache_background_update on; 63 | 64 | # valid for "any" http status within 10 minutes 65 | proxy_cache_valid any 10m; 66 | proxy_cache_valid 301 302 307 0s; 67 | proxy_cache_key $request_uri; 68 | 69 | # only allow GET method 70 | proxy_method GET; 71 | 72 | location /healthcheck { 73 | default_type text/plain; 74 | return 200 "OK"; 75 | } 76 | 77 | location ~* ^/rx/([^\/]+)/(.*) { 78 | set $myargs "$1"; 79 | set $protocol "http"; 80 | set $image_uri "$2"; 81 | set $cmd "resize"; 82 | set $image_path ""; 83 | set $clean_uri ""; 84 | 85 | # if no protocol in URL, add them 86 | if ($image_uri !~ "(http:|https:)") { 87 | set $image_uri "http://$image_uri"; 88 | } 89 | 90 | # now process the real image url 91 | if ($image_uri ~ "^(http|https)+([:\/]+)([^/]*)(.*)") { 92 | set $protocol $1; 93 | set $myhost $3; 94 | set $image_path $4; 95 | set $image_uri "$protocol://$myhost$image_path"; 96 | } 97 | 98 | # change this to whitelist your host 99 | # if ($myhost !~ ".*(host1.com|host2.org|host3.edu|host4.net|host5.info)$") { 100 | # set $image_uri ""; 101 | # set $debugkey "$myhost=denied"; 102 | # return 403; 103 | # break; 104 | # } 105 | 106 | # width 107 | if ($myargs ~ "^(\d+)\D*") { 108 | set $width $1; 109 | } 110 | 111 | if ($myargs ~ "w([_]*)(\d+)") { 112 | set $width $2; 113 | } 114 | 115 | if ($arg_w) { 116 | set $width $arg_w; 117 | } 118 | 119 | # height 120 | if ($myargs ~ "x(\d+)") { 121 | set $height $1; 122 | } 123 | 124 | if ($myargs ~ "h([_]*)(\d+)") { 125 | set $height $2; 126 | } 127 | 128 | if ($arg_h) { 129 | set $height $arg_h; 130 | } 131 | 132 | # quality 133 | if ($myargs ~ "q([_]*)(\d+)") { 134 | set $quality $2; 135 | } 136 | 137 | if ($arg_q) { 138 | set $quality $arg_q; 139 | } 140 | 141 | # rotate 142 | if ($myargs ~ "r([_]*)(\d+)") { 143 | set $rotate $2; 144 | } 145 | 146 | if ($arg_r) { 147 | set $rotate $arg_r; 148 | } 149 | 150 | # gravity 151 | if ($myargs ~ "Center") { 152 | set $crop_offx center; 153 | set $crop_offy center; 154 | } 155 | 156 | if ($arg_g ~ "Center") { 157 | set $crop_offx center; 158 | set $crop_offy center; 159 | } 160 | 161 | if ($myargs ~ "South") { 162 | set $crop_offy bottom; 163 | } 164 | 165 | if ($arg_g ~ "South") { 166 | set $crop_offy bottom; 167 | } 168 | 169 | if ($myargs ~ "East") { 170 | set $crop_offx right; 171 | } 172 | 173 | if ($arg_g ~ "East") { 174 | set $crop_offx right; 175 | } 176 | 177 | # sharpen 178 | if ($myargs ~ "e([_]*)(\d+)") { 179 | set $sharpen $2; 180 | } 181 | 182 | if ($arg_e) { 183 | set $sharpen $arg_e; 184 | } 185 | 186 | # output format 187 | if ($myargs ~ "ofmt([_]*)(\w+)") { 188 | set $ofmt $2; 189 | } 190 | 191 | if ($arg_ofmt) { 192 | set $ofmt $arg_ofmt; 193 | } 194 | 195 | # crop 196 | if ($myargs ~ "c([_]*)1") { 197 | set $cmd "crop"; 198 | } 199 | 200 | if ($arg_c = "1") { 201 | set $cmd "crop"; 202 | } 203 | 204 | if ($myargs ~ "g_+") { 205 | set $cmd "crop"; 206 | } 207 | 208 | if ($arg_g) { 209 | set $cmd "crop"; 210 | } 211 | 212 | set $debugkey "$image_uri?w=$width&h=$height&q=$quality&r=$rotate&e=$sharpen&cmd=$cmd&ofmt=$ofmt"; 213 | 214 | set_unescape_uri $clean_uri "$image_uri$is_args$args"; 215 | rewrite ^ /cmd/$cmd last; 216 | } 217 | 218 | location /cmd/resize { 219 | internal; 220 | 221 | proxy_pass $clean_uri; 222 | include /etc/nginx/include/proxy-hide-headers.common; 223 | 224 | add_header X-ImageProxy-Cache $upstream_cache_status; 225 | add_header X-ImageProxy-Debug $debugkey; 226 | expires 24h; 227 | add_header Cache-Control "public"; 228 | 229 | # image_filter_water_image /app/logo.png; 230 | # image_filter_water_pos center; 231 | image_filter_scale_max 3; 232 | 233 | image_filter_sharpen $sharpen; 234 | image_filter_jpeg_quality $quality; 235 | image_filter_webp_quality $quality; 236 | image_filter_output $ofmt; 237 | image_filter rotate $rotate; 238 | 239 | image_filter resize $width $height; 240 | } 241 | 242 | location /cmd/crop { 243 | internal; 244 | 245 | proxy_pass $clean_uri; 246 | include /etc/nginx/include/proxy-hide-headers.common; 247 | 248 | add_header X-ImageProxy-Cache $upstream_cache_status; 249 | add_header X-ImageProxy-Debug $debugkey; 250 | expires 24h; 251 | add_header Cache-Control "public"; 252 | 253 | # image_filter_water_image /app/logo.png; 254 | # image_filter_water_pos center; 255 | image_filter_scale_max 3; 256 | 257 | image_filter_sharpen $sharpen; 258 | image_filter_jpeg_quality $quality; 259 | image_filter_webp_quality $quality; 260 | image_filter_output $ofmt; 261 | image_filter rotate $rotate; 262 | 263 | image_filter_crop_offset $crop_offx $crop_offy; 264 | image_filter crop $width $height; 265 | } 266 | 267 | location @handle_redirect { 268 | set $image_uri "$upstream_http_location"; 269 | 270 | # if relative url, append base path 271 | if ($image_uri !~ "(http:|https:)") { 272 | set $image_uri "$protocol://$myhost$image_uri"; 273 | } 274 | 275 | set_unescape_uri $clean_uri "http://127.0.0.1/rx/$myargs/$image_uri"; 276 | proxy_cache_bypass 1; 277 | proxy_pass $clean_uri; 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /files/root/bin/dummycert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Specify where we will install 3 | # the dummycert certificate 4 | SSL_DIR="/etc/nginx/ssl" 5 | 6 | # Create our SSL directory 7 | # in case it doesn't exist 8 | mkdir -p "$SSL_DIR" 9 | 10 | # Generate dummy self-signed certificate. 11 | if [ ! -f $SSL_DIR/dummycert.pem ] || [ ! -f $SSL_DIR/dummykey.pem ] 12 | then 13 | echo "Generating dummy SSL certificate..." 14 | openssl req \ 15 | -new \ 16 | -newkey rsa:2048 \ 17 | -days 3650 \ 18 | -nodes \ 19 | -x509 \ 20 | -subj '/O=localhost/OU=localhost/CN=localhost' \ 21 | -keyout $SSL_DIR/dummykey.pem \ 22 | -out $SSL_DIR/dummycert.pem 23 | echo "Complete" 24 | fi 25 | -------------------------------------------------------------------------------- /files/root/bin/my-startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | function die { 5 | echo >&2 "$@" 6 | exit 1 7 | } 8 | 9 | ####################################### 10 | # Echo/log function 11 | # Arguments: 12 | # String: value to log 13 | ####################################### 14 | function log { 15 | if [[ "$@" ]]; then echo "[`date +'%Y-%m-%d %T'`] $@"; 16 | else echo; fi 17 | } 18 | 19 | if [ -n "$SERVER_CONF" ] ; then 20 | 21 | # backup old config if exists 22 | if [ -f /app/etc/nginx/sites-enabled/server.conf ]; then 23 | mv /app/etc/nginx/sites-enabled/server.conf /app/etc/nginx/sites-enabled/server.bak 24 | fi 25 | 26 | log "Getting new server.conf" 27 | curl -SL $SERVER_CONF --output /app/etc/nginx/sites-enabled/server.conf 28 | fi 29 | 30 | # now=$(date +"%T") 31 | # echo "Current time : $now" 32 | 33 | ls -la /root/bin 34 | 35 | echo "*** Running nginx" 36 | exec /usr/sbin/nginx -g "daemon off;" -------------------------------------------------------------------------------- /files/sbin/my_init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export TERM=xterm 4 | 5 | # save environment variables for use later in cron, if required 6 | env > /root/env.txt 7 | 8 | # no conf, so copy content 9 | if [ ! -f /etc/nginx/nginx.conf ]; then 10 | echo "[i] running for the 1st time" 11 | rsync --update -raz /app-start/* /app 12 | fi 13 | 14 | mkdir -p /tmp/nginx/cache \ 15 | /tmp/nginx/body 16 | chown -R www-data:nginx /tmp/nginx 17 | 18 | # if there is nginx.new, then make it current and backup existing 19 | if [ -f /etc/nginx/nginx.new ]; then 20 | # backup old conf if exists 21 | if [ -f /etc/nginx/nginx.conf ]; then 22 | mv /etc/nginx/nginx.conf /etc/nginx/nginx.old 23 | fi 24 | mv /etc/nginx/nginx.new /etc/nginx/nginx.conf 25 | fi 26 | 27 | # Dynamically generate resolvers file, if resolver is IPv6, enclose in `[]` 28 | echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) ipv6=off valid=10s;" > /etc/nginx/include/resolvers.conf 29 | 30 | echo "*** Running /root/bin/my-startup.sh..." 31 | bash /root/bin/my-startup.sh 32 | -------------------------------------------------------------------------------- /files/usr/share/nginx/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Default Site 8 | 38 | 39 | 40 |

Success!

41 |
42 |

This page is used to test the proper operation of the HTTP server after it has been 43 | installed. If you can read this page, it means that the 44 | web server installed at this site is working 45 | properly.

46 |
47 | 48 | --------------------------------------------------------------------------------