Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Major rewrite of "postgres_escape" directive.

It is now possible to:
- escape variable in-place,
- escape both anonymous and named regex captures,
- safely re-use same $escaped variable name in different locations,
- concatenate multiple variables/strings and escape them into single variable.

All this comes at the price of not being able to tell the difference between
empty string and non-existing value, which means that starting with this commit
all empty strings will be escaped to NULL value. This behavior can be disabled
by prefixing unescaped value with '=' sign.
  • Loading branch information...
commit 4d061fed0aded9502c765d0876cfc10432e154af 1 parent 280f591
@PiotrSikora PiotrSikora authored
View
8 README.md
@@ -127,13 +127,17 @@ This directive can be used more than once within same context.
postgres_escape
---------------
-* **syntax**: `postgres_escape $escaped $unescaped`
+* **syntax**: `postgres_escape $escaped [[=]$unescaped]`
* **default**: `none`
* **context**: `http`, `server`, `location`
-Escape and quote `$unescaped` variable. Result is stored in `$escaped` variable
+Escape and quote `$unescaped` string. Result is stored in `$escaped` variable
which can be safely used in SQL queries.
+Because nginx cannot tell the difference between empty and non-existing strings,
+all empty strings are by default escaped to `NULL` value. This behavior can be
+disabled by prefixing `$unescaped` string with `=` sign.
+
postgres_connect_timeout
------------------------
View
69 src/ngx_postgres_escape.c
@@ -32,49 +32,62 @@
#include <libpq-fe.h>
-ngx_int_t
-ngx_postgres_escape_string(ngx_http_request_t *r,
- ngx_http_variable_value_t *v, uintptr_t data)
+uintptr_t ngx_postgres_script_exit_code = (uintptr_t) NULL;
+
+
+void
+ngx_postgres_escape_string(ngx_http_script_engine_t *e)
{
- ngx_postgres_escape_t *pgesc = (ngx_postgres_escape_t *) data;
- ngx_http_variable_value_t *src;
- u_char *p;
+ ngx_postgres_escape_t *pge;
+ ngx_http_variable_value_t *v;
+ u_char *p, *s;
- dd("entering: \"$%.*s\"", (int) pgesc->var->name.len,
- pgesc->var->name.data);
+ v = e->sp - 1;
- src = ngx_http_get_indexed_variable(r, pgesc->idx);
+ dd("entering: \"%.*s\"", (int) v->len, v->data);
- v->valid = 1;
- v->no_cacheable = 0;
- v->not_found = 0;
+ pge = (ngx_postgres_escape_t *) e->ip;
+ e->ip += sizeof(ngx_postgres_escape_t);
- if ((src == NULL) || (src->not_found)) {
+ if ((v == NULL) || (v->not_found)) {
v->data = (u_char *) "NULL";
v->len = sizeof("NULL") - 1;
- dd("returning NGX_OK (NULL)");
- return NGX_OK;
+ dd("returning (NULL)");
+ goto done;
}
- if (src->len == 0) {
- v->data = (u_char *) "''";
- v->len = 2;
- dd("returning NGX_OK (empty)");
- return NGX_OK;
+ if (v->len == 0) {
+ if (pge->empty) {
+ v->data = (u_char *) "''";
+ v->len = 2;
+ dd("returning (empty/empty)");
+ goto done;
+ } else {
+ v->data = (u_char *) "NULL";
+ v->len = sizeof("NULL") - 1;
+ dd("returning (empty/NULL)");
+ goto done;
+ }
}
- v->data = ngx_pnalloc(r->pool, 2 * src->len + 2);
- if (v->data == NULL) {
- dd("returning NGX_ERROR");
- return NGX_ERROR;
+ s = p = ngx_pnalloc(e->request->pool, 2 * v->len + 2);
+ if (p == NULL) {
+ e->ip = (u_char *) &ngx_postgres_script_exit_code;
+ e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ dd("returning (NGX_HTTP_INTERNAL_SERVER_ERROR)");
+ return;
}
- p = v->data;
*p++ = '\'';
- v->len = PQescapeString((char *) p, (const char *) src->data, src->len);
+ v->len = PQescapeString((char *) p, (const char *) v->data, v->len);
p[v->len] = '\'';
v->len += 2;
+ v->data = s;
- dd("returning NGX_OK");
- return NGX_OK;
+ dd("returning");
+
+done:
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
}
View
3  src/ngx_postgres_escape.h
@@ -31,7 +31,6 @@
#include <ngx_http.h>
-ngx_int_t ngx_postgres_escape_string(ngx_http_request_t *,
- ngx_http_variable_value_t *, uintptr_t);
+void ngx_postgres_escape_string(ngx_http_script_engine_t *);
#endif /* _NGX_POSTGRES_ESCAPE_H_ */
View
124 src/ngx_postgres_module.c
@@ -94,7 +94,7 @@ static ngx_command_t ngx_postgres_module_commands[] = {
NULL },
{ ngx_string("postgres_escape"),
- NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
ngx_postgres_conf_escape,
NGX_HTTP_LOC_CONF_OFFSET,
0,
@@ -941,8 +941,8 @@ ngx_postgres_conf_rewrite(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
if (to.data[0] == '=') {
keep_body = 1;
- to.data++;
to.len--;
+ to.data++;
} else {
keep_body = 0;
}
@@ -1141,7 +1141,7 @@ ngx_postgres_conf_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
/*
* Check if "$variable" was previously defined,
* back-off even if it was marked as "CHANGEABLE".
- */
+ */
if (pgvar->var->get_handler != NULL) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"postgres: variable \"$%V\" is duplicate"
@@ -1206,18 +1206,50 @@ ngx_postgres_conf_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
return NGX_CONF_OK;
}
+/*
+ * Based on: ngx_http_rewrite_module.c/ngx_http_rewrite_set
+ * Copyright (C) Igor Sysoev
+ */
char *
ngx_postgres_conf_escape(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
- ngx_str_t *value = cf->args->elts;
- ngx_str_t dst = value[1];
- ngx_str_t src = value[2];
- ngx_postgres_escape_t *pgesc;
- ngx_int_t idx;
+ ngx_str_t *value = cf->args->elts;
+ ngx_str_t src = value[cf->args->nelts - 1];
+ ngx_int_t index;
+ ngx_http_variable_t *v;
+ ngx_http_script_var_code_t *vcode;
+ ngx_http_script_var_handler_code_t *vhcode;
+ ngx_postgres_rewrite_loc_conf_t *rlcf;
+ ngx_postgres_escape_t *pge;
+ ngx_str_t dst;
+ ngx_uint_t empty;
dd("entering");
- if ((dst.len < 2) || (src.len < 2)) {
+ if ((src.len != 0) && (src.data[0] == '=')) {
+ empty = 1;
+ src.len--;
+ src.data++;
+ } else {
+ empty = 0;
+ }
+
+ if (src.len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: empty value in \"%V\" directive",
+ &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ if (cf->args->nelts == 2) {
+ dst = src;
+ } else {
+ dst = value[1];
+ }
+
+ if (dst.len < 2) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"postgres: empty variable name in \"%V\" directive",
&cmd->name);
@@ -1238,63 +1270,69 @@ ngx_postgres_conf_escape(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
dst.len--;
dst.data++;
- if (src.data[0] != '$') {
- ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
- "postgres: invalid variable name \"%V\""
- " in \"%V\" directive", &src, &cmd->name);
-
+ v = ngx_http_add_variable(cf, &dst, NGX_HTTP_VAR_CHANGEABLE);
+ if (v == NULL) {
dd("returning NGX_CONF_ERROR");
return NGX_CONF_ERROR;
}
- src.len--;
- src.data++;
-
- pgesc = ngx_palloc(cf->pool, sizeof(ngx_postgres_escape_t));
- if (pgesc == NULL) {
+ index = ngx_http_get_variable_index(cf, &dst);
+ if (index == NGX_ERROR) {
dd("returning NGX_CONF_ERROR");
return NGX_CONF_ERROR;
}
- pgesc->var = ngx_http_add_variable(cf, &dst, 0);
- if (pgesc->var == NULL) {
- dd("returning NGX_CONF_ERROR");
- return NGX_CONF_ERROR;
+ if (v->get_handler == NULL
+ && ngx_strncasecmp(dst.data, (u_char *) "http_", 5) != 0
+ && ngx_strncasecmp(dst.data, (u_char *) "sent_http_", 10) != 0
+ && ngx_strncasecmp(dst.data, (u_char *) "upstream_http_", 14) != 0)
+ {
+ v->get_handler = ngx_postgres_rewrite_var;
+ v->data = index;
}
- /* idx of escaped variable */
- idx = ngx_http_get_variable_index(cf, &dst);
- if (idx == NGX_ERROR) {
+ rlcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_rewrite_module);
+
+ if (ngx_postgres_rewrite_value(cf, rlcf, &src) != NGX_CONF_OK) {
dd("returning NGX_CONF_ERROR");
return NGX_CONF_ERROR;
}
- /* idx of unescaped variable */
- pgesc->idx = ngx_http_get_variable_index(cf, &src);
- if (pgesc->idx == NGX_ERROR) {
- ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
- "postgres: unknown variable \"$%V\""
- " in \"%V\" directive", &src, &cmd->name);
-
+ pge = ngx_http_script_start_code(cf->pool, &rlcf->codes,
+ sizeof(ngx_postgres_escape_t));
+ if (pge == NULL) {
dd("returning NGX_CONF_ERROR");
return NGX_CONF_ERROR;
}
- /*
- * Check if "$variable" was previously defined,
- * back-off even if it was marked as "CHANGEABLE".
- */
- if (pgesc->var->get_handler != NULL) {
- ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
- "postgres: variable \"$%V\" is duplicate"
- " in \"%V\" directive", &dst, &cmd->name);
+ pge->code = ngx_postgres_escape_string;
+ pge->empty = empty;
+
+ if (v->set_handler) {
+ vhcode = ngx_http_script_start_code(cf->pool, &rlcf->codes,
+ sizeof(ngx_http_script_var_handler_code_t));
+ if (vhcode == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ vhcode->code = ngx_http_script_var_set_handler_code;
+ vhcode->handler = v->set_handler;
+ vhcode->data = v->data;
+
+ dd("returning NGX_CONF_OK");
+ return NGX_CONF_OK;
+ }
+ vcode = ngx_http_script_start_code(cf->pool, &rlcf->codes,
+ sizeof(ngx_http_script_var_code_t));
+ if (vcode == NULL) {
dd("returning NGX_CONF_ERROR");
return NGX_CONF_ERROR;
}
- pgesc->var->get_handler = ngx_postgres_escape_string;
- pgesc->var->data = (uintptr_t) pgesc;
+ vcode->code = ngx_http_script_set_var_code;
+ vcode->index = (uintptr_t) index;
dd("returning NGX_CONF_OK");
return NGX_CONF_OK;
View
4 src/ngx_postgres_module.h
@@ -39,8 +39,8 @@ extern ngx_module_t ngx_postgres_module;
typedef struct {
- ngx_int_t idx;
- ngx_http_variable_t *var;
+ ngx_http_script_code_pt code;
+ ngx_uint_t empty;
} ngx_postgres_escape_t;
typedef struct {
View
90 src/ngx_postgres_util.c
@@ -318,3 +318,93 @@ ngx_postgres_upstream_test_connect(ngx_connection_t *c)
dd("returning NGX_OK");
return NGX_OK;
}
+
+ngx_int_t
+ngx_postgres_rewrite_var(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ngx_http_variable_t *var;
+ ngx_http_core_main_conf_t *cmcf;
+ ngx_postgres_rewrite_loc_conf_t *rlcf;
+
+ rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rewrite_module);
+
+ if (rlcf->uninitialized_variable_warn == 0) {
+ *v = ngx_http_variable_null_value;
+ return NGX_OK;
+ }
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ var = cmcf->variables.elts;
+
+ /*
+ * the ngx_http_rewrite_module sets variables directly in r->variables,
+ * and they should be handled by ngx_http_get_indexed_variable(),
+ * so the handler is called only if the variable is not initialized
+ */
+
+ ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+ "using uninitialized \"%V\" variable", &var[data].name);
+
+ *v = ngx_http_variable_null_value;
+
+ return NGX_OK;
+}
+
+char *
+ngx_postgres_rewrite_value(ngx_conf_t *cf, ngx_postgres_rewrite_loc_conf_t *lcf,
+ ngx_str_t *value)
+{
+ ngx_int_t n;
+ ngx_http_script_compile_t sc;
+ ngx_http_script_value_code_t *val;
+ ngx_http_script_complex_value_code_t *complex;
+
+ n = ngx_http_script_variables_count(value);
+
+ if (n == 0) {
+ val = ngx_http_script_start_code(cf->pool, &lcf->codes,
+ sizeof(ngx_http_script_value_code_t));
+ if (val == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ n = ngx_atoi(value->data, value->len);
+
+ if (n == NGX_ERROR) {
+ n = 0;
+ }
+
+ val->code = ngx_http_script_value_code;
+ val->value = (uintptr_t) n;
+ val->text_len = (uintptr_t) value->len;
+ val->text_data = (uintptr_t) value->data;
+
+ return NGX_CONF_OK;
+ }
+
+ complex = ngx_http_script_start_code(cf->pool, &lcf->codes,
+ sizeof(ngx_http_script_complex_value_code_t));
+ if (complex == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ complex->code = ngx_http_script_complex_value_code;
+ complex->lengths = NULL;
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = value;
+ sc.lengths = &complex->lengths;
+ sc.values = &lcf->codes;
+ sc.variables = n;
+ sc.complete_lengths = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
View
18 src/ngx_postgres_util.h
@@ -33,10 +33,28 @@
#include <ngx_http.h>
+extern ngx_module_t ngx_http_rewrite_module;
+
+
+typedef struct {
+ ngx_array_t *codes; /* uintptr_t */
+
+ ngx_uint_t stack_size;
+
+ ngx_flag_t log;
+ ngx_flag_t uninitialized_variable_warn;
+} ngx_postgres_rewrite_loc_conf_t;
+
+
void ngx_postgres_upstream_finalize_request(ngx_http_request_t *,
ngx_http_upstream_t *, ngx_int_t);
void ngx_postgres_upstream_next(ngx_http_request_t *,
ngx_http_upstream_t *, ngx_int_t);
ngx_int_t ngx_postgres_upstream_test_connect(ngx_connection_t *);
+ngx_int_t ngx_postgres_rewrite_var(ngx_http_request_t *,
+ ngx_http_variable_value_t *, uintptr_t);
+char *ngx_postgres_rewrite_value(ngx_conf_t *,
+ ngx_postgres_rewrite_loc_conf_t *, ngx_str_t *);
+
#endif /* _NGX_POSTGRES_UTIL_H_ */
View
229 t/escape.t
@@ -95,7 +95,7 @@ GET /test
--- response_headers
Content-Type: text/plain
--- response_body
-''
+NULL
--- timeout: 10
@@ -132,3 +132,230 @@ Content-Type: text/plain
--- response_body
'he''llo!'
--- timeout: 10
+
+
+
+=== TEST 8: NULL (empty)
+--- config
+ location /test {
+ postgres_escape $escaped =$remote_user;
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+''
+--- timeout: 10
+
+
+
+=== TEST 9: empty string (empty)
+--- config
+ location /test {
+ set $empty "";
+ postgres_escape $escaped =$empty;
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+''
+--- timeout: 10
+
+
+
+=== TEST 10: in-place escape
+--- config
+ location /test {
+ set $test "t'\\est";
+ postgres_escape $test;
+ echo $test;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+'t''\\est'
+--- timeout: 10
+
+
+
+=== TEST 11: re-useable variable name (test1)
+--- config
+ location /test1 {
+ set $a "a";
+ postgres_escape $escaped $a;
+ echo $escaped;
+ }
+ location /test2 {
+ set $b "b";
+ postgres_escape $escaped $b;
+ echo $escaped;
+ }
+--- request
+GET /test1
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+'a'
+--- timeout: 10
+
+
+
+=== TEST 12: re-useable variable name (test2)
+--- config
+ location /test1 {
+ set $a "a";
+ postgres_escape $escaped $a;
+ echo $escaped;
+ }
+ location /test2 {
+ set $b "b";
+ postgres_escape $escaped $b;
+ echo $escaped;
+ }
+--- request
+GET /test2
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+'b'
+--- timeout: 10
+
+
+
+=== TEST 13: concatenate multiple sources
+--- config
+ location /test {
+ set $test "t'\\est";
+ set $hello " he'llo";
+ postgres_escape $escaped "$test$hello world!";
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+'t''\\est he''llo world!'
+--- timeout: 10
+
+
+
+=== TEST 14: concatenate multiple empty sources
+--- config
+ location /test {
+ set $a "";
+ set $b "";
+ postgres_escape $escaped "$a$b";
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+NULL
+--- timeout: 10
+
+
+
+=== TEST 15: concatenate multiple empty sources (empty)
+--- config
+ location /test {
+ set $a "";
+ set $b "";
+ postgres_escape $escaped "=$a$b";
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+''
+--- timeout: 10
+
+
+
+=== TEST 16: in-place escape on empty string
+--- config
+ location /test {
+ set $test "";
+ postgres_escape $test;
+ echo $test;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+NULL
+--- timeout: 10
+
+
+
+=== TEST 17: in-place escape on empty string (empty)
+--- config
+ location /test {
+ set $test "";
+ postgres_escape =$test;
+ echo $test;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+''
+--- timeout: 10
+
+
+
+=== TEST 18: escape anonymous regex capture
+--- config
+ location ~ /(.*) {
+ postgres_escape $escaped $1;
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+'test'
+--- timeout: 10
+
+
+
+=== TEST 19: escape named regex capture
+--- config
+ location ~ /(?<test>.*) {
+ postgres_escape $escaped $test;
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+'test'
+--- timeout: 10
+--- skip_nginx: 3: < 0.8.25
Please sign in to comment.
Something went wrong with that request. Please try again.