1 /*
2 * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18 * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 #ifndef DDEBUG
28 #define DDEBUG 0
29 #endif
30
31 #include "ngx_postgres_ddebug.h"
32 #include "ngx_postgres_module.h"
33 #include "ngx_postgres_rewrite.h"
34
35
ngx_postgres_find_variables(char * variables[10],char * url,int size)36 int ngx_postgres_find_variables(char *variables[10], char *url, int size) {
37 int vars = 0;
38
39 // find variables in redirect url
40
41 char *p;
42 for (p = url; p < url + size - 2; p++)
43 if (*p == ':' && *(p + 1) != '/')
44 variables[vars++] = (p + 1);
45
46 return vars;
47 }
48
ngx_postgres_find_values(char * values[10],char * variables[10],int vars,char * columned[10],ngx_postgres_ctx_t * pgctx,int find_error)49 char *ngx_postgres_find_values(char *values[10], char *variables[10], int vars, char *columned[10], ngx_postgres_ctx_t *pgctx, int find_error) {
50
51
52 PGresult *res = pgctx->res;
53
54 ngx_int_t col_count = pgctx->var_cols;
55 ngx_int_t row_count = pgctx->var_rows;
56
57 char *error = NULL;
58 int error_in_columns = 0;
59 int resolved = 0;
60
61
62 // check if returned columns match variable
63 ngx_int_t col;
64 for (col = 0; col < col_count; col++) {
65 char *col_name = PQfname(res, col);
66 ngx_int_t i;
67 for (i = 0; i < vars; i++) {
68 if (strncmp(variables[i], col_name, strlen(col_name)) == 0) {
69 if (!PQgetisnull(res, 0, col)) {
70 values[i] = PQgetvalue(res, 0, col);
71 columned[i] = values[i];
72 resolved++;
73 //fprintf(stdout, "Resolved variable [%s] to column %s\n", col_name, values[i]);
74 }
75 }
76 }
77 if (find_error) {
78 if (*col_name == 'e' && *(col_name+1) == 'r'&& *(col_name+2) == 'r'&& *(col_name+3) == 'o'&& *(col_name+4) == 'r') {
79 if (!PQgetisnull(res, 0, col)) {
80 error = PQgetvalue(res, 0, col);
81 }
82 error_in_columns = 1;
83 }
84 }
85 }
86
87 //fprintf(stdout, "Is error in column %d\n", error_in_columns);
88 //fprintf(stdout, "Resolved to columns %d\n", resolved);
89
90 int failed = 0;
91 if ((find_error && !error_in_columns) || resolved < vars) {
92 int current = -1;
93 //fprintf(stdout, "Scanning json %d\n", vars - resolved);
94
95 // find some json in pg results
96 ngx_int_t row;
97 for (row = 0; row < row_count && !failed; row++) {
98 ngx_int_t col;
99 for (col = 0; col < col_count && !failed; col++) {
100 if (!PQgetisnull(res, row, col)) {
101 char *value = PQgetvalue(res, row, col);
102 int size = PQgetlength(res, row, col);
103 char *p;
104 for (p = value; p < value + size; p++) {
105 //if not inside string
106 if (*p == '"') {
107 ngx_int_t i;
108 for (i = 0; i < vars; i++) {
109 if (values[i] != NULL) continue;
110 char *s, *k;
111 if (current == i) {
112 s = "value";
113 k = "value";
114 } else {
115 s = variables[i];
116 k = variables[i];
117 }
118 for (; *k == *(p + (k - s) + 1); k++) {
119 char *n = k + 1;
120 if (*n == '\0' || *n == '=' || *n == '&' || *n == '-' || *n == '%' || *n == '/' || *n == '$') {
121 if (*(p + (k - s) + 2) != '"') break;
122 //fprintf(stdout, "matched %s %d\n", p + (k - s) + 3, i);
123
124 values[i] = p + (k - s) + 3; // 2 quotes + 1 ahead
125 // skip space & colon
126 while (*values[i] == ' ' || *values[i] == ':' || *values[i] == '\n') values[i]++;
127
128 // {"name": "column", "value": "something"}
129 if (*values[i] == ',') {
130 //fprintf(stdout, "SETTING CURRENT %s\n", s);
131 values[i] = NULL;
132 current = i;
133 // {"column": "value"}
134 } else if (current == i) {
135 current = -1;
136 }
137 //fprintf(stdout, "matching %d %s\n %s\n", k - s, s, values[i]);
138 }
139 }
140 }
141 }
142
143
144 // find a key that looks like "errors": something
145 if (find_error && !error_in_columns &&
146 *p == 'e' && *(p+1) == 'r'&& *(p+2) == 'r'&& *(p+3) == 'o'&& *(p+4) == 'r') {
147 char *ch = (p + 5);
148 if (*ch == 's')
149 ch++;
150 while (*ch == ' ' || *ch == '\t') ch++;
151 if (*ch != '"') continue;
152 ch++;
153 if (*ch != ':') continue;
154 ch++;
155 while (*ch == ' ' || *ch == '\t') ch++;
156 if (*ch == 'n') continue;
157
158 error = ch;
159
160 //fprintf(stdout, "found error: %s\n", p);
161
162 failed = 1;
163 }
164 }
165 }
166 }
167 }
168 }
169
170 return error;
171 }
172
ngx_postgres_interpolate_url(char * redirect,int size,char * variables[10],int vars,char * columned[10],char * values[10],ngx_http_request_t * r)173 char *ngx_postgres_interpolate_url(char *redirect, int size, char *variables[10], int vars, char *columned[10], char *values[10], ngx_http_request_t *r) {
174
175 char url[512] = "";
176 ngx_memzero(url, 512);
177
178 int written = 0;
179 char *p;
180 for (p = redirect; p < redirect + size; p++) {
181
182 // substitute nginx variable
183 if (*p == '$') {
184 ngx_str_t url_variable;
185
186 url_variable.data = (u_char *) p + 1;
187 url_variable.len = 0;
188 //fprintf(stdout, "something here %s\n", p);
189
190 while(url_variable.len < (size_t) ((redirect + size) - (p + 1))) {
191 u_char *n = url_variable.data + url_variable.len;
192 if (*n == '\0' || *n == '=' || *n == '&' || *n == '-' || *n == '%' || *n == '/' || *n == '#' || *n == '?' || *n == ':')
193 break;
194 url_variable.len++;
195 }
196
197 ngx_int_t num = ngx_atoi(url_variable.data, url_variable.len);
198
199 // captures $1, $2
200 if (num != NGX_ERROR && num > 0 && (ngx_uint_t) num <= r->ncaptures) {
201
202 int *cap = r->captures;
203 int ncap = num * 2;
204
205 ngx_str_t capture;
206 capture.data = r->captures_data + cap[ncap];
207 capture.len = cap[ncap + 1] - cap[ncap];
208 size_t l;
209 for (l = 0; l < capture.len; l++) {
210 url[written] = *(capture.data + l);
211 written++;
212 }
213 //fprintf(stdout, "capture %d %s\n", capture.len, url);
214 // nginx variables
215 } else {
216 ngx_uint_t url_variable_hash = ngx_hash_key(url_variable.data, url_variable.len);
217 ngx_http_variable_value_t *url_value = ngx_http_get_variable( r, &url_variable, url_variable_hash );
218 ngx_uint_t l;
219 if (!url_value->not_found)
220 for (l = 0; l < url_value->len; l++) {
221 url[written++] = *(url_value->data + l);
222 }
223 //fprintf(stdout, "variable %s\n", url);
224 }
225 // skip variable
226 while (*p != '\0' && *p != '=' && *p != '&' && *p != '-' && *p != '%' && *p != '/' && *p != '#'&& *p != ':' && *p != '?') {
227 p++;
228 }
229 }
230
231 ngx_int_t i;
232 for (i= 0; i < vars; i++) {
233
234 if (variables[i] == p +1) {
235
236 // output value
237 if (values[i] != NULL) {
238 // fprintf(stdout, "OUTPUT VARIABLE%s\n", variables[i]);
239 char *n = values[i];
240 char *start = values[i];
241 if (*n == '"') {
242 start++;
243 n++;
244 // find string boundary
245 while (*n != '"' || *(n - 1) == '\\') {
246 n++;
247 }
248 // output external string
249 } else if (columned[i] != NULL) {
250 n += strlen(values[i]);
251 } else {
252 // find unquoted value boundary
253 while (*n != ',' && *n != ' ' && *n != '\n' && *n != '}' && *n != ']') {
254 n++;
255 }
256 }
257
258 int l = n - start;
259 int escape = ngx_escape_uri(NULL, (u_char *) start, l, NGX_ESCAPE_URI_COMPONENT);
260 ngx_escape_uri((u_char *) (url + written), (u_char *) start, l, NGX_ESCAPE_URI_COMPONENT);
261 //fprintf(stdout, "HERE VARIABLE%d\n%s\n", l, url + written);
262
263 written += l + escape * 3;
264 }
265 // skip variable
266 while (*p != '\0' && *p != '=' && *p != '&' && *p != '-' && *p != '%' && *p != '/' && *p != '#' && *p != '?') {
267 p++;
268 }
269
270 // Special case, ignore slash after variable if url already has query
271 if (*p == '/') {
272 int j = 0;
273 for (;j < written; j++) {
274 if (url[j] == '?') {
275 p++;
276 break;
277 }
278 }
279 }
280 continue;
281 }
282 }
283 url[written] = *p;
284 written++;
285
286
287 }
288 if (written)
289 url[written++] = '\0';
290
291 //fprintf(stdout, "HERE COMES URL %s\n", url);
292 char *m = ngx_pnalloc(r->pool, written);
293 memcpy(m, url, written);
294
295 return m;
296 }
297
298 ngx_int_t
ngx_postgres_rewrite(ngx_http_request_t * r,ngx_postgres_rewrite_conf_t * pgrcf,char * url)299 ngx_postgres_rewrite(ngx_http_request_t *r,
300 ngx_postgres_rewrite_conf_t *pgrcf, char *url)
301 {
302 ngx_postgres_rewrite_t *rewrite;
303 ngx_uint_t i;
304
305 dd("entering");
306
307 if (pgrcf->methods_set & r->method) {
308 /* method-specific */
309 rewrite = pgrcf->methods->elts;
310 for (i = 0; i < pgrcf->methods->nelts; i++) {
311 if (rewrite[i].key & r->method) {
312
313 if (rewrite[i].location.len > 0) {
314
315 // write template name into $html
316 // if location had no slashes and no variables (can't read template file by variable name)
317 if (ngx_strnstr(rewrite[i].location.data, "$", rewrite[i].location.len) == NULL &&
318 ngx_strnstr(rewrite[i].location.data, ":", rewrite[i].location.len) == NULL &&
319 ngx_strnstr(rewrite[i].location.data, ".html", rewrite[i].location.len) != NULL) {
320
321
322 ngx_str_t html_variable = ngx_string("html");
323 ngx_uint_t html_variable_hash = ngx_hash_key(html_variable.data, html_variable.len);
324 ngx_http_variable_value_t *raw_html = ngx_http_get_variable( r, &html_variable, html_variable_hash );
325
326 raw_html->len = rewrite[i].location.len;
327 raw_html->data = rewrite[i].location.data;
328
329 // bad request 400 on errors
330 // if i return 400 here, pg result is lost :( YF: FIXME
331 if (pgrcf->key % 2 == 1 && pgrcf->handler == &ngx_postgres_rewrite_valid) {
332 return 200;
333 } else {
334 return 200;
335 }
336 // redirect to outside url
337 } else {
338 // errors/no_errors rewriters already provide interpolated url,
339 // but others need to do it here
340 if (url == NULL) {
341 char *variables[10];
342 char *columned[10];
343 char *values[10];
344 int vars = ngx_postgres_find_variables(variables, (char *) rewrite[i].location.data, rewrite[i].location.len);
345 url = ngx_postgres_interpolate_url((char *) rewrite[i].location.data, rewrite[i].location.len, variables, vars, columned, values, r);
346 }
347
348 int len = strlen(url);
349
350 // redirect out
351 r->headers_out.location = ngx_list_push(&r->headers_out.headers);
352
353 u_char *m = ngx_pnalloc(r->pool, len + 1);
354 int written = 0;
355
356 // remove double // and /0/, leave ://
357 char *c;
358 for (c = url; c < url + len; c++) {
359 if (*c == '/' && (c == url || *(c - 1) != ':')) {
360 if (*(c + 1) == '/')
361 continue;
362 if (*(c + 1) == '0' && *(c + 2) == '/') {
363 c++;
364 continue;
365 }
366 }
367 m[written++] = *c;
368 }
369 m[written] = '\0';
370 r->headers_out.location->value.data = (u_char *) m;
371 r->headers_out.location->value.len = written;
372 r->headers_out.location->hash = 1;
373 ngx_str_set(&r->headers_out.location->key, "Location");
374
375 return 303;
376 }
377 }
378 dd("returning status:%d", (int) rewrite[i].status);
379 return rewrite[i].status;
380 }
381 }
382 } else if (pgrcf->def) {
383 /* default */
384 dd("returning status:%d", (int) pgrcf->def->status);
385 return pgrcf->def->status;
386 }
387
388 dd("returning NGX_DECLINED");
389 return NGX_DECLINED;
390 }
391
392 ngx_int_t
ngx_postgres_rewrite_changes(ngx_http_request_t * r,ngx_postgres_rewrite_conf_t * pgrcf)393 ngx_postgres_rewrite_changes(ngx_http_request_t *r,
394 ngx_postgres_rewrite_conf_t *pgrcf)
395 {
396 ngx_postgres_ctx_t *pgctx;
397
398 dd("entering");
399
400 pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
401
402 if ((pgrcf->key % 2 == 0) && (pgctx->var_affected == 0)) {
403 /* no_changes */
404 dd("returning");
405 return ngx_postgres_rewrite(r, pgrcf, NULL);
406 }
407
408 if ((pgrcf->key % 2 == 1) && (pgctx->var_affected > 0)) {
409 /* changes */
410 dd("returning");
411 return ngx_postgres_rewrite(r, pgrcf, NULL);
412 }
413
414 dd("returning NGX_DECLINED");
415 return NGX_DECLINED;
416 }
417
418 ngx_int_t
ngx_postgres_rewrite_rows(ngx_http_request_t * r,ngx_postgres_rewrite_conf_t * pgrcf)419 ngx_postgres_rewrite_rows(ngx_http_request_t *r,
420 ngx_postgres_rewrite_conf_t *pgrcf)
421 {
422 ngx_postgres_ctx_t *pgctx;
423
424 dd("entering");
425
426 pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
427
428 if ((pgrcf->key % 2 == 0) && (pgctx->var_rows == 0)) {
429 /* no_rows */
430 dd("returning");
431 return ngx_postgres_rewrite(r, pgrcf, NULL);
432 }
433
434 if ((pgrcf->key % 2 == 1) && (pgctx->var_rows > 0)) {
435 /* rows */
436 dd("returning");
437 return ngx_postgres_rewrite(r, pgrcf, NULL);
438 }
439
440 dd("returning NGX_DECLINED");
441 return NGX_DECLINED;
442 }
443
444 ngx_int_t
ngx_postgres_rewrite_valid(ngx_http_request_t * r,ngx_postgres_rewrite_conf_t * pgrcf)445 ngx_postgres_rewrite_valid(ngx_http_request_t *r,
446 ngx_postgres_rewrite_conf_t *pgrcf)
447 {
448 ngx_postgres_ctx_t *pgctx;
449 dd("entering");
450
451 pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
452
453 ngx_str_t redirect;
454 redirect.len = 0;
455
456 char *variables[10];
457 char *columned[10];
458 char *values[10];
459
460 ngx_postgres_rewrite_t *rewrite;
461 ngx_uint_t i;
462
463 for (i = 0; i < 10; i++)
464 {
465 values[i] = columned[i] = variables[i] = NULL;
466 }
467
468 // find callback
469 if (pgrcf->methods_set & r->method) {
470 rewrite = pgrcf->methods->elts;
471 for (i = 0; i < pgrcf->methods->nelts; i++)
472 if (rewrite[i].key & r->method)
473 if (rewrite[i].location.len > 0) {
474 redirect.data = rewrite[i].location.data;
475 redirect.len = rewrite[i].location.len;
476 break;
477 }
478 }
479
480 int vars = 0;
481 if (redirect.len > 0) {
482 vars = ngx_postgres_find_variables(variables, (char *) redirect.data, redirect.len);
483 }
484 // when interpolating redirect url, also look for errors
485 char *error = ngx_postgres_find_values(values, variables, vars, columned, pgctx, 1);
486 char *url = NULL;
487 if (redirect.len > 0) {
488 url = ngx_postgres_interpolate_url((char *) redirect.data, redirect.len, variables, vars, columned, values, r);
489 }
490
491 if ((pgrcf->key % 2 == 0) && error == NULL) {
492 /* no_rows */
493 dd("returning");
494 //fprintf(stdout, "Valid: redirect1%s\n", url);
495 return ngx_postgres_rewrite(r, pgrcf, url);
496 }
497
498 if ((pgrcf->key % 2 == 1) && error != NULL) {
499 /* rows */
500 dd("returning");
501 //fprintf(stdout, "Invalid: %s\n", url);
502 return ngx_postgres_rewrite(r, pgrcf, url);
503 }
504
505 dd("returning NGX_DECLINED");
506 return NGX_DECLINED;
507 }
508