upstream nginx-0.7.35
[nginx.git] / nginx / src / http / modules / ngx_http_referer_module.c
1
2 /*
3  * Copyright (C) Igor Sysoev
4  */
5
6
7 #include <ngx_config.h>
8 #include <ngx_core.h>
9 #include <ngx_http.h>
10
11
12 #define NGX_HTTP_REFERER_NO_URI_PART  ((void *) 4)
13
14 #if !(NGX_PCRE)
15
16 #define ngx_regex_t          void
17
18 #endif
19
20
21 typedef struct {
22     ngx_hash_combined_t      hash;
23
24 #if (NGX_PCRE)
25     ngx_array_t             *regex;
26 #endif
27
28     ngx_flag_t               no_referer;
29     ngx_flag_t               blocked_referer;
30
31     ngx_hash_keys_arrays_t  *keys;
32 } ngx_http_referer_conf_t;
33
34
35 static void * ngx_http_referer_create_conf(ngx_conf_t *cf);
36 static char * ngx_http_referer_merge_conf(ngx_conf_t *cf, void *parent,
37     void *child);
38 static char *ngx_http_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd,
39     void *conf);
40 static char *ngx_http_add_referer(ngx_conf_t *cf, ngx_hash_keys_arrays_t *keys,
41     ngx_str_t *value, ngx_str_t *uri);
42 static char *ngx_http_add_regex_referer(ngx_conf_t *cf,
43     ngx_http_referer_conf_t *rlcf, ngx_str_t *name, ngx_regex_t *regex);
44 static int ngx_libc_cdecl ngx_http_cmp_referer_wildcards(const void *one,
45     const void *two);
46
47
48 static ngx_command_t  ngx_http_referer_commands[] = {
49
50     { ngx_string("valid_referers"),
51       NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
52       ngx_http_valid_referers,
53       NGX_HTTP_LOC_CONF_OFFSET,
54       0,
55       NULL },
56
57       ngx_null_command
58 };
59
60
61 static ngx_http_module_t  ngx_http_referer_module_ctx = {
62     NULL,                                  /* preconfiguration */
63     NULL,                                  /* postconfiguration */
64
65     NULL,                                  /* create main configuration */
66     NULL,                                  /* init main configuration */
67
68     NULL,                                  /* create server configuration */
69     NULL,                                  /* merge server configuration */
70
71     ngx_http_referer_create_conf,          /* create location configuration */
72     ngx_http_referer_merge_conf            /* merge location configuration */
73 };
74
75
76 ngx_module_t  ngx_http_referer_module = {
77     NGX_MODULE_V1,
78     &ngx_http_referer_module_ctx,          /* module context */
79     ngx_http_referer_commands,             /* module directives */
80     NGX_HTTP_MODULE,                       /* module type */
81     NULL,                                  /* init master */
82     NULL,                                  /* init module */
83     NULL,                                  /* init process */
84     NULL,                                  /* init thread */
85     NULL,                                  /* exit thread */
86     NULL,                                  /* exit process */
87     NULL,                                  /* exit master */
88     NGX_MODULE_V1_PADDING
89 };
90
91
92 static ngx_int_t
93 ngx_http_referer_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
94      uintptr_t data)
95 {
96     u_char                    *p, *ref, *last;
97     size_t                     len;
98     ngx_str_t                 *uri;
99     ngx_uint_t                 i, key;
100     ngx_http_referer_conf_t   *rlcf;
101     u_char                     buf[256];
102
103     rlcf = ngx_http_get_module_loc_conf(r, ngx_http_referer_module);
104
105     if (rlcf->hash.hash.buckets == NULL
106         && rlcf->hash.wc_head == NULL
107         && rlcf->hash.wc_tail == NULL
108 #if (NGX_PCRE)
109         && rlcf->regex == NULL
110 #endif
111        )
112     {
113         goto valid;
114     }
115
116     if (r->headers_in.referer == NULL) {
117         if (rlcf->no_referer) {
118             goto valid;
119         }
120
121         goto invalid;
122     }
123
124     len = r->headers_in.referer->value.len;
125     ref = r->headers_in.referer->value.data;
126
127     if (len < sizeof("http://i.ru") - 1
128         || (ngx_strncasecmp(ref, (u_char *) "http://", 7) != 0))
129     {
130         if (rlcf->blocked_referer) {
131             goto valid;
132         }
133
134         goto invalid;
135     }
136
137     last = ref + len;
138     ref += 7;
139     i = 0;
140     key = 0;
141
142     for (p = ref; p < last; p++) {
143         if (*p == '/' || *p == ':') {
144             break;
145         }
146
147         buf[i] = ngx_tolower(*p);
148         key = ngx_hash(key, buf[i++]);
149
150         if (i == 256) {
151             goto invalid;
152         }
153     }
154
155     uri = ngx_hash_find_combined(&rlcf->hash, key, buf, p - ref);
156
157     if (uri) {
158         goto uri;
159     }
160
161 #if (NGX_PCRE)
162
163     if (rlcf->regex) {
164         ngx_int_t  rc;
165         ngx_str_t  referer;
166
167         referer.len = len - 7;
168         referer.data = ref;
169
170         rc = ngx_regex_exec_array(rlcf->regex, &referer, r->connection->log);
171
172         if (rc == NGX_OK) {
173             goto valid;
174         }
175
176         if (rc == NGX_ERROR) {
177             return rc;
178         }
179
180         /* NGX_DECLINED */
181     }
182
183 #endif
184
185 invalid:
186
187     *v = ngx_http_variable_true_value;
188
189     return NGX_OK;
190
191 uri:
192
193     for ( /* void */ ; p < last; p++) {
194         if (*p == '/') {
195             break;
196         }
197     }
198
199     len = last - p;
200
201     if (uri == NGX_HTTP_REFERER_NO_URI_PART) {
202         goto valid;
203     }
204
205     if (len < uri->len || ngx_strncmp(uri->data, p, uri->len) != 0) {
206         goto invalid;
207     }
208
209 valid:
210
211     *v = ngx_http_variable_null_value;
212
213     return NGX_OK;
214 }
215
216
217 static void *
218 ngx_http_referer_create_conf(ngx_conf_t *cf)
219 {
220     ngx_http_referer_conf_t  *conf;
221
222     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_referer_conf_t));
223     if (conf == NULL) {
224         return NGX_CONF_ERROR;
225     }
226
227 #if (NGX_PCRE)
228     conf->regex = NGX_CONF_UNSET_PTR;
229 #endif
230
231     conf->no_referer = NGX_CONF_UNSET;
232     conf->blocked_referer = NGX_CONF_UNSET;
233
234     return conf;
235 }
236
237
238 static char *
239 ngx_http_referer_merge_conf(ngx_conf_t *cf, void *parent, void *child)
240 {
241     ngx_http_referer_conf_t *prev = parent;
242     ngx_http_referer_conf_t *conf = child;
243
244     ngx_hash_init_t  hash;
245
246     if (conf->keys == NULL) {
247         conf->hash = prev->hash;
248
249 #if (NGX_PCRE)
250         ngx_conf_merge_ptr_value(conf->regex, prev->regex, NULL);
251 #endif
252         ngx_conf_merge_value(conf->no_referer, prev->no_referer, 0);
253         ngx_conf_merge_value(conf->blocked_referer, prev->blocked_referer, 0);
254
255         return NGX_CONF_OK;
256     }
257
258     if ((conf->no_referer == 1 || conf->blocked_referer == 1)
259         && conf->keys->keys.nelts == 0
260         && conf->keys->dns_wc_head.nelts == 0
261         && conf->keys->dns_wc_tail.nelts == 0)
262     {
263         ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
264                       "the \"none\" or \"blocked\" referers are specified "
265                       "in the \"valid_referers\" directive "
266                       "without any valid referer");
267         return NGX_CONF_ERROR;
268     }
269
270     hash.key = ngx_hash_key_lc;
271     hash.max_size = 2048; /* TODO: referer_hash_max_size; */
272     hash.bucket_size = 64; /* TODO: referer_hash_bucket_size; */
273     hash.name = "referers_hash";
274     hash.pool = cf->pool;
275
276     if (conf->keys->keys.nelts) {
277         hash.hash = &conf->hash.hash;
278         hash.temp_pool = NULL;
279
280         if (ngx_hash_init(&hash, conf->keys->keys.elts, conf->keys->keys.nelts)
281             != NGX_OK)
282         {
283             return NGX_CONF_ERROR;
284         }
285     }
286
287     if (conf->keys->dns_wc_head.nelts) {
288
289         ngx_qsort(conf->keys->dns_wc_head.elts,
290                   (size_t) conf->keys->dns_wc_head.nelts,
291                   sizeof(ngx_hash_key_t),
292                   ngx_http_cmp_referer_wildcards);
293
294         hash.hash = NULL;
295         hash.temp_pool = cf->temp_pool;
296
297         if (ngx_hash_wildcard_init(&hash, conf->keys->dns_wc_head.elts,
298                                    conf->keys->dns_wc_head.nelts)
299             != NGX_OK)
300         {
301             return NGX_CONF_ERROR;
302         }
303
304         conf->hash.wc_head = (ngx_hash_wildcard_t *) hash.hash;
305     }
306
307     if (conf->keys->dns_wc_tail.nelts) {
308
309         ngx_qsort(conf->keys->dns_wc_tail.elts,
310                   (size_t) conf->keys->dns_wc_tail.nelts,
311                   sizeof(ngx_hash_key_t),
312                   ngx_http_cmp_referer_wildcards);
313
314         hash.hash = NULL;
315         hash.temp_pool = cf->temp_pool;
316
317         if (ngx_hash_wildcard_init(&hash, conf->keys->dns_wc_tail.elts,
318                                    conf->keys->dns_wc_tail.nelts)
319             != NGX_OK)
320         {
321             return NGX_CONF_ERROR;
322         }
323
324         conf->hash.wc_tail = (ngx_hash_wildcard_t *) hash.hash;
325     }
326
327 #if (NGX_PCRE)
328     ngx_conf_merge_ptr_value(conf->regex, prev->regex, NULL);
329 #endif
330
331     if (conf->no_referer == NGX_CONF_UNSET) {
332         conf->no_referer = 0;
333     }
334
335     if (conf->blocked_referer == NGX_CONF_UNSET) {
336         conf->blocked_referer = 0;
337     }
338
339     conf->keys = NULL;
340
341     return NGX_CONF_OK;
342 }
343
344
345 static char *
346 ngx_http_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
347 {
348     ngx_http_referer_conf_t  *rlcf = conf;
349
350     u_char                    *p;
351     ngx_str_t                 *value, uri, name;
352     ngx_uint_t                 i, n;
353     ngx_http_variable_t       *var;
354     ngx_http_server_name_t    *sn;
355     ngx_http_core_srv_conf_t  *cscf;
356
357     name.len = sizeof("invalid_referer") - 1;
358     name.data = (u_char *) "invalid_referer";
359
360     var = ngx_http_add_variable(cf, &name,
361                                 NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOHASH);
362     if (var == NULL) {
363         return NGX_CONF_ERROR;
364     }
365
366     var->get_handler = ngx_http_referer_variable;
367
368     if (rlcf->keys == NULL) {
369         rlcf->keys = ngx_pcalloc(cf->temp_pool, sizeof(ngx_hash_keys_arrays_t));
370         if (rlcf->keys == NULL) {
371             return NGX_CONF_ERROR;
372         }
373
374         rlcf->keys->pool = cf->pool;
375         rlcf->keys->temp_pool = cf->pool;
376
377         if (ngx_hash_keys_array_init(rlcf->keys, NGX_HASH_SMALL) != NGX_OK) {
378             return NGX_CONF_ERROR;
379         }
380     }
381
382     value = cf->args->elts;
383
384     for (i = 1; i < cf->args->nelts; i++) {
385         if (value[i].len == 0) {
386             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
387                                "invalid referer \"%V\"", &value[i]);
388             return NGX_CONF_ERROR;
389         }
390
391         if (ngx_strcmp(value[i].data, "none") == 0) {
392             rlcf->no_referer = 1;
393             continue;
394         }
395
396         if (ngx_strcmp(value[i].data, "blocked") == 0) {
397             rlcf->blocked_referer = 1;
398             continue;
399         }
400
401         uri.len = 0;
402         uri.data = NULL;
403
404         if (ngx_strcmp(value[i].data, "server_names") == 0) {
405
406             cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module);
407
408             sn = cscf->server_names.elts;
409             for (n = 0; n < cscf->server_names.nelts; n++) {
410
411 #if (NGX_PCRE)
412                 if (sn[n].regex) {
413
414                     if (ngx_http_add_regex_referer(cf, rlcf, &sn[n].name,
415                                                    sn[n].regex)
416                         != NGX_OK)
417                     {
418                         return NGX_CONF_ERROR;
419                     }
420
421                     continue;
422                 }
423 #endif
424
425                 if (ngx_http_add_referer(cf, rlcf->keys, &sn[n].name, &uri)
426                     != NGX_OK)
427                 {
428                     return NGX_CONF_ERROR;
429                 }
430             }
431
432             continue;
433         }
434
435         if (value[i].data[0] == '~') {
436             if (ngx_http_add_regex_referer(cf, rlcf, &value[i], NULL) != NGX_OK)
437             {
438                 return NGX_CONF_ERROR;
439             }
440
441             continue;
442         }
443
444         p = (u_char *) ngx_strchr(value[i].data, '/');
445
446         if (p) {
447             uri.len = (value[i].data + value[i].len) - p;
448             uri.data = p;
449             value[i].len = p - value[i].data;
450         }
451
452         if (ngx_http_add_referer(cf, rlcf->keys, &value[i], &uri) != NGX_OK) {
453             return NGX_CONF_ERROR;
454         }
455     }
456
457     return NGX_CONF_OK;
458 }
459
460
461 static char *
462 ngx_http_add_referer(ngx_conf_t *cf, ngx_hash_keys_arrays_t *keys,
463     ngx_str_t *value, ngx_str_t *uri)
464 {
465     ngx_int_t   rc;
466     ngx_str_t  *u;
467
468     if (uri->len == 0) {
469         u = NGX_HTTP_REFERER_NO_URI_PART;
470
471     } else {
472         u = ngx_palloc(cf->pool, sizeof(ngx_str_t));
473         if (u == NULL) {
474             return NGX_CONF_ERROR;
475         }
476
477         *u = *uri;
478     }
479
480     rc = ngx_hash_add_key(keys, value, u, NGX_HASH_WILDCARD_KEY);
481
482     if (rc == NGX_OK) {
483         return NGX_CONF_OK;
484     }
485
486     if (rc == NGX_DECLINED) {
487         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
488                            "invalid hostname or wildcard \"%V\"", value);
489     }
490
491     if (rc == NGX_BUSY) {
492         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
493                            "conflicting parameter \"%V\"", value);
494     }
495
496     return NGX_CONF_ERROR;
497 }
498
499
500 static char *
501 ngx_http_add_regex_referer(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf,
502     ngx_str_t *name, ngx_regex_t *regex)
503 {
504 #if (NGX_PCRE)
505     ngx_str_t         err;
506     ngx_regex_elt_t  *re;
507     u_char            errstr[NGX_MAX_CONF_ERRSTR];
508
509     if (rlcf->regex == NGX_CONF_UNSET_PTR) {
510         rlcf->regex = ngx_array_create(cf->pool, 2, sizeof(ngx_regex_elt_t));
511         if (rlcf->regex == NULL) {
512             return NGX_CONF_ERROR;
513         }
514     }
515
516     re = ngx_array_push(rlcf->regex);
517     if (re == NULL) {
518         return NGX_CONF_ERROR;
519     }
520
521     if (regex) {
522         re->regex = regex;
523         re->name = name->data;
524
525         return NGX_CONF_OK;
526     }
527
528     err.len = NGX_MAX_CONF_ERRSTR;
529     err.data = errstr;
530
531     name->len--;
532     name->data++;
533
534     re->regex = ngx_regex_compile(name, NGX_REGEX_CASELESS, cf->pool, &err);
535
536     if (re->regex == NULL) {
537         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%s", err.data);
538         return NGX_CONF_ERROR;
539     }
540
541     re->name = name->data;
542
543     return NGX_CONF_OK;
544
545 #else
546
547     ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
548                        "the using of the regex \"%V\" requires PCRE library",
549                        name);
550
551     return NGX_CONF_ERROR;
552
553 #endif
554 }
555
556
557 static int ngx_libc_cdecl
558 ngx_http_cmp_referer_wildcards(const void *one, const void *two)
559 {
560     ngx_hash_key_t  *first, *second;
561
562     first = (ngx_hash_key_t *) one;
563     second = (ngx_hash_key_t *) two;
564
565     return ngx_strcmp(first->key.data, second->key.data);
566 }