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