upstream nginx-0.7.34
[nginx.git] / nginx / src / http / modules / ngx_http_map_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 typedef struct {
13     ngx_uint_t                  hash_max_size;
14     ngx_uint_t                  hash_bucket_size;
15 } ngx_http_map_conf_t;
16
17
18 typedef struct {
19     ngx_hash_keys_arrays_t      keys;
20
21     ngx_array_t                *values_hash;
22
23     ngx_http_variable_value_t  *default_value;
24     ngx_uint_t                  hostnames;      /* unsigned  hostnames:1 */
25 } ngx_http_map_conf_ctx_t;
26
27
28 typedef struct {
29     ngx_hash_combined_t         hash;
30     ngx_int_t                   index;
31     ngx_http_variable_value_t  *default_value;
32     ngx_uint_t                  hostnames;      /* unsigned  hostnames:1 */
33 } ngx_http_map_ctx_t;
34
35
36 static int ngx_libc_cdecl ngx_http_map_cmp_dns_wildcards(const void *one,
37     const void *two);
38 static void *ngx_http_map_create_conf(ngx_conf_t *cf);
39 static char *ngx_http_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
40 static char *ngx_http_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
41
42
43 static ngx_command_t  ngx_http_map_commands[] = {
44
45     { ngx_string("map"),
46       NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2,
47       ngx_http_map_block,
48       NGX_HTTP_MAIN_CONF_OFFSET,
49       0,
50       NULL },
51
52     { ngx_string("map_hash_max_size"),
53       NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
54       ngx_conf_set_num_slot,
55       NGX_HTTP_MAIN_CONF_OFFSET,
56       offsetof(ngx_http_map_conf_t, hash_max_size),
57       NULL },
58
59     { ngx_string("map_hash_bucket_size"),
60       NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
61       ngx_conf_set_num_slot,
62       NGX_HTTP_MAIN_CONF_OFFSET,
63       offsetof(ngx_http_map_conf_t, hash_bucket_size),
64       NULL },
65
66       ngx_null_command
67 };
68
69
70 static ngx_http_module_t  ngx_http_map_module_ctx = {
71     NULL,                                  /* preconfiguration */
72     NULL,                                  /* postconfiguration */
73
74     ngx_http_map_create_conf,              /* create main configuration */
75     NULL,                                  /* init main configuration */
76
77     NULL,                                  /* create server configuration */
78     NULL,                                  /* merge server configuration */
79
80     NULL,                                  /* create location configuration */
81     NULL                                   /* merge location configuration */
82 };
83
84
85 ngx_module_t  ngx_http_map_module = {
86     NGX_MODULE_V1,
87     &ngx_http_map_module_ctx,              /* module context */
88     ngx_http_map_commands,                 /* module directives */
89     NGX_HTTP_MODULE,                       /* module type */
90     NULL,                                  /* init master */
91     NULL,                                  /* init module */
92     NULL,                                  /* init process */
93     NULL,                                  /* init thread */
94     NULL,                                  /* exit thread */
95     NULL,                                  /* exit process */
96     NULL,                                  /* exit master */
97     NGX_MODULE_V1_PADDING
98 };
99
100
101 static ngx_int_t
102 ngx_http_map_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
103     uintptr_t data)
104 {
105     ngx_http_map_ctx_t  *map = (ngx_http_map_ctx_t *) data;
106
107     size_t                      len;
108     u_char                     *name;
109     ngx_uint_t                  key;
110     ngx_http_variable_value_t  *vv, *value;
111
112     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
113                    "http map started");
114
115     vv = ngx_http_get_flushed_variable(r, map->index);
116
117     if (vv == NULL || vv->not_found) {
118         *v = *map->default_value;
119         return NGX_OK;
120     }
121
122     len = vv->len;
123
124     if (len && map->hostnames && vv->data[len - 1] == '.') {
125         len--;
126     }
127
128     if (len == 0) {
129         *v = *map->default_value;
130         return NGX_OK;
131     }
132
133     name = ngx_pnalloc(r->pool, len);
134     if (name == NULL) {
135         return NGX_ERROR;
136     }
137
138     key = ngx_hash_strlow(name, vv->data, len);
139
140     value = ngx_hash_find_combined(&map->hash, key, name, len);
141
142     if (value) {
143         *v = *value;
144
145     } else {
146         *v = *map->default_value;
147     }
148
149     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
150                    "http map: \"%v\" \"%v\"", vv, v);
151
152     return NGX_OK;
153 }
154
155
156 static void *
157 ngx_http_map_create_conf(ngx_conf_t *cf)
158 {
159     ngx_http_map_conf_t  *mcf;
160
161     mcf = ngx_palloc(cf->pool, sizeof(ngx_http_map_conf_t));
162     if (mcf == NULL) {
163         return NGX_CONF_ERROR;
164     }
165
166     mcf->hash_max_size = NGX_CONF_UNSET_UINT;
167     mcf->hash_bucket_size = NGX_CONF_UNSET_UINT;
168
169     return mcf;
170 }
171
172
173 static char *
174 ngx_http_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
175 {
176     ngx_http_map_conf_t  *mcf = conf;
177
178     char                      *rv;
179     ngx_str_t                 *value, name;
180     ngx_conf_t                 save;
181     ngx_pool_t                *pool;
182     ngx_hash_init_t            hash;
183     ngx_http_map_ctx_t        *map;
184     ngx_http_variable_t       *var;
185     ngx_http_map_conf_ctx_t    ctx;
186
187     if (mcf->hash_max_size == NGX_CONF_UNSET_UINT) {
188         mcf->hash_max_size = 2048;
189     }
190
191     if (mcf->hash_bucket_size == NGX_CONF_UNSET_UINT) {
192         mcf->hash_bucket_size = ngx_cacheline_size;
193
194     } else {
195         mcf->hash_bucket_size = ngx_align(mcf->hash_bucket_size,
196                                           ngx_cacheline_size);
197     }
198
199     map = ngx_pcalloc(cf->pool, sizeof(ngx_http_map_ctx_t));
200     if (map == NULL) {
201         return NGX_CONF_ERROR;
202     }
203
204     value = cf->args->elts;
205
206     name = value[1];
207     name.len--;
208     name.data++;
209
210     map->index = ngx_http_get_variable_index(cf, &name);
211
212     if (map->index == NGX_ERROR) {
213         return NGX_CONF_ERROR;
214     }
215
216     name = value[2];
217     name.len--;
218     name.data++;
219
220     var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
221     if (var == NULL) {
222         return NGX_CONF_ERROR;
223     }
224
225     var->get_handler = ngx_http_map_variable;
226     var->data = (uintptr_t) map;
227
228     pool = ngx_create_pool(16384, cf->log);
229     if (pool == NULL) {
230         return NGX_CONF_ERROR;
231     }
232
233     ctx.keys.pool = cf->pool;
234     ctx.keys.temp_pool = pool;
235
236     if (ngx_hash_keys_array_init(&ctx.keys, NGX_HASH_LARGE) != NGX_OK) {
237         ngx_destroy_pool(pool);
238         return NGX_CONF_ERROR;
239     }
240
241     ctx.values_hash = ngx_pcalloc(pool, sizeof(ngx_array_t) * ctx.keys.hsize);
242     if (ctx.values_hash == NULL) {
243         ngx_destroy_pool(pool);
244         return NGX_CONF_ERROR;
245     }
246
247     ctx.default_value = NULL;
248     ctx.hostnames = 0;
249
250     save = *cf;
251     cf->pool = pool;
252     cf->ctx = &ctx;
253     cf->handler = ngx_http_map;
254     cf->handler_conf = conf;
255
256     rv = ngx_conf_parse(cf, NULL);
257
258     *cf = save;
259
260     if (rv != NGX_CONF_OK) {
261         ngx_destroy_pool(pool);
262         return rv;
263     }
264
265     map->default_value = ctx.default_value ? ctx.default_value:
266                                              &ngx_http_variable_null_value;
267
268     hash.key = ngx_hash_key_lc;
269     hash.max_size = mcf->hash_max_size;
270     hash.bucket_size = mcf->hash_bucket_size;
271     hash.name = "map_hash";
272     hash.pool = cf->pool;
273
274     if (ctx.keys.keys.nelts) {
275         hash.hash = &map->hash.hash;
276         hash.temp_pool = NULL;
277
278         if (ngx_hash_init(&hash, ctx.keys.keys.elts, ctx.keys.keys.nelts)
279             != NGX_OK)
280         {
281             ngx_destroy_pool(pool);
282             return NGX_CONF_ERROR;
283         }
284     }
285
286     if (ctx.keys.dns_wc_head.nelts) {
287
288         ngx_qsort(ctx.keys.dns_wc_head.elts,
289                   (size_t) ctx.keys.dns_wc_head.nelts,
290                   sizeof(ngx_hash_key_t), ngx_http_map_cmp_dns_wildcards);
291
292         hash.hash = NULL;
293         hash.temp_pool = pool;
294
295         if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_head.elts,
296                                    ctx.keys.dns_wc_head.nelts)
297             != NGX_OK)
298         {
299             ngx_destroy_pool(pool);
300             return NGX_CONF_ERROR;
301         }
302
303         map->hash.wc_head = (ngx_hash_wildcard_t *) hash.hash;
304     }
305
306     if (ctx.keys.dns_wc_tail.nelts) {
307
308         ngx_qsort(ctx.keys.dns_wc_tail.elts,
309                   (size_t) ctx.keys.dns_wc_tail.nelts,
310                   sizeof(ngx_hash_key_t), ngx_http_map_cmp_dns_wildcards);
311
312         hash.hash = NULL;
313         hash.temp_pool = pool;
314
315         if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_tail.elts,
316                                    ctx.keys.dns_wc_tail.nelts)
317             != NGX_OK)
318         {
319             ngx_destroy_pool(pool);
320             return NGX_CONF_ERROR;
321         }
322
323         map->hash.wc_tail = (ngx_hash_wildcard_t *) hash.hash;
324     }
325
326     ngx_destroy_pool(pool);
327
328     return rv;
329 }
330
331
332 static int ngx_libc_cdecl
333 ngx_http_map_cmp_dns_wildcards(const void *one, const void *two)
334 {
335     ngx_hash_key_t  *first, *second;
336
337     first = (ngx_hash_key_t *) one;
338     second = (ngx_hash_key_t *) two;
339
340     return ngx_strcmp(first->key.data, second->key.data);
341 }
342
343
344 static char *
345 ngx_http_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
346 {
347     ngx_int_t                   rc;
348     ngx_str_t                  *value, file;
349     ngx_uint_t                  i, key;
350     ngx_http_map_conf_ctx_t    *ctx;
351     ngx_http_variable_value_t  *var, **vp;
352
353     ctx = cf->ctx;
354
355     value = cf->args->elts;
356
357     if (cf->args->nelts == 1
358         && ngx_strcmp(value[0].data, "hostnames") == 0)
359     {
360         ctx->hostnames = 1;
361         return NGX_CONF_OK;
362
363     } else if (cf->args->nelts != 2) {
364         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
365                            "invalid number of the map parameters");
366         return NGX_CONF_ERROR;
367
368     } else if (value[0].len == 0) {
369         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
370                            "invalid first parameter");
371         return NGX_CONF_ERROR;
372     }
373
374     if (ngx_strcmp(value[0].data, "include") == 0) {
375         file = value[1];
376
377         if (ngx_conf_full_name(cf->cycle, &file, 1) == NGX_ERROR){
378             return NGX_CONF_ERROR;
379         }
380
381         ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
382
383         return ngx_conf_parse(cf, &file);
384     }
385
386     key = 0;
387
388     for (i = 0; i < value[1].len; i++) {
389         key = ngx_hash(key, value[1].data[i]);
390     }
391
392     key %= ctx->keys.hsize;
393
394     vp = ctx->values_hash[key].elts;
395
396     if (vp) {
397         for (i = 0; i < ctx->values_hash[key].nelts; i++) {
398             if (value[1].len != (size_t) vp[i]->len) {
399                 continue;
400             }
401
402             if (ngx_strncmp(value[1].data, vp[i]->data, value[1].len) == 0) {
403                 var = vp[i];
404                 goto found;
405             }
406         }
407
408     } else {
409         if (ngx_array_init(&ctx->values_hash[key], cf->pool, 4,
410                            sizeof(ngx_http_variable_value_t *))
411             != NGX_OK)
412         {
413             return NGX_CONF_ERROR;
414         }
415     }
416
417     var = ngx_palloc(ctx->keys.pool, sizeof(ngx_http_variable_value_t));
418     if (var == NULL) {
419         return NGX_CONF_ERROR;
420     }
421
422     var->len = value[1].len;
423     var->data = ngx_pstrdup(ctx->keys.pool, &value[1]);
424     if (var->data == NULL) {
425         return NGX_CONF_ERROR;
426     }
427
428     var->valid = 1;
429     var->no_cacheable = 0;
430     var->not_found = 0;
431
432     vp = ngx_array_push(&ctx->values_hash[key]);
433     if (vp == NULL) {
434         return NGX_CONF_ERROR;
435     }
436
437     *vp = var;
438
439 found:
440
441     if (ngx_strcmp(value[0].data, "default") == 0) {
442
443         if (ctx->default_value) {
444             ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
445                                "duplicate default map parameter");
446             return NGX_CONF_ERROR;
447         }
448
449         ctx->default_value = var;
450
451         return NGX_CONF_OK;
452     }
453
454     if (value[0].len && value[0].data[0] == '!') {
455         value[0].len--;
456         value[0].data++;
457     }
458
459     rc = ngx_hash_add_key(&ctx->keys, &value[0], var,
460                           (ctx->hostnames) ? NGX_HASH_WILDCARD_KEY : 0);
461
462     if (rc == NGX_OK) {
463         return NGX_CONF_OK;
464     }
465
466     if (rc == NGX_DECLINED) {
467         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
468                            "invalid hostname or wildcard \"%V\"", &value[0]);
469     }
470
471     if (rc == NGX_BUSY) {
472         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
473                            "conflicting parameter \"%V\"", &value[0]);
474     }
475
476     return NGX_CONF_ERROR;
477 }