| |
| /* |
| * Copyright (C) Igor Sysoev |
| * Copyright (C) Nginx, Inc. |
| */ |
| |
| |
| #include <ngx_config.h> |
| #include <ngx_core.h> |
| |
| |
| typedef struct { |
| ngx_flag_t pcre_jit; |
| ngx_list_t *studies; |
| } ngx_regex_conf_t; |
| |
| |
| static ngx_inline void ngx_regex_malloc_init(ngx_pool_t *pool); |
| static ngx_inline void ngx_regex_malloc_done(void); |
| |
| #if (NGX_PCRE2) |
| static void * ngx_libc_cdecl ngx_regex_malloc(size_t size, void *data); |
| static void ngx_libc_cdecl ngx_regex_free(void *p, void *data); |
| #else |
| static void * ngx_libc_cdecl ngx_regex_malloc(size_t size); |
| static void ngx_libc_cdecl ngx_regex_free(void *p); |
| #endif |
| static void ngx_regex_cleanup(void *data); |
| |
| static ngx_int_t ngx_regex_module_init(ngx_cycle_t *cycle); |
| |
| static void *ngx_regex_create_conf(ngx_cycle_t *cycle); |
| static char *ngx_regex_init_conf(ngx_cycle_t *cycle, void *conf); |
| |
| static char *ngx_regex_pcre_jit(ngx_conf_t *cf, void *post, void *data); |
| static ngx_conf_post_t ngx_regex_pcre_jit_post = { ngx_regex_pcre_jit }; |
| |
| |
| static ngx_command_t ngx_regex_commands[] = { |
| |
| { ngx_string("pcre_jit"), |
| NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, |
| ngx_conf_set_flag_slot, |
| 0, |
| offsetof(ngx_regex_conf_t, pcre_jit), |
| &ngx_regex_pcre_jit_post }, |
| |
| ngx_null_command |
| }; |
| |
| |
| static ngx_core_module_t ngx_regex_module_ctx = { |
| ngx_string("regex"), |
| ngx_regex_create_conf, |
| ngx_regex_init_conf |
| }; |
| |
| |
| ngx_module_t ngx_regex_module = { |
| NGX_MODULE_V1, |
| &ngx_regex_module_ctx, /* module context */ |
| ngx_regex_commands, /* module directives */ |
| NGX_CORE_MODULE, /* module type */ |
| NULL, /* init master */ |
| ngx_regex_module_init, /* init module */ |
| NULL, /* init process */ |
| NULL, /* init thread */ |
| NULL, /* exit thread */ |
| NULL, /* exit process */ |
| NULL, /* exit master */ |
| NGX_MODULE_V1_PADDING |
| }; |
| |
| |
| static ngx_pool_t *ngx_regex_pool; |
| static ngx_list_t *ngx_regex_studies; |
| static ngx_uint_t ngx_regex_direct_alloc; |
| |
| #if (NGX_PCRE2) |
| static pcre2_compile_context *ngx_regex_compile_context; |
| static pcre2_match_data *ngx_regex_match_data; |
| static ngx_uint_t ngx_regex_match_data_size; |
| #endif |
| |
| |
| void |
| ngx_regex_init(void) |
| { |
| #if !(NGX_PCRE2) |
| pcre_malloc = ngx_regex_malloc; |
| pcre_free = ngx_regex_free; |
| #endif |
| } |
| |
| |
| static ngx_inline void |
| ngx_regex_malloc_init(ngx_pool_t *pool) |
| { |
| ngx_regex_pool = pool; |
| ngx_regex_direct_alloc = (pool == NULL) ? 1 : 0; |
| } |
| |
| |
| static ngx_inline void |
| ngx_regex_malloc_done(void) |
| { |
| ngx_regex_pool = NULL; |
| ngx_regex_direct_alloc = 0; |
| } |
| |
| |
| #if (NGX_PCRE2) |
| |
| ngx_int_t |
| ngx_regex_compile(ngx_regex_compile_t *rc) |
| { |
| int n, errcode; |
| char *p; |
| u_char errstr[128]; |
| size_t erroff; |
| uint32_t options; |
| pcre2_code *re; |
| ngx_regex_elt_t *elt; |
| pcre2_general_context *gctx; |
| pcre2_compile_context *cctx; |
| |
| if (ngx_regex_compile_context == NULL) { |
| /* |
| * Allocate a compile context if not yet allocated. This uses |
| * direct allocations from heap, so the result can be cached |
| * even at runtime. |
| */ |
| |
| ngx_regex_malloc_init(NULL); |
| |
| gctx = pcre2_general_context_create(ngx_regex_malloc, ngx_regex_free, |
| NULL); |
| if (gctx == NULL) { |
| ngx_regex_malloc_done(); |
| goto nomem; |
| } |
| |
| cctx = pcre2_compile_context_create(gctx); |
| if (cctx == NULL) { |
| pcre2_general_context_free(gctx); |
| ngx_regex_malloc_done(); |
| goto nomem; |
| } |
| |
| ngx_regex_compile_context = cctx; |
| |
| pcre2_general_context_free(gctx); |
| ngx_regex_malloc_done(); |
| } |
| |
| options = 0; |
| |
| if (rc->options & NGX_REGEX_CASELESS) { |
| options |= PCRE2_CASELESS; |
| } |
| |
| if (rc->options & NGX_REGEX_MULTILINE) { |
| options |= PCRE2_MULTILINE; |
| } |
| |
| if (rc->options & ~(NGX_REGEX_CASELESS|NGX_REGEX_MULTILINE)) { |
| rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, |
| "regex \"%V\" compilation failed: invalid options", |
| &rc->pattern) |
| - rc->err.data; |
| return NGX_ERROR; |
| } |
| |
| ngx_regex_malloc_init(rc->pool); |
| |
| re = pcre2_compile(rc->pattern.data, rc->pattern.len, options, |
| &errcode, &erroff, ngx_regex_compile_context); |
| |
| /* ensure that there is no current pool */ |
| ngx_regex_malloc_done(); |
| |
| if (re == NULL) { |
| pcre2_get_error_message(errcode, errstr, 128); |
| |
| if ((size_t) erroff == rc->pattern.len) { |
| rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, |
| "pcre2_compile() failed: %s in \"%V\"", |
| errstr, &rc->pattern) |
| - rc->err.data; |
| |
| } else { |
| rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, |
| "pcre2_compile() failed: %s in \"%V\" at \"%s\"", |
| errstr, &rc->pattern, rc->pattern.data + erroff) |
| - rc->err.data; |
| } |
| |
| return NGX_ERROR; |
| } |
| |
| rc->regex = re; |
| |
| /* do not study at runtime */ |
| |
| if (ngx_regex_studies != NULL) { |
| elt = ngx_list_push(ngx_regex_studies); |
| if (elt == NULL) { |
| goto nomem; |
| } |
| |
| elt->regex = rc->regex; |
| elt->name = rc->pattern.data; |
| } |
| |
| n = pcre2_pattern_info(re, PCRE2_INFO_CAPTURECOUNT, &rc->captures); |
| if (n < 0) { |
| p = "pcre2_pattern_info(\"%V\", PCRE2_INFO_CAPTURECOUNT) failed: %d"; |
| goto failed; |
| } |
| |
| if (rc->captures == 0) { |
| return NGX_OK; |
| } |
| |
| n = pcre2_pattern_info(re, PCRE2_INFO_NAMECOUNT, &rc->named_captures); |
| if (n < 0) { |
| p = "pcre2_pattern_info(\"%V\", PCRE2_INFO_NAMECOUNT) failed: %d"; |
| goto failed; |
| } |
| |
| if (rc->named_captures == 0) { |
| return NGX_OK; |
| } |
| |
| n = pcre2_pattern_info(re, PCRE2_INFO_NAMEENTRYSIZE, &rc->name_size); |
| if (n < 0) { |
| p = "pcre2_pattern_info(\"%V\", PCRE2_INFO_NAMEENTRYSIZE) failed: %d"; |
| goto failed; |
| } |
| |
| n = pcre2_pattern_info(re, PCRE2_INFO_NAMETABLE, &rc->names); |
| if (n < 0) { |
| p = "pcre2_pattern_info(\"%V\", PCRE2_INFO_NAMETABLE) failed: %d"; |
| goto failed; |
| } |
| |
| return NGX_OK; |
| |
| failed: |
| |
| rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, p, &rc->pattern, n) |
| - rc->err.data; |
| return NGX_ERROR; |
| |
| nomem: |
| |
| rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, |
| "regex \"%V\" compilation failed: no memory", |
| &rc->pattern) |
| - rc->err.data; |
| return NGX_ERROR; |
| } |
| |
| #else |
| |
| ngx_int_t |
| ngx_regex_compile(ngx_regex_compile_t *rc) |
| { |
| int n, erroff; |
| char *p; |
| pcre *re; |
| const char *errstr; |
| ngx_uint_t options; |
| ngx_regex_elt_t *elt; |
| |
| options = 0; |
| |
| if (rc->options & NGX_REGEX_CASELESS) { |
| options |= PCRE_CASELESS; |
| } |
| |
| if (rc->options & NGX_REGEX_MULTILINE) { |
| options |= PCRE_MULTILINE; |
| } |
| |
| if (rc->options & ~(NGX_REGEX_CASELESS|NGX_REGEX_MULTILINE)) { |
| rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, |
| "regex \"%V\" compilation failed: invalid options", |
| &rc->pattern) |
| - rc->err.data; |
| return NGX_ERROR; |
| } |
| |
| ngx_regex_malloc_init(rc->pool); |
| |
| re = pcre_compile((const char *) rc->pattern.data, (int) options, |
| &errstr, &erroff, NULL); |
| |
| /* ensure that there is no current pool */ |
| ngx_regex_malloc_done(); |
| |
| if (re == NULL) { |
| if ((size_t) erroff == rc->pattern.len) { |
| rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, |
| "pcre_compile() failed: %s in \"%V\"", |
| errstr, &rc->pattern) |
| - rc->err.data; |
| |
| } else { |
| rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, |
| "pcre_compile() failed: %s in \"%V\" at \"%s\"", |
| errstr, &rc->pattern, rc->pattern.data + erroff) |
| - rc->err.data; |
| } |
| |
| return NGX_ERROR; |
| } |
| |
| rc->regex = ngx_pcalloc(rc->pool, sizeof(ngx_regex_t)); |
| if (rc->regex == NULL) { |
| goto nomem; |
| } |
| |
| rc->regex->code = re; |
| |
| /* do not study at runtime */ |
| |
| if (ngx_regex_studies != NULL) { |
| elt = ngx_list_push(ngx_regex_studies); |
| if (elt == NULL) { |
| goto nomem; |
| } |
| |
| elt->regex = rc->regex; |
| elt->name = rc->pattern.data; |
| } |
| |
| n = pcre_fullinfo(re, NULL, PCRE_INFO_CAPTURECOUNT, &rc->captures); |
| if (n < 0) { |
| p = "pcre_fullinfo(\"%V\", PCRE_INFO_CAPTURECOUNT) failed: %d"; |
| goto failed; |
| } |
| |
| if (rc->captures == 0) { |
| return NGX_OK; |
| } |
| |
| n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMECOUNT, &rc->named_captures); |
| if (n < 0) { |
| p = "pcre_fullinfo(\"%V\", PCRE_INFO_NAMECOUNT) failed: %d"; |
| goto failed; |
| } |
| |
| if (rc->named_captures == 0) { |
| return NGX_OK; |
| } |
| |
| n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMEENTRYSIZE, &rc->name_size); |
| if (n < 0) { |
| p = "pcre_fullinfo(\"%V\", PCRE_INFO_NAMEENTRYSIZE) failed: %d"; |
| goto failed; |
| } |
| |
| n = pcre_fullinfo(re, NULL, PCRE_INFO_NAMETABLE, &rc->names); |
| if (n < 0) { |
| p = "pcre_fullinfo(\"%V\", PCRE_INFO_NAMETABLE) failed: %d"; |
| goto failed; |
| } |
| |
| return NGX_OK; |
| |
| failed: |
| |
| rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, p, &rc->pattern, n) |
| - rc->err.data; |
| return NGX_ERROR; |
| |
| nomem: |
| |
| rc->err.len = ngx_snprintf(rc->err.data, rc->err.len, |
| "regex \"%V\" compilation failed: no memory", |
| &rc->pattern) |
| - rc->err.data; |
| return NGX_ERROR; |
| } |
| |
| #endif |
| |
| |
| #if (NGX_PCRE2) |
| |
| ngx_int_t |
| ngx_regex_exec(ngx_regex_t *re, ngx_str_t *s, int *captures, ngx_uint_t size) |
| { |
| size_t *ov; |
| ngx_int_t rc; |
| ngx_uint_t n, i; |
| |
| /* |
| * The pcre2_match() function might allocate memory for backtracking |
| * frames, typical allocations are from 40k and above. So the allocator |
| * is configured to do direct allocations from heap during matching. |
| */ |
| |
| ngx_regex_malloc_init(NULL); |
| |
| if (ngx_regex_match_data == NULL |
| || size > ngx_regex_match_data_size) |
| { |
| /* |
| * Allocate a match data if not yet allocated or smaller than |
| * needed. |
| */ |
| |
| if (ngx_regex_match_data) { |
| pcre2_match_data_free(ngx_regex_match_data); |
| } |
| |
| ngx_regex_match_data_size = size; |
| ngx_regex_match_data = pcre2_match_data_create(size / 3, NULL); |
| |
| if (ngx_regex_match_data == NULL) { |
| rc = PCRE2_ERROR_NOMEMORY; |
| goto failed; |
| } |
| } |
| |
| rc = pcre2_match(re, s->data, s->len, 0, 0, ngx_regex_match_data, NULL); |
| |
| if (rc < 0) { |
| goto failed; |
| } |
| |
| n = pcre2_get_ovector_count(ngx_regex_match_data); |
| ov = pcre2_get_ovector_pointer(ngx_regex_match_data); |
| |
| if (n > size / 3) { |
| n = size / 3; |
| } |
| |
| for (i = 0; i < n; i++) { |
| captures[i * 2] = ov[i * 2]; |
| captures[i * 2 + 1] = ov[i * 2 + 1]; |
| } |
| |
| failed: |
| |
| ngx_regex_malloc_done(); |
| |
| return rc; |
| } |
| |
| #else |
| |
| ngx_int_t |
| ngx_regex_exec(ngx_regex_t *re, ngx_str_t *s, int *captures, ngx_uint_t size) |
| { |
| return pcre_exec(re->code, re->extra, (const char *) s->data, s->len, |
| 0, 0, captures, size); |
| } |
| |
| #endif |
| |
| |
| ngx_int_t |
| ngx_regex_exec_array(ngx_array_t *a, ngx_str_t *s, ngx_log_t *log) |
| { |
| ngx_int_t n; |
| ngx_uint_t i; |
| ngx_regex_elt_t *re; |
| |
| re = a->elts; |
| |
| for (i = 0; i < a->nelts; i++) { |
| |
| n = ngx_regex_exec(re[i].regex, s, NULL, 0); |
| |
| if (n == NGX_REGEX_NO_MATCHED) { |
| continue; |
| } |
| |
| if (n < 0) { |
| ngx_log_error(NGX_LOG_ALERT, log, 0, |
| ngx_regex_exec_n " failed: %i on \"%V\" using \"%s\"", |
| n, s, re[i].name); |
| return NGX_ERROR; |
| } |
| |
| /* match */ |
| |
| return NGX_OK; |
| } |
| |
| return NGX_DECLINED; |
| } |
| |
| |
| #if (NGX_PCRE2) |
| |
| static void * ngx_libc_cdecl |
| ngx_regex_malloc(size_t size, void *data) |
| { |
| if (ngx_regex_pool) { |
| return ngx_palloc(ngx_regex_pool, size); |
| } |
| |
| if (ngx_regex_direct_alloc) { |
| return ngx_alloc(size, ngx_cycle->log); |
| } |
| |
| return NULL; |
| } |
| |
| |
| static void ngx_libc_cdecl |
| ngx_regex_free(void *p, void *data) |
| { |
| if (ngx_regex_direct_alloc) { |
| ngx_free(p); |
| } |
| |
| return; |
| } |
| |
| #else |
| |
| static void * ngx_libc_cdecl |
| ngx_regex_malloc(size_t size) |
| { |
| if (ngx_regex_pool) { |
| return ngx_palloc(ngx_regex_pool, size); |
| } |
| |
| return NULL; |
| } |
| |
| |
| static void ngx_libc_cdecl |
| ngx_regex_free(void *p) |
| { |
| return; |
| } |
| |
| #endif |
| |
| |
| static void |
| ngx_regex_cleanup(void *data) |
| { |
| #if (NGX_PCRE2 || NGX_HAVE_PCRE_JIT) |
| ngx_regex_conf_t *rcf = data; |
| |
| ngx_uint_t i; |
| ngx_list_part_t *part; |
| ngx_regex_elt_t *elts; |
| |
| part = &rcf->studies->part; |
| elts = part->elts; |
| |
| for (i = 0; /* void */ ; i++) { |
| |
| if (i >= part->nelts) { |
| if (part->next == NULL) { |
| break; |
| } |
| |
| part = part->next; |
| elts = part->elts; |
| i = 0; |
| } |
| |
| /* |
| * The PCRE JIT compiler uses mmap for its executable codes, so we |
| * have to explicitly call the pcre_free_study() function to free |
| * this memory. In PCRE2, we call the pcre2_code_free() function |
| * for the same reason. |
| */ |
| |
| #if (NGX_PCRE2) |
| pcre2_code_free(elts[i].regex); |
| #else |
| if (elts[i].regex->extra != NULL) { |
| pcre_free_study(elts[i].regex->extra); |
| } |
| #endif |
| } |
| #endif |
| |
| /* |
| * On configuration parsing errors ngx_regex_module_init() will not |
| * be called. Make sure ngx_regex_studies is properly cleared anyway. |
| */ |
| |
| ngx_regex_studies = NULL; |
| |
| #if (NGX_PCRE2) |
| |
| /* |
| * Free compile context and match data. If needed at runtime by |
| * the new cycle, these will be re-allocated. |
| */ |
| |
| if (ngx_regex_compile_context) { |
| pcre2_compile_context_free(ngx_regex_compile_context); |
| ngx_regex_compile_context = NULL; |
| } |
| |
| if (ngx_regex_match_data) { |
| pcre2_match_data_free(ngx_regex_match_data); |
| ngx_regex_match_data = NULL; |
| ngx_regex_match_data_size = 0; |
| } |
| |
| #endif |
| } |
| |
| |
| static ngx_int_t |
| ngx_regex_module_init(ngx_cycle_t *cycle) |
| { |
| int opt; |
| #if !(NGX_PCRE2) |
| const char *errstr; |
| #endif |
| ngx_uint_t i; |
| ngx_list_part_t *part; |
| ngx_regex_elt_t *elts; |
| ngx_regex_conf_t *rcf; |
| |
| opt = 0; |
| |
| rcf = (ngx_regex_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_regex_module); |
| |
| #if (NGX_PCRE2 || NGX_HAVE_PCRE_JIT) |
| |
| if (rcf->pcre_jit) { |
| #if (NGX_PCRE2) |
| opt = 1; |
| #else |
| opt = PCRE_STUDY_JIT_COMPILE; |
| #endif |
| } |
| |
| #endif |
| |
| ngx_regex_malloc_init(cycle->pool); |
| |
| part = &rcf->studies->part; |
| elts = part->elts; |
| |
| for (i = 0; /* void */ ; i++) { |
| |
| if (i >= part->nelts) { |
| if (part->next == NULL) { |
| break; |
| } |
| |
| part = part->next; |
| elts = part->elts; |
| i = 0; |
| } |
| |
| #if (NGX_PCRE2) |
| |
| if (opt) { |
| int n; |
| |
| n = pcre2_jit_compile(elts[i].regex, PCRE2_JIT_COMPLETE); |
| |
| if (n != 0) { |
| ngx_log_error(NGX_LOG_INFO, cycle->log, 0, |
| "pcre2_jit_compile() failed: %d in \"%s\", " |
| "ignored", |
| n, elts[i].name); |
| } |
| } |
| |
| #else |
| |
| elts[i].regex->extra = pcre_study(elts[i].regex->code, opt, &errstr); |
| |
| if (errstr != NULL) { |
| ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, |
| "pcre_study() failed: %s in \"%s\"", |
| errstr, elts[i].name); |
| } |
| |
| #if (NGX_HAVE_PCRE_JIT) |
| if (opt & PCRE_STUDY_JIT_COMPILE) { |
| int jit, n; |
| |
| jit = 0; |
| n = pcre_fullinfo(elts[i].regex->code, elts[i].regex->extra, |
| PCRE_INFO_JIT, &jit); |
| |
| if (n != 0 || jit != 1) { |
| ngx_log_error(NGX_LOG_INFO, cycle->log, 0, |
| "JIT compiler does not support pattern: \"%s\"", |
| elts[i].name); |
| } |
| } |
| #endif |
| #endif |
| } |
| |
| ngx_regex_malloc_done(); |
| |
| ngx_regex_studies = NULL; |
| #if (NGX_PCRE2) |
| ngx_regex_compile_context = NULL; |
| #endif |
| |
| return NGX_OK; |
| } |
| |
| |
| static void * |
| ngx_regex_create_conf(ngx_cycle_t *cycle) |
| { |
| ngx_regex_conf_t *rcf; |
| ngx_pool_cleanup_t *cln; |
| |
| rcf = ngx_pcalloc(cycle->pool, sizeof(ngx_regex_conf_t)); |
| if (rcf == NULL) { |
| return NULL; |
| } |
| |
| rcf->pcre_jit = NGX_CONF_UNSET; |
| |
| cln = ngx_pool_cleanup_add(cycle->pool, 0); |
| if (cln == NULL) { |
| return NULL; |
| } |
| |
| cln->handler = ngx_regex_cleanup; |
| cln->data = rcf; |
| |
| rcf->studies = ngx_list_create(cycle->pool, 8, sizeof(ngx_regex_elt_t)); |
| if (rcf->studies == NULL) { |
| return NULL; |
| } |
| |
| ngx_regex_studies = rcf->studies; |
| |
| return rcf; |
| } |
| |
| |
| static char * |
| ngx_regex_init_conf(ngx_cycle_t *cycle, void *conf) |
| { |
| ngx_regex_conf_t *rcf = conf; |
| |
| ngx_conf_init_value(rcf->pcre_jit, 0); |
| |
| return NGX_CONF_OK; |
| } |
| |
| |
| static char * |
| ngx_regex_pcre_jit(ngx_conf_t *cf, void *post, void *data) |
| { |
| ngx_flag_t *fp = data; |
| |
| if (*fp == 0) { |
| return NGX_CONF_OK; |
| } |
| |
| #if (NGX_PCRE2) |
| { |
| int r; |
| uint32_t jit; |
| |
| jit = 0; |
| r = pcre2_config(PCRE2_CONFIG_JIT, &jit); |
| |
| if (r != 0 || jit != 1) { |
| ngx_conf_log_error(NGX_LOG_WARN, cf, 0, |
| "PCRE2 library does not support JIT"); |
| *fp = 0; |
| } |
| } |
| #elif (NGX_HAVE_PCRE_JIT) |
| { |
| int jit, r; |
| |
| jit = 0; |
| r = pcre_config(PCRE_CONFIG_JIT, &jit); |
| |
| if (r != 0 || jit != 1) { |
| ngx_conf_log_error(NGX_LOG_WARN, cf, 0, |
| "PCRE library does not support JIT"); |
| *fp = 0; |
| } |
| } |
| #else |
| ngx_conf_log_error(NGX_LOG_WARN, cf, 0, |
| "nginx was built without PCRE JIT support"); |
| *fp = 0; |
| #endif |
| |
| return NGX_CONF_OK; |
| } |