├── config ├── config_gfm ├── template.html ├── license.txt ├── readme.md └── ngx_markdown_filter_module.c /config: -------------------------------------------------------------------------------- 1 | ngx_module_type=HTTP_FILTER 2 | ngx_module_name=ngx_markdown_filter_module 3 | ngx_module_srcs="$ngx_addon_dir/ngx_markdown_filter_module.c" 4 | ngx_module_libs=-lcmark 5 | 6 | . auto/module 7 | 8 | ngx_addon_name=$ngx_module_name -------------------------------------------------------------------------------- /config_gfm: -------------------------------------------------------------------------------- 1 | ngx_module_type=HTTP_FILTER 2 | ngx_module_name=ngx_markdown_filter_module 3 | ngx_module_srcs="$ngx_addon_dir/ngx_markdown_filter_module.c" 4 | ngx_module_libs="-lcmark-gfm -lcmark-gfm-extensions" 5 | 6 | . auto/module 7 | 8 | ngx_addon_name=$ngx_module_name -------------------------------------------------------------------------------- /template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | 17 | 18 | 19 |
20 | {{content}} 21 |
22 | 23 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Karim Ulzhabayev 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 | ## ngx_markdown_filter_module 2 | 3 | The `ngx_markdown_filter_module` module is a filter that transforms markdown files to html format. 4 | 5 | This module utilizes the [cmark](https://github.com/commonmark/cmark) library. 6 | 7 | ### Example configuration 8 | 9 | ``` 10 | location ~ \.md { 11 | markdown_filter on; 12 | markdown_template html/template.html; 13 | } 14 | ``` 15 | 16 | This works on proxy locations as well. 17 | 18 | ### Directives 19 | 20 | ``` 21 | Syntax: markdown_filter on|off; 22 | Context: location 23 | ``` 24 | 25 | ``` 26 | Syntax: markdown_template html/template.html; 27 | Context: location 28 | ``` 29 | 30 | ``` 31 | # enable `unsafe` mode for cmark 32 | Syntax: markdown_unsafe on|off; 33 | Context: location; 34 | ``` 35 | 36 | ``` 37 | # enable `tagfilter` extension for cmark-gfm 38 | Syntax: markdown_gfm_tagfilter on|off; 39 | Context: location; 40 | ``` 41 | 42 | ``` 43 | # enable `tasklist` extension for cmark-gfm 44 | Syntax: markdown_gfm_tasklist on|off; 45 | Context: location; 46 | ``` 47 | 48 | ``` 49 | # enable `strikethrough` extension for cmark-gfm 50 | Syntax: markdown_gfm_strikethrough on|off; 51 | Context: location; 52 | ``` 53 | 54 | ``` 55 | # enable `autolink` extension for cmark-gfm 56 | Syntax: markdown_gfm_autolink on|off; 57 | Context: location; 58 | ``` 59 | 60 | ### Build 61 | 62 | 1. Clone this repo 63 | 64 | 2. Install `cmark` lib with development headers 65 | 66 | ``` 67 | dnf install cmark-devel 68 | ``` 69 | 70 | 3. Download [nginx src archive](http://nginx.org/en/download.html) and unpack it 71 | 72 | 4. Run `configure` script (see nginx src) and build nginx 73 | 74 | ``` 75 | > ./configure --add-module=/path/to/ngx_markdown_filter_module 76 | > make 77 | ``` 78 | 79 | 5. Apply markdown directives to nginx conf and run it 80 | 81 | ### Build with cmark-gfm (tables support) 82 | 83 | Original cmark library doesn't support tables. But there is [cmark-gfm](https://github.com/github/cmark-gfm) 84 | fork with table extension, supported by Github. 85 | 86 | 1. Clone this repo 87 | 88 | 2. Rename `config_gfm` to `config` 89 | 90 | 3. Install `cmark-gfm` lib 91 | 92 | 4. Download [nginx src archive](http://nginx.org/en/download.html) and unpack it 93 | 94 | 5. Run `configure` script (see nginx src) and build nginx 95 | 96 | ``` 97 | > ./configure --add-module=/path/to/ngx_markdown_filter_module --with-cc-opt=-DWITH_CMARK_GFM=1 98 | > make 99 | ``` 100 | 101 | 6. Apply markdown directives to nginx conf and run it 102 | -------------------------------------------------------------------------------- /ngx_markdown_filter_module.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #ifdef WITH_CMARK_GFM 6 | #include 7 | #include 8 | #include 9 | #else 10 | #include 11 | #endif 12 | 13 | 14 | // pointers to next handlers 15 | 16 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter; 17 | 18 | static ngx_http_output_body_filter_pt ngx_http_next_body_filter; 19 | 20 | 21 | // location conf 22 | 23 | typedef struct { 24 | ngx_flag_t enable; 25 | u_char *header; 26 | u_char *footer; 27 | ngx_int_t header_len; 28 | ngx_int_t footer_len; 29 | ngx_flag_t unsafe; 30 | ngx_flag_t gfm_tagfilter_enabled; 31 | ngx_flag_t gfm_tasklist_enabled; 32 | ngx_flag_t gfm_strikethrough_enabled; 33 | ngx_flag_t gfm_autolink_enabled; 34 | } ngx_markdown_filter_conf_t; 35 | 36 | 37 | // request context 38 | 39 | typedef struct { 40 | cmark_parser *parser; 41 | cmark_llist *extensions; 42 | } ngx_markdown_filter_ctx_t; 43 | 44 | 45 | static void *ngx_markdown_filter_create_conf(ngx_conf_t *cf); 46 | 47 | static char *ngx_markdown_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child); 48 | 49 | static ngx_int_t ngx_markdown_header_filter(ngx_http_request_t *r); 50 | 51 | static ngx_int_t ngx_markdown_body_filter(ngx_http_request_t *r, ngx_chain_t *chain); 52 | 53 | static ngx_int_t ngx_markdown_filter_init(ngx_conf_t *cf); 54 | 55 | static void cmark_parser_cleanup(void *parser); 56 | 57 | static void cmark_extensions_cleanup(void *data); 58 | 59 | static char *ngx_conf_set_template(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 60 | 61 | /* module directives */ 62 | 63 | static ngx_command_t ngx_markdown_filter_commands[] = { 64 | 65 | { ngx_string("markdown_filter"), 66 | NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 67 | ngx_conf_set_flag_slot, 68 | NGX_HTTP_LOC_CONF_OFFSET, 69 | offsetof(ngx_markdown_filter_conf_t, enable), 70 | NULL }, 71 | 72 | { ngx_string("markdown_template"), 73 | NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 74 | ngx_conf_set_template, 75 | NGX_HTTP_LOC_CONF_OFFSET, 76 | 0, // unused 77 | NULL }, 78 | 79 | { ngx_string("markdown_unsafe"), 80 | NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 81 | ngx_conf_set_flag_slot, 82 | NGX_HTTP_LOC_CONF_OFFSET, 83 | offsetof(ngx_markdown_filter_conf_t, unsafe), 84 | NULL }, 85 | 86 | { ngx_string("markdown_gfm_tagfilter"), 87 | NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 88 | ngx_conf_set_flag_slot, 89 | NGX_HTTP_LOC_CONF_OFFSET, 90 | offsetof(ngx_markdown_filter_conf_t, gfm_tagfilter_enabled), 91 | NULL }, 92 | 93 | { ngx_string("markdown_gfm_tasklist"), 94 | NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 95 | ngx_conf_set_flag_slot, 96 | NGX_HTTP_LOC_CONF_OFFSET, 97 | offsetof(ngx_markdown_filter_conf_t, gfm_tasklist_enabled), 98 | NULL }, 99 | 100 | { ngx_string("markdown_gfm_strikethrough"), 101 | NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 102 | ngx_conf_set_flag_slot, 103 | NGX_HTTP_LOC_CONF_OFFSET, 104 | offsetof(ngx_markdown_filter_conf_t, gfm_strikethrough_enabled), 105 | NULL }, 106 | 107 | { ngx_string("markdown_gfm_autolink"), 108 | NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 109 | ngx_conf_set_flag_slot, 110 | NGX_HTTP_LOC_CONF_OFFSET, 111 | offsetof(ngx_markdown_filter_conf_t, gfm_autolink_enabled), 112 | NULL }, 113 | 114 | ngx_null_command 115 | }; 116 | 117 | 118 | /* module context */ 119 | 120 | static ngx_http_module_t ngx_markdown_filter_module_ctx = { 121 | NULL, /* preconfiguration */ 122 | ngx_markdown_filter_init, /* postconfiguration */ 123 | 124 | NULL, /* create main configuration */ 125 | NULL, /* init main configuration */ 126 | 127 | NULL, /* create server configuration */ 128 | NULL, /* merge server configuration */ 129 | 130 | ngx_markdown_filter_create_conf, /* create location configuration */ 131 | ngx_markdown_filter_merge_conf /* merge location configuration */ 132 | }; 133 | 134 | 135 | /* module itself */ 136 | 137 | ngx_module_t ngx_markdown_filter_module = { 138 | NGX_MODULE_V1, 139 | &ngx_markdown_filter_module_ctx, /* module context */ 140 | ngx_markdown_filter_commands, /* module directives */ 141 | NGX_HTTP_MODULE, /* module type */ 142 | NULL, /* init master */ 143 | NULL, /* init module */ 144 | NULL, /* init process */ 145 | NULL, /* init thread */ 146 | NULL, /* exit thread */ 147 | NULL, /* exit process */ 148 | NULL, /* exit master */ 149 | NGX_MODULE_V1_PADDING 150 | }; 151 | 152 | 153 | static char *ngx_conf_set_template(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 154 | { 155 | ngx_str_t *value = cf->args->elts; 156 | ngx_str_t filename = value[1]; 157 | ngx_markdown_filter_conf_t *markdown_conf = (ngx_markdown_filter_conf_t *) conf; 158 | 159 | ngx_fd_t fd = ngx_open_file(filename.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); 160 | if (fd == NGX_INVALID_FILE) { 161 | ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "cannot open template file %s", filename.data); 162 | return NGX_CONF_ERROR; 163 | } 164 | 165 | ngx_file_info_t fi; 166 | if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR){ 167 | ngx_close_file(fd); 168 | ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "cannot get stats for template file %s", filename.data); 169 | return NGX_CONF_ERROR; 170 | } 171 | 172 | u_char *template = ngx_calloc(fi.st_size + 1, cf->log); 173 | if (template == NULL) { 174 | ngx_close_file(fd); 175 | ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "cannot allocate memory for template content"); 176 | return NGX_CONF_ERROR; 177 | } 178 | 179 | ngx_file_t file; 180 | file.fd = fd; 181 | file.info = fi; 182 | file.log = cf->log; 183 | 184 | ngx_int_t n = ngx_read_file(&file, template, fi.st_size, 0); 185 | template[fi.st_size] = '\0'; 186 | 187 | ngx_close_file(fd); 188 | 189 | for (ngx_int_t i = 0; i < n; i++) { 190 | if (template[i] == '{' && template[i+1] == '{') { 191 | template[i] = '\0'; 192 | markdown_conf->header = template; 193 | markdown_conf->header_len = ngx_strlen(template); 194 | continue; 195 | } 196 | if (template[i] == '}' && template[i+1] == '}') { 197 | markdown_conf->footer = template + (i+2); // Note!! pointer arithmetic 198 | markdown_conf->footer_len = ngx_strlen(markdown_conf->footer); 199 | break; 200 | } 201 | } 202 | 203 | return NGX_CONF_OK; 204 | } 205 | 206 | 207 | static void *ngx_markdown_filter_create_conf(ngx_conf_t *cf) 208 | { 209 | ngx_markdown_filter_conf_t *conf = ngx_pcalloc(cf->pool, sizeof(ngx_markdown_filter_conf_t)); 210 | if (conf == NULL) { 211 | return NULL; 212 | } 213 | conf->enable = NGX_CONF_UNSET; 214 | conf->header = NGX_CONF_UNSET_PTR; 215 | conf->footer = NGX_CONF_UNSET_PTR; 216 | conf->header_len = NGX_CONF_UNSET; 217 | conf->footer_len = NGX_CONF_UNSET; 218 | conf->unsafe = NGX_CONF_UNSET; 219 | conf->gfm_tagfilter_enabled = NGX_CONF_UNSET; 220 | conf->gfm_tasklist_enabled = NGX_CONF_UNSET; 221 | conf->gfm_strikethrough_enabled = NGX_CONF_UNSET; 222 | conf->gfm_autolink_enabled = NGX_CONF_UNSET; 223 | return conf; 224 | } 225 | 226 | 227 | static char *ngx_markdown_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child) 228 | { 229 | ngx_markdown_filter_conf_t *prev = parent; 230 | ngx_markdown_filter_conf_t *conf = child; 231 | ngx_conf_merge_value(conf->enable, prev->enable, 0); 232 | ngx_conf_merge_ptr_value(conf->header, prev->header, NULL); 233 | ngx_conf_merge_ptr_value(conf->footer, prev->footer, NULL); 234 | ngx_conf_merge_value(conf->header_len, prev->header_len, 0); 235 | ngx_conf_merge_value(conf->footer_len, prev->footer_len, 0); 236 | ngx_conf_merge_value(conf->unsafe, prev->unsafe, 0); 237 | ngx_conf_merge_value(conf->gfm_tagfilter_enabled, prev->gfm_tagfilter_enabled, 0); 238 | ngx_conf_merge_value(conf->gfm_tasklist_enabled, prev->gfm_tasklist_enabled, 0); 239 | ngx_conf_merge_value(conf->gfm_strikethrough_enabled, prev->gfm_strikethrough_enabled, 0); 240 | ngx_conf_merge_value(conf->gfm_autolink_enabled, prev->gfm_autolink_enabled, 0); 241 | return NGX_CONF_OK; 242 | } 243 | 244 | 245 | static ngx_int_t ngx_markdown_filter_init(ngx_conf_t *cf) 246 | { 247 | 248 | ngx_http_next_header_filter = ngx_http_top_header_filter; 249 | ngx_http_top_header_filter = ngx_markdown_header_filter; 250 | 251 | ngx_http_next_body_filter = ngx_http_top_body_filter; 252 | ngx_http_top_body_filter = ngx_markdown_body_filter; 253 | 254 | return NGX_OK; 255 | } 256 | 257 | 258 | static void cmark_parser_cleanup(void *data) 259 | { 260 | if (data == NULL) { 261 | return; 262 | } 263 | cmark_parser *parser = data; 264 | cmark_parser_free(parser); 265 | } 266 | 267 | static void cmark_extensions_cleanup(void *data) 268 | { 269 | if (data == NULL) { 270 | return; 271 | } 272 | cmark_llist *extensions = data; 273 | cmark_llist_free(cmark_get_default_mem_allocator(), extensions); 274 | } 275 | 276 | static ngx_int_t ngx_markdown_header_filter(ngx_http_request_t *r) 277 | { 278 | ngx_markdown_filter_conf_t *lc = ngx_http_get_module_loc_conf(r, ngx_markdown_filter_module); 279 | if (lc->enable && r->headers_out.status == NGX_HTTP_OK) { 280 | ngx_markdown_filter_ctx_t *ctx = ngx_pcalloc(r->pool, sizeof(ngx_markdown_filter_ctx_t)); 281 | if (ctx == NULL) { 282 | return NGX_ERROR; 283 | } 284 | int cmark_opts = lc->unsafe ? CMARK_OPT_UNSAFE : CMARK_OPT_DEFAULT; 285 | cmark_parser *parser = cmark_parser_new(cmark_opts); 286 | if (parser == NULL) { 287 | return NGX_ERROR; 288 | } 289 | 290 | cmark_llist *extensions = NULL; 291 | 292 | #ifdef WITH_CMARK_GFM 293 | cmark_gfm_core_extensions_ensure_registered(); 294 | cmark_syntax_extension *ext_table = cmark_find_syntax_extension("table"); 295 | if (ext_table != NULL) { 296 | cmark_parser_attach_syntax_extension(parser, ext_table); 297 | } 298 | 299 | if (lc->gfm_tagfilter_enabled) { 300 | cmark_syntax_extension *ext_tagfilter = cmark_find_syntax_extension("tagfilter"); 301 | if (ext_tagfilter != NULL) { 302 | extensions = cmark_llist_append(cmark_get_default_mem_allocator(), extensions, ext_tagfilter); 303 | cmark_parser_attach_syntax_extension(parser, ext_tagfilter); 304 | } 305 | } 306 | 307 | if (lc->gfm_tasklist_enabled) { 308 | cmark_syntax_extension *ext_tasklist = cmark_find_syntax_extension("tasklist"); 309 | if (ext_tasklist != NULL) { 310 | extensions = cmark_llist_append(cmark_get_default_mem_allocator(), extensions, ext_tasklist); 311 | cmark_parser_attach_syntax_extension(parser, ext_tasklist); 312 | } 313 | } 314 | 315 | if (lc->gfm_strikethrough_enabled) { 316 | cmark_syntax_extension *ext_strikethrough = cmark_find_syntax_extension("strikethrough"); 317 | if (ext_strikethrough != NULL) { 318 | extensions = cmark_llist_append(cmark_get_default_mem_allocator(), extensions, ext_strikethrough); 319 | cmark_parser_attach_syntax_extension(parser, ext_strikethrough); 320 | } 321 | } 322 | 323 | if (lc->gfm_autolink_enabled) { 324 | cmark_syntax_extension *ext_autolink = cmark_find_syntax_extension("autolink"); 325 | if (ext_autolink != NULL) { 326 | extensions = cmark_llist_append(cmark_get_default_mem_allocator(), extensions, ext_autolink); 327 | cmark_parser_attach_syntax_extension(parser, ext_autolink); 328 | } 329 | } 330 | #endif 331 | 332 | ctx->parser = parser; 333 | ctx->extensions = extensions; 334 | 335 | ngx_pool_cleanup_t *cln_parser = ngx_pool_cleanup_add(r->pool, 0); 336 | if (cln_parser == NULL) { 337 | cmark_parser_cleanup(parser); 338 | return NGX_ERROR; 339 | } 340 | cln_parser->handler = cmark_parser_cleanup; 341 | cln_parser->data = parser; 342 | 343 | if (extensions != NULL) { 344 | ngx_pool_cleanup_t *cln_extensions = ngx_pool_cleanup_add(r->pool, 0); 345 | if (cln_extensions == NULL) { 346 | cmark_extensions_cleanup(extensions); 347 | return NGX_ERROR; 348 | } 349 | cln_extensions->handler = cmark_extensions_cleanup; 350 | cln_extensions->data = extensions; 351 | } 352 | 353 | ngx_http_set_ctx(r, ctx, ngx_markdown_filter_module); 354 | 355 | ngx_str_t mime = ngx_string("text/html;charset=utf-8"); 356 | r->headers_out.content_type = mime; 357 | r->main_filter_need_in_memory = 1; 358 | ngx_http_clear_content_length(r); 359 | } 360 | return ngx_http_next_header_filter(r); 361 | } 362 | 363 | 364 | static ngx_int_t ngx_markdown_body_filter(ngx_http_request_t *r, ngx_chain_t *chain) 365 | { 366 | if (chain == NULL) { 367 | return ngx_http_next_body_filter(r, chain); 368 | } 369 | 370 | if (r->headers_out.status != NGX_HTTP_OK) { 371 | return ngx_http_next_body_filter(r, chain); 372 | } 373 | 374 | ngx_markdown_filter_conf_t *lc = ngx_http_get_module_loc_conf(r, ngx_markdown_filter_module); 375 | if (!(lc->enable)) { 376 | return ngx_http_next_body_filter(r, chain); 377 | } 378 | 379 | ngx_markdown_filter_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_markdown_filter_module); 380 | if (ctx == NULL) { 381 | return NGX_ERROR; 382 | } 383 | 384 | cmark_parser *parser = ctx->parser; 385 | if (parser == NULL) { 386 | return NGX_ERROR; 387 | } 388 | 389 | cmark_llist *extensions = ctx->extensions; 390 | 391 | int last = 0; 392 | for (ngx_chain_t *cl = chain; cl; cl = cl->next) { 393 | ngx_buf_t *buf = cl->buf; 394 | 395 | cmark_parser_feed(parser, (char *)(buf->pos), ngx_buf_size(buf)); 396 | 397 | buf->pos = buf->last; 398 | buf->flush = 0; 399 | 400 | if (buf->last_buf) { 401 | last = 1; 402 | } 403 | } 404 | if (last) { 405 | cmark_node *root = cmark_parser_finish(parser); 406 | int cmark_opts = lc->unsafe ? CMARK_OPT_UNSAFE : CMARK_OPT_DEFAULT; 407 | 408 | #ifdef WITH_CMARK_GFM 409 | char *html = cmark_render_html(root, cmark_opts, extensions); 410 | #else 411 | char *html = cmark_render_html(root, cmark_opts); 412 | #endif 413 | 414 | cmark_node_free(root); // remove document tree 415 | 416 | if (html == NULL) { 417 | return NGX_ERROR; 418 | } 419 | 420 | ngx_pool_cleanup_t *cln = ngx_pool_cleanup_add(r->pool, 0); 421 | if (cln == NULL) { 422 | ngx_free(html); 423 | return NGX_ERROR; 424 | } 425 | 426 | cln->handler = ngx_free; 427 | cln->data = html; 428 | 429 | ngx_chain_t *out_chain = NULL; 430 | 431 | // add header 432 | 433 | if (lc->header != NULL) { 434 | out_chain = ngx_alloc_chain_link(r->pool); 435 | if (out_chain == NULL) { 436 | return NGX_ERROR; 437 | } 438 | ngx_buf_t *header_buf = ngx_calloc_buf(r->pool); 439 | if (header_buf == NULL) { 440 | return NGX_ERROR; 441 | } 442 | header_buf->pos = lc->header; 443 | header_buf->last = header_buf->pos + lc->header_len; 444 | header_buf->memory = 1; // Set readonly flag, and do not create copy of lc->header 445 | header_buf->last_buf = 0; 446 | header_buf->last_in_chain = 0; 447 | 448 | out_chain->buf = header_buf; 449 | out_chain->next = NULL; 450 | } 451 | 452 | int footer_missing = lc->footer == NULL ? 1 : 0; 453 | 454 | // add markdown content 455 | 456 | ngx_chain_t *content_chain = ngx_alloc_chain_link(r->pool); 457 | if (content_chain == NULL) { 458 | return NGX_ERROR; 459 | } 460 | 461 | ngx_buf_t *content_buf = ngx_calloc_buf(r->pool); 462 | if (content_buf == NULL) { 463 | return NGX_ERROR; 464 | } 465 | content_buf->pos = (u_char *) html; 466 | content_buf->last = content_buf->pos + strlen(html); 467 | content_buf->memory = 1; 468 | content_buf->last_buf = footer_missing; 469 | content_buf->last_in_chain = footer_missing; 470 | 471 | content_chain->buf = content_buf; 472 | content_chain->next = NULL; 473 | 474 | if (out_chain == NULL) { 475 | out_chain = content_chain; 476 | } else { 477 | out_chain->next = content_chain; 478 | } 479 | 480 | // add footer 481 | 482 | if (!footer_missing) { 483 | ngx_chain_t *footer_chain = ngx_alloc_chain_link(r->pool); 484 | if (footer_chain == NULL) { 485 | return NGX_ERROR; 486 | } 487 | ngx_buf_t *footer_buf = ngx_calloc_buf(r->pool); 488 | if (footer_buf == NULL) { 489 | return NGX_ERROR; 490 | } 491 | footer_buf->pos = lc->footer; 492 | footer_buf->last = footer_buf->pos + lc->footer_len; 493 | footer_buf->memory = 1; // Set readonly flag, and do not create copy of lc->footer 494 | footer_buf->last_buf = 1; 495 | footer_buf->last_in_chain = 1; 496 | 497 | footer_chain->buf = footer_buf; 498 | footer_chain->next = NULL; 499 | 500 | content_chain->next = footer_chain; 501 | } 502 | 503 | return ngx_http_next_body_filter(r, out_chain); 504 | } 505 | 506 | return NGX_OK; 507 | } 508 | --------------------------------------------------------------------------------