1 /******************************************************************************
2  * $Id$
3  *
4  * Project:  MapServer
5  * Purpose:  MapCache tile caching support file: xml configuration parser
6  * Author:   Thomas Bonfort and the MapServer team.
7  *
8  ******************************************************************************
9  * Copyright (c) 1996-2011 Regents of the University of Minnesota.
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included in
19  * all copies of this Software or works derived from this Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27  * DEALINGS IN THE SOFTWARE.
28  *****************************************************************************/
29 
30 #include "mapcache.h"
31 #include "ezxml.h"
32 #include <string.h>
33 #include <stdlib.h>
34 #include <apr_strings.h>
35 #include <apr_hash.h>
36 #include <apr_tables.h>
37 #include <apr_file_io.h>
38 #include <apr_file_info.h>
39 #include <math.h>
40 
41 
parseMetadata(mapcache_context * ctx,ezxml_t node,apr_table_t * metadata)42 void parseMetadata(mapcache_context *ctx, ezxml_t node, apr_table_t *metadata)
43 {
44   ezxml_t cur_node;
45   for(cur_node = node->child; cur_node; cur_node = cur_node->ordered) {
46     if (!cur_node->child) {
47       // Parse simple text
48       apr_table_add(metadata, cur_node->name, cur_node->txt);
49     } else {
50       // Parse tags:
51       //   `>` suffix in name indicates that value is a table and not a string
52       char * name = apr_pstrcat(ctx->pool,cur_node->name,">",NULL);
53       apr_table_t * contents = apr_table_make(ctx->pool,3);
54       ezxml_t sub_node;
55       for(sub_node = cur_node->child; sub_node; sub_node = sub_node->ordered) {
56         apr_table_add(contents, sub_node->name, sub_node->txt);
57       }
58       apr_table_addn(metadata, name, (const char *)contents);
59     }
60   }
61 }
62 
parseDimensions(mapcache_context * ctx,ezxml_t node,mapcache_tileset * tileset)63 void parseDimensions(mapcache_context *ctx, ezxml_t node, mapcache_tileset *tileset)
64 {
65   ezxml_t dimension_node;
66   ezxml_t wms_querybymap_node;
67   apr_array_header_t *dimensions = apr_array_make(ctx->pool,1,sizeof(mapcache_dimension*));
68   for(dimension_node = ezxml_child(node,"dimension"); dimension_node; dimension_node = dimension_node->next) {
69     char *name = (char*)ezxml_attr(dimension_node,"name");
70     char *type = (char*)ezxml_attr(dimension_node,"type");
71     char *unit = (char*)ezxml_attr(dimension_node,"unit");
72     char *time = (char*)ezxml_attr(dimension_node,"time");
73     char *default_value = (char*)ezxml_attr(dimension_node,"default");
74 
75     mapcache_dimension *dimension = NULL;
76 
77     if(!name || !strlen(name)) {
78       ctx->set_error(ctx, 400, "mandatory attribute \"name\" not found in <dimension>");
79       return;
80     }
81 
82     if(type && *type) {
83       if(!strcmp(type,"values")) {
84         dimension = mapcache_dimension_values_create(ctx,ctx->pool);
85       } else if(!strcmp(type,"regex")) {
86         dimension = mapcache_dimension_regex_create(ctx,ctx->pool);
87       } else if(!strcmp(type,"postgresql")) {
88         dimension = mapcache_dimension_postgresql_create(ctx,ctx->pool);
89       } else if(!strcmp(type,"sqlite")) {
90         dimension = mapcache_dimension_sqlite_create(ctx,ctx->pool);
91       } else if(!strcmp(type,"elasticsearch")) {
92         dimension = mapcache_dimension_elasticsearch_create(ctx,ctx->pool);
93       } else if(!strcmp(type,"time")) {
94         //backwards compatibility
95         dimension = mapcache_dimension_sqlite_create(ctx,ctx->pool);
96         dimension->isTime = 1;
97       } else {
98         ctx->set_error(ctx,400,"unknown dimension type \"%s\"",type);
99         return;
100       }
101     } else {
102       ctx->set_error(ctx,400, "mandatory attribute \"type\" not found in <dimensions>");
103       return;
104     }
105     GC_CHECK_ERROR(ctx);
106 
107     dimension->name = apr_pstrdup(ctx->pool,name);
108 
109     if(unit && *unit) {
110       dimension->unit = apr_pstrdup(ctx->pool,unit);
111     }
112 
113     if(time && *time && !strcasecmp(time,"true")) {
114       dimension->isTime = 1;
115     }
116 
117     if(default_value && *default_value) {
118       dimension->default_value = apr_pstrdup(ctx->pool,default_value);
119     } else {
120       ctx->set_error(ctx,400,"dimension \"%s\" has no \"default\" attribute",dimension->name);
121       return;
122     }
123 
124     dimension->wms_querybymap_minzoom = -1;
125     wms_querybymap_node = ezxml_child(dimension_node,"wms_querybymap");
126     if (wms_querybymap_node && wms_querybymap_node->txt) {
127       if (!strcasecmp(wms_querybymap_node->txt,"true")) {
128         const char * minzoom = ezxml_attr(wms_querybymap_node,"minzoom");
129         dimension->wms_querybymap_minzoom = 0;
130         if (minzoom && *minzoom) {
131           char *endptr;
132           dimension->wms_querybymap_minzoom = strtol(minzoom,&endptr,10);
133           if (*endptr != 0 || dimension->wms_querybymap_minzoom < 0) {
134             ctx->set_error(ctx, 400, "failed to parse minzoom \"%s\" for <wms_querybymap>"
135                 "expecting an integer starting from 0",minzoom);
136             return;
137           }
138         }
139       } else if (strcasecmp(wms_querybymap_node->txt,"false")) {
140         ctx->set_error(ctx,400,"failed to parse <wms_querybymap> (%s), expecting \"true\" or \"false\"",wms_querybymap_node->txt);
141         return;
142       }
143     }
144 
145     dimension->configuration_parse_xml(ctx,dimension,dimension_node);
146     GC_CHECK_ERROR(ctx);
147 
148     APR_ARRAY_PUSH(dimensions,mapcache_dimension*) = dimension;
149   }
150   if(apr_is_empty_array(dimensions)) {
151     ctx->set_error(ctx, 400, "<dimensions> for tileset \"%s\" has no dimensions defined (expecting <dimension> children)",tileset->name);
152     return;
153   }
154 
155   tileset->dimensions = dimensions;
156   dimension_node = ezxml_child(node,"store_assemblies");
157   if(dimension_node && dimension_node->txt) {
158     if(!strcmp(dimension_node->txt,"false")) {
159       tileset->store_dimension_assemblies = 0;
160     } else if(strcmp(dimension_node->txt,"true")) {
161       ctx->set_error(ctx,400,"failed to parse <store_assemblies> (%s), expecting \"true\" or \"false\"",dimension_node->txt);
162       return;
163     }
164   }
165 
166   dimension_node = ezxml_child(node,"assembly_type");
167   if(dimension_node) {
168     if(!strcmp(dimension_node->txt,"stack")) {
169       tileset->dimension_assembly_type = MAPCACHE_DIMENSION_ASSEMBLY_STACK;
170     } else if(!strcmp(dimension_node->txt,"animate")) {
171       tileset->dimension_assembly_type = MAPCACHE_DIMENSION_ASSEMBLY_ANIMATE;
172       ctx->set_error(ctx,400,"animate dimension assembly mode not implemented");
173       return;
174     } else if(strcmp(dimension_node->txt,"none")) {
175       ctx->set_error(ctx,400,"unknown dimension assembly mode (%s). Can be one of \"stack\" or \"none\"",dimension_node->txt);
176       return;
177     } else {
178       tileset->dimension_assembly_type = MAPCACHE_DIMENSION_ASSEMBLY_NONE;
179     }
180   }
181 
182   tileset->assembly_threaded_fetching_maxzoom = -1;
183   dimension_node = ezxml_child(node,"assembly_threaded_fetching");
184   if (dimension_node) {
185     if (dimension_node && dimension_node->txt) {
186       if (!strcmp(dimension_node->txt,"true")) {
187         int maxzoom = INT_MAX;
188         char * smaxzoom = (char*)ezxml_attr(dimension_node,"maxzoom");;
189         if (smaxzoom && *smaxzoom) {
190           char *endptr;
191           maxzoom = (int)strtol(smaxzoom,&endptr,10);
192           if(*endptr != 0 || maxzoom < 0) {
193             ctx->set_error(ctx, 400, "failed to parse assembly_threaded_fetching"
194                 " maxzoom %s (expecting a positive integer)", smaxzoom);
195             return;
196           }
197         }
198         tileset->assembly_threaded_fetching_maxzoom = maxzoom;
199       } else if (strcmp(dimension_node->txt,"false")) {
200         ctx->set_error(ctx,400,"failed to parse <assembly_threaded_fetching>"
201             " (%s), expecting \"true\" or \"false\"",dimension_node->txt);
202         return;
203       }
204     }
205   }
206 
207   /* should we create subdimensions from source if not found in cache.
208   e.g. if dimension=mosaic returns dimension=val1,val2,val3 should we
209   query the wms source with dimension=val1 , dimension=val2 and/or
210   dimension=val3 if they are not found in cache */
211   dimension_node = ezxml_child(node,"subdimensions_read_only");
212   if(dimension_node) {
213     if(tileset->dimension_assembly_type == MAPCACHE_DIMENSION_ASSEMBLY_NONE) {
214       ctx->set_error(ctx,400,"<subdimensions_read_only> used on a tileset with no <assembly_type> set, which makes no sense");
215       return;
216     }
217     if(dimension_node && dimension_node->txt && !strcmp(dimension_node->txt,"true")) {
218       tileset->subdimension_read_only = 1;
219     } else if(strcmp(dimension_node->txt,"false")) {
220       ctx->set_error(ctx,400,"failed to parse <subdimensions_read_only> (%s), expecting \"true\" or \"false\"",dimension_node->txt);
221       return;
222     }
223   }
224 }
225 
parseRuleset(mapcache_context * ctx,ezxml_t node,mapcache_cfg * config)226 void parseRuleset(mapcache_context *ctx, ezxml_t node, mapcache_cfg *config)
227 {
228   char *name;
229   mapcache_ruleset *ruleset;
230   ezxml_t cur_node;
231   int i;
232 
233   name = (char*)ezxml_attr(node,"name");
234 
235   if(!name || !strlen(name)) {
236     ctx->set_error(ctx, 400, "mandatory attribute \"name\" not found in <ruleset>");
237     return;
238   } else {
239     name = apr_pstrdup(ctx->pool, name);
240     /* check we don't already have a ruleset defined with this name */
241     if(mapcache_configuration_get_ruleset(config, name)) {
242       ctx->set_error(ctx, 400, "duplicate ruleset with name \"%s\"",name);
243       return;
244     }
245   }
246 
247   ruleset = mapcache_ruleset_create(ctx->pool);
248   ruleset->name = name;
249 
250   /* parse rules, <rule> */
251   for(cur_node = ezxml_child(node,"rule"), i = 0; cur_node; cur_node = cur_node->next, i++) {
252     int *zoom, nzoom, j;
253     char* zoom_attr = (char*)ezxml_attr(cur_node, "zoom_level");
254     ezxml_t visibility = ezxml_child(cur_node, "visibility");
255     mapcache_rule *rule = mapcache_ruleset_rule_create(ctx->pool);
256 
257     /* parse zoom_level attribute */
258     if(zoom_attr && *zoom_attr) {
259       char *value = apr_pstrdup(ctx->pool, zoom_attr);
260       if(MAPCACHE_SUCCESS != mapcache_util_extract_int_list(ctx, value, NULL, &zoom, &nzoom) || nzoom < 1) {
261         ctx->set_error(ctx, 400, "failed to parse zoom_level array %s in ruleset %s, rule %d. "
262                       "(expecting space separated integers, eg <rule zoom_level=\"0 1 2\">)",
263                        value, ruleset->name, i+1);
264         return;
265       }
266     } else {
267         ctx->set_error(ctx, 400, "zoom_level not set in rule %d", i+1);
268         return;
269     }
270 
271     /* parse visibility, <visibility> */
272     if(visibility) {
273       char *hidden_color = (char*)ezxml_attr(visibility, "hidden_color");
274       ezxml_t extent_node;
275 
276       if (hidden_color && *hidden_color) {
277         /* parse color, base 16 */
278         rule->hidden_color = (unsigned int)strtol(hidden_color, NULL, 16);
279 
280         if (strlen(hidden_color) <= 6) {
281             /* if color is set, but no alpha value. Assume no transparency. */
282             rule->hidden_color += 0xff000000;
283         }
284       }
285 
286       /* parse extents, <extent> */
287       for (extent_node = ezxml_child(visibility,"extent"); extent_node; extent_node = extent_node->next) {
288         double *values;
289         int nvalues;
290         char *value = apr_pstrdup(ctx->pool,extent_node->txt);
291         mapcache_extent extent = {0,0,0,0};
292         mapcache_extent *pextent;
293 
294         if(MAPCACHE_SUCCESS != mapcache_util_extract_double_list(ctx, value, NULL, &values, &nvalues) ||
295             nvalues != 4) {
296           ctx->set_error(ctx, 400, "failed to parse extent array %s in ruleset %s, rule %d. "
297                          "(expecting 4 space separated numbers, got %d (%f %f %f %f)"
298                          "eg <extent>-180 -90 180 90</extent>)",
299                          value,ruleset->name,i+1,nvalues,values[0],values[1],values[2],values[3]);
300           return;
301         }
302 
303         extent.minx = values[0];
304         extent.miny = values[1];
305         extent.maxx = values[2];
306         extent.maxy = values[3];
307         pextent =  (mapcache_extent*)apr_pcalloc(ctx->pool, sizeof(mapcache_extent));
308         *pextent = extent;
309         APR_ARRAY_PUSH(rule->visible_extents, mapcache_extent*) = pextent;
310       }
311     }
312 
313     /* add this rule for given zoom_levels */
314     for(j = 0; j < nzoom; j++) {
315       mapcache_rule *clone_rule = mapcache_ruleset_rule_clone(ctx->pool, rule);
316       /* check for duplicate rule for this zoom level */
317       if(mapcache_ruleset_rule_find(ruleset->rules, zoom[j]) != NULL) {
318         ctx->set_error(ctx, 400, "found duplicate rule for zoom_level %d", zoom[j]);
319         return;
320       }
321       clone_rule->zoom_level = zoom[j];
322       APR_ARRAY_PUSH(ruleset->rules, mapcache_rule*) = clone_rule;
323     }
324   }
325   mapcache_configuration_add_ruleset(config,ruleset,ruleset->name);
326 }
327 
parseGrid(mapcache_context * ctx,ezxml_t node,mapcache_cfg * config)328 void parseGrid(mapcache_context *ctx, ezxml_t node, mapcache_cfg *config)
329 {
330   char *name;
331   mapcache_extent extent = {0,0,0,0};
332   mapcache_grid *grid;
333   ezxml_t cur_node;
334   char *value;
335 
336   name = (char*)ezxml_attr(node,"name");
337   if(!name || !strlen(name)) {
338     ctx->set_error(ctx, 400, "mandatory attribute \"name\" not found in <grid>");
339     return;
340   } else {
341     name = apr_pstrdup(ctx->pool, name);
342     /* check we don't already have a grid defined with this name */
343     if(mapcache_configuration_get_grid(config, name)) {
344       ctx->set_error(ctx, 400, "duplicate grid with name \"%s\"",name);
345       return;
346     }
347   }
348   grid = mapcache_grid_create(ctx->pool);
349   grid->name = name;
350 
351   if ((cur_node = ezxml_child(node,"extent")) != NULL) {
352     double *values;
353     int nvalues;
354     value = apr_pstrdup(ctx->pool,cur_node->txt);
355     if(MAPCACHE_SUCCESS != mapcache_util_extract_double_list(ctx, value, NULL, &values, &nvalues) ||
356         nvalues != 4) {
357       ctx->set_error(ctx, 400, "failed to parse extent array %s."
358                      "(expecting 4 space separated numbers, got %d (%f %f %f %f)"
359                      "eg <extent>-180 -90 180 90</extent>",
360                      value,nvalues,values[0],values[1],values[2],values[3]);
361       return;
362     }
363     extent.minx = values[0];
364     extent.miny = values[1];
365     extent.maxx = values[2];
366     extent.maxy = values[3];
367   }
368 
369   if ((cur_node = ezxml_child(node,"metadata")) != NULL) {
370     parseMetadata(ctx, cur_node, grid->metadata);
371     GC_CHECK_ERROR(ctx);
372   }
373 
374   if ((cur_node = ezxml_child(node,"units")) != NULL) {
375     if(!strcasecmp(cur_node->txt,"dd")) {
376       grid->unit = MAPCACHE_UNIT_DEGREES;
377     } else if(!strcasecmp(cur_node->txt,"m")) {
378       grid->unit = MAPCACHE_UNIT_METERS;
379     } else if(!strcasecmp(cur_node->txt,"ft")) {
380       grid->unit = MAPCACHE_UNIT_FEET;
381     } else {
382       ctx->set_error(ctx, 400, "unknown unit %s for grid %s (valid values are \"dd\", \"m\", and \"ft\"",
383                      cur_node->txt, grid->name);
384       return;
385     }
386   }
387   if ((cur_node = ezxml_child(node,"srs")) != NULL) {
388     grid->srs = apr_pstrdup(ctx->pool,cur_node->txt);
389   }
390 
391   for(cur_node = ezxml_child(node,"srsalias"); cur_node; cur_node = cur_node->next) {
392     value = apr_pstrdup(ctx->pool,cur_node->txt);
393     APR_ARRAY_PUSH(grid->srs_aliases,char*) = value;
394   }
395 
396   if ((cur_node = ezxml_child(node,"origin")) != NULL) {
397     if(!strcasecmp(cur_node->txt,"top-left")) {
398       grid->origin = MAPCACHE_GRID_ORIGIN_TOP_LEFT;
399     } else if(!strcasecmp(cur_node->txt,"bottom-left")) {
400       grid->origin = MAPCACHE_GRID_ORIGIN_BOTTOM_LEFT;
401     } else if(!strcasecmp(cur_node->txt,"top-right")) {
402       grid->origin = MAPCACHE_GRID_ORIGIN_TOP_RIGHT;
403     } else if(!strcasecmp(cur_node->txt,"bottom-right")) {
404       grid->origin = MAPCACHE_GRID_ORIGIN_BOTTOM_RIGHT;
405     } else {
406       ctx->set_error(ctx, 400,
407           "unknown origin %s for grid %s (valid values are \"top-left\", \"bottom-left\", \"top-right\" and \"bottom-right\"",
408           cur_node->txt, grid->name);
409       return;
410     }
411     if(grid->origin == MAPCACHE_GRID_ORIGIN_BOTTOM_RIGHT || grid->origin == MAPCACHE_GRID_ORIGIN_TOP_RIGHT) {
412       ctx->set_error(ctx,500,"grid origin %s not implemented",cur_node->txt);
413       return;
414     }
415   }
416   if ((cur_node = ezxml_child(node,"size")) != NULL) {
417     int *sizes, nsizes;
418     value = apr_pstrdup(ctx->pool,cur_node->txt);
419 
420     if(MAPCACHE_SUCCESS != mapcache_util_extract_int_list(ctx, value, NULL, &sizes, &nsizes) ||
421         nsizes != 2) {
422       ctx->set_error(ctx, 400, "failed to parse size array %s in  grid %s"
423                      "(expecting two space separated integers, eg <size>256 256</size>",
424                      value, grid->name);
425       return;
426     }
427     grid->tile_sx = sizes[0];
428     grid->tile_sy = sizes[1];
429   }
430 
431   if ((cur_node = ezxml_child(node,"resolutions")) != NULL) {
432     int nvalues;
433     double *values;
434     value = apr_pstrdup(ctx->pool,cur_node->txt);
435 
436     if(MAPCACHE_SUCCESS != mapcache_util_extract_double_list(ctx, value, NULL, &values, &nvalues) ||
437         !nvalues) {
438       ctx->set_error(ctx, 400, "failed to parse resolutions array %s."
439                      "(expecting space separated numbers, "
440                      "eg <resolutions>1 2 4 8 16 32</resolutions>",
441                      value);
442       return;
443     }
444     grid->nlevels = nvalues;
445     grid->levels = (mapcache_grid_level**)apr_pcalloc(ctx->pool,
446                    grid->nlevels*sizeof(mapcache_grid_level));
447     while(nvalues--) {
448       double unitheight;
449       double unitwidth;
450       mapcache_grid_level *level = (mapcache_grid_level*)apr_pcalloc(ctx->pool,sizeof(mapcache_grid_level));
451       level->resolution = values[nvalues];
452       unitheight = grid->tile_sy * level->resolution;
453       unitwidth = grid->tile_sx * level->resolution;
454       level->maxy = ceil((extent.maxy-extent.miny - 0.01* unitheight)/unitheight);
455       level->maxx = ceil((extent.maxx-extent.minx - 0.01* unitwidth)/unitwidth);
456       grid->levels[nvalues] = level;
457     }
458   }
459 
460   if(grid->srs == NULL) {
461     ctx->set_error(ctx, 400, "grid \"%s\" has no srs configured."
462                    " You must add a <srs> tag.", grid->name);
463     return;
464   }
465   if(extent.minx >= extent.maxx || extent.miny >= extent.maxy) {
466     ctx->set_error(ctx, 400, "grid \"%s\" has no (or invalid) extent configured"
467                    " You must add/correct a <extent> tag.", grid->name);
468     return;
469   } else {
470     grid->extent = extent;
471   }
472   if(grid->tile_sx <= 0 || grid->tile_sy <= 0) {
473     ctx->set_error(ctx, 400, "grid \"%s\" has no (or invalid) tile size configured"
474                    " You must add/correct a <size> tag.", grid->name);
475     return;
476   }
477   if(!grid->nlevels) {
478     ctx->set_error(ctx, 400, "grid \"%s\" has no resolutions configured."
479                    " You must add a <resolutions> tag.", grid->name);
480     return;
481   }
482   mapcache_configuration_add_grid(config,grid,name);
483 }
484 
parseSource(mapcache_context * ctx,ezxml_t node,mapcache_cfg * config)485 void parseSource(mapcache_context *ctx, ezxml_t node, mapcache_cfg *config)
486 {
487   ezxml_t cur_node;
488   char *name = NULL, *type = NULL;
489   mapcache_source *source;
490 
491   name = (char*)ezxml_attr(node,"name");
492   type = (char*)ezxml_attr(node,"type");
493   if(!name || !strlen(name)) {
494     ctx->set_error(ctx, 400, "mandatory attribute \"name\" not found in <source>");
495     return;
496   } else {
497     name = apr_pstrdup(ctx->pool, name);
498     /* check we don't already have a source defined with this name */
499     if(mapcache_configuration_get_source(config, name)) {
500       ctx->set_error(ctx, 400, "duplicate source with name \"%s\"",name);
501       return;
502     }
503   }
504   if(!type || !strlen(type)) {
505     ctx->set_error(ctx, 400, "mandatory attribute \"type\" not found in <source>");
506     return;
507   }
508   source = NULL;
509   if(!strcmp(type,"wms")) {
510     source = mapcache_source_wms_create(ctx);
511   } else if(!strcmp(type,"mapserver")) {
512     source = mapcache_source_mapserver_create(ctx);
513   } else if(!strcmp(type,"gdal")) {
514     source = mapcache_source_gdal_create(ctx);
515   } else if(!strcmp(type,"dummy")) {
516     source = mapcache_source_dummy_create(ctx);
517   } else if(!strcmp(type,"fallback")) {
518     source = mapcache_source_fallback_create(ctx);
519   } else {
520     ctx->set_error(ctx, 400, "unknown source type %s for source \"%s\"", type, name);
521     return;
522   }
523   if(source == NULL) {
524     ctx->set_error(ctx, 400, "failed to parse source \"%s\"", name);
525     return;
526   }
527   source->name = name;
528 
529   if ((cur_node = ezxml_child(node,"metadata")) != NULL) {
530     parseMetadata(ctx, cur_node, source->metadata);
531     GC_CHECK_ERROR(ctx);
532   }
533   if ((cur_node = ezxml_child(node,"retries")) != NULL) {
534     source->retry_count = atoi(cur_node->txt);
535     if(source->retry_count > 10) {
536       ctx->set_error(ctx,400,"source (%s) <retries> count of %d is unreasonably large. max is 10", source->name, source->retry_count);
537       return;
538     }
539   }
540   if ((cur_node = ezxml_child(node,"retry_delay")) != NULL) {
541     source->retry_delay = (double)atof(cur_node->txt);
542     if(source->retry_delay < 0) {
543       ctx->set_error(ctx,400,"source (%s) retry delay of %f must be positive",source->name, source->retry_delay);
544       return;
545     }
546   }
547 
548   source->configuration_parse_xml(ctx,node,source, config);
549   GC_CHECK_ERROR(ctx);
550   source->configuration_check(ctx,config,source);
551   GC_CHECK_ERROR(ctx);
552   mapcache_configuration_add_source(config,source,name);
553 }
554 
parseFormat(mapcache_context * ctx,ezxml_t node,mapcache_cfg * config)555 void parseFormat(mapcache_context *ctx, ezxml_t node, mapcache_cfg *config)
556 {
557   char *name = NULL,  *type = NULL;
558   mapcache_image_format *format = NULL;
559   ezxml_t cur_node;
560   name = (char*)ezxml_attr(node,"name");
561   type = (char*)ezxml_attr(node,"type");
562   if(!name || !strlen(name)) {
563     ctx->set_error(ctx, 400, "mandatory attribute \"name\" not found in <format>");
564     return;
565   }
566   name = apr_pstrdup(ctx->pool, name);
567   if(!type || !strlen(type)) {
568     ctx->set_error(ctx, 400, "mandatory attribute \"type\" not found in <format>");
569     return;
570   }
571   if(!strcmp(type,"PNG")) {
572     int colors = -1;
573     mapcache_compression_type compression = MAPCACHE_COMPRESSION_DEFAULT;
574     if ((cur_node = ezxml_child(node,"compression")) != NULL) {
575       if(!strcmp(cur_node->txt, "fast")) {
576         compression = MAPCACHE_COMPRESSION_FAST;
577       } else if(!strcmp(cur_node->txt, "best")) {
578         compression = MAPCACHE_COMPRESSION_BEST;
579       } else if(!strcmp(cur_node->txt, "none")) {
580         compression = MAPCACHE_COMPRESSION_DISABLE;
581       } else {
582         ctx->set_error(ctx, 400, "unknown compression type %s for format \"%s\"", cur_node->txt, name);
583         return;
584       }
585     }
586     if ((cur_node = ezxml_child(node,"colors")) != NULL) {
587       char *endptr;
588       colors = (int)strtol(cur_node->txt,&endptr,10);
589       if(*endptr != 0 || colors < 2 || colors > 256) {
590         ctx->set_error(ctx, 400, "failed to parse colors \"%s\" for format \"%s\""
591                        "(expecting an  integer between 2 and 256 "
592                        "eg <colors>256</colors>",
593                        cur_node->txt,name);
594         return;
595       }
596     }
597 
598     if(colors == -1) {
599       format = mapcache_imageio_create_png_format(ctx->pool,
600                name,compression);
601     } else {
602       format = mapcache_imageio_create_png_q_format(ctx->pool,
603                name,compression, colors);
604     }
605   } else if(!strcmp(type,"JPEG")) {
606     int quality = 95;
607     int optimize = TRUE;
608     mapcache_photometric photometric = MAPCACHE_PHOTOMETRIC_YCBCR;
609     if ((cur_node = ezxml_child(node,"quality")) != NULL) {
610       char *endptr;
611       quality = (int)strtol(cur_node->txt,&endptr,10);
612       if(*endptr != 0 || quality < 1 || quality > 100) {
613         ctx->set_error(ctx, 400, "failed to parse quality \"%s\" for format \"%s\""
614                        "(expecting an  integer between 1 and 100 "
615                        "eg <quality>90</quality>",
616                        cur_node->txt,name);
617         return;
618       }
619     }
620     if ((cur_node = ezxml_child(node,"photometric")) != NULL) {
621       if(!strcasecmp(cur_node->txt,"RGB"))
622         photometric = MAPCACHE_PHOTOMETRIC_RGB;
623       else if(!strcasecmp(cur_node->txt,"YCBCR"))
624         photometric = MAPCACHE_PHOTOMETRIC_YCBCR;
625       else {
626         ctx->set_error(ctx,500,"failed to parse jpeg format %s photometric %s. expecting rgb or ycbcr",
627                        name,cur_node->txt);
628         return;
629       }
630     }
631     if ((cur_node = ezxml_child(node,"optimize")) != NULL) {
632       if(cur_node->txt && !strcasecmp(cur_node->txt,"false"))
633         optimize = MAPCACHE_OPTIMIZE_NO;
634       else if(cur_node->txt && !strcasecmp(cur_node->txt,"true"))
635         optimize = MAPCACHE_OPTIMIZE_YES;
636       else if(cur_node->txt && !strcasecmp(cur_node->txt,"arithmetic"))
637         optimize = MAPCACHE_OPTIMIZE_ARITHMETIC;
638       else {
639         ctx->set_error(ctx,500,"failed to parse jpeg format %s optimize %s. expecting true, false or arithmetic",
640                        name,cur_node->txt);
641         return;
642       }
643     }
644     format = mapcache_imageio_create_jpeg_format(ctx->pool,
645              name,quality,photometric,optimize);
646   } else if(!strcasecmp(type,"MIXED")) {
647     mapcache_image_format *transparent=NULL, *opaque=NULL;
648     unsigned int alpha_cutoff=255;
649     if ((cur_node = ezxml_child(node,"transparent")) != NULL) {
650       transparent = mapcache_configuration_get_image_format(config,cur_node->txt);
651     }
652     if(!transparent) {
653       ctx->set_error(ctx,400, "mixed format %s references unknown transparent format %s"
654                      "(order is important, format %s should appear first)",
655                      name,cur_node->txt,cur_node->txt);
656       return;
657     }
658     if ((cur_node = ezxml_child(node,"opaque")) != NULL) {
659       opaque = mapcache_configuration_get_image_format(config,cur_node->txt);
660     }
661     if(!opaque) {
662       ctx->set_error(ctx,400, "mixed format %s references unknown opaque format %s"
663                      "(order is important, format %s should appear first)",
664                      name,cur_node->txt,cur_node->txt);
665       return;
666     }
667     if ((cur_node = ezxml_child(node,"alpha_cutoff")) != NULL) {
668       alpha_cutoff = atoi(cur_node->txt);
669     }
670     format = mapcache_imageio_create_mixed_format(ctx->pool,name,transparent, opaque, alpha_cutoff);
671   } else if(!strcasecmp(type,"RAW")) {
672     char *extension=NULL;
673     char *mime_type=NULL;
674     if ((cur_node = ezxml_child(node,"extension")) != NULL) extension = apr_pstrdup(ctx->pool, cur_node->txt);
675     if ((cur_node = ezxml_child(node,"mime_type")) != NULL) mime_type = apr_pstrdup(ctx->pool, cur_node->txt);
676     format = mapcache_imageio_create_raw_format(ctx->pool,name,extension,mime_type);
677   } else {
678     ctx->set_error(ctx, 400, "unknown format type %s for format \"%s\"", type, name);
679     return;
680   }
681   if(format == NULL) {
682     ctx->set_error(ctx, 400, "failed to parse format \"%s\"", name);
683     return;
684   }
685 
686   mapcache_configuration_add_image_format(config,format,name);
687   return;
688 }
689 
parseCache(mapcache_context * ctx,ezxml_t node,mapcache_cfg * config)690 void parseCache(mapcache_context *ctx, ezxml_t node, mapcache_cfg *config)
691 {
692   char *name = NULL,  *type = NULL;
693   mapcache_cache *cache = NULL;
694   ezxml_t cur_node;
695   name = (char*)ezxml_attr(node,"name");
696   type = (char*)ezxml_attr(node,"type");
697   if(!name || !strlen(name)) {
698     ctx->set_error(ctx, 400, "mandatory attribute \"name\" not found in <cache>");
699     return;
700   } else {
701     name = apr_pstrdup(ctx->pool, name);
702     /* check we don't already have a cache defined with this name */
703     if(mapcache_configuration_get_cache(config, name)) {
704       ctx->set_error(ctx, 400, "duplicate cache with name \"%s\"",name);
705       return;
706     }
707   }
708   if(!type || !strlen(type)) {
709     ctx->set_error(ctx, 400, "mandatory attribute \"type\" not found in <cache>");
710     return;
711   }
712   if(!strcmp(type,"disk")) {
713     cache = mapcache_cache_disk_create(ctx);
714   } else if(!strcmp(type,"fallback")) {
715     cache = mapcache_cache_fallback_create(ctx);
716   } else if(!strcmp(type,"multitier")) {
717     cache = mapcache_cache_multitier_create(ctx);
718   } else if(!strcmp(type,"composite")) {
719     cache = mapcache_cache_composite_create(ctx);
720   } else if(!strcmp(type,"rest")) {
721     cache = mapcache_cache_rest_create(ctx);
722   } else if(!strcmp(type,"s3")) {
723     cache = mapcache_cache_s3_create(ctx);
724   } else if(!strcmp(type,"azure")) {
725     cache = mapcache_cache_azure_create(ctx);
726   } else if(!strcmp(type,"google")) {
727     cache = mapcache_cache_google_create(ctx);
728   } else if(!strcmp(type,"bdb")) {
729     cache = mapcache_cache_bdb_create(ctx);
730   } else if(!strcmp(type,"tokyocabinet")) {
731     cache = mapcache_cache_tc_create(ctx);
732   } else if(!strcmp(type,"sqlite3")) {
733     cache = mapcache_cache_sqlite_create(ctx);
734   } else if(!strcmp(type,"mbtiles")) {
735     cache = mapcache_cache_mbtiles_create(ctx);
736   } else if(!strcmp(type,"memcache")) {
737     cache = mapcache_cache_memcache_create(ctx);
738   } else if(!strcmp(type,"tiff")) {
739     cache = mapcache_cache_tiff_create(ctx);
740   } else if(!strcmp(type,"couchbase")) {
741     cache = mapcache_cache_couchbase_create(ctx);
742   } else if(!strcmp(type,"riak")) {
743     cache = mapcache_cache_riak_create(ctx);
744   } else {
745     ctx->set_error(ctx, 400, "unknown cache type %s for cache \"%s\"", type, name);
746     return;
747   }
748   GC_CHECK_ERROR(ctx);
749   if(cache == NULL) {
750     ctx->set_error(ctx, 400, "failed to parse cache \"%s\"", name);
751     return;
752   }
753   cache->name = name;
754 
755   if ((cur_node = ezxml_child(node,"retries")) != NULL) {
756     cache->retry_count = atoi(cur_node->txt);
757     if(cache->retry_count > 10) {
758       ctx->set_error(ctx,400,"cache (%s) <retries> count of %d is unreasonably large. max is 10", cache->name, cache->retry_count);
759       return;
760     }
761   }
762   if ((cur_node = ezxml_child(node,"retry_delay")) != NULL) {
763     cache->retry_delay = (double)atof(cur_node->txt);
764     if(cache->retry_delay < 0) {
765       ctx->set_error(ctx,400,"cache (%s) retry delay of %f must be positive",cache->name, cache->retry_delay);
766       return;
767     }
768   }
769 
770 
771   cache->configuration_parse_xml(ctx,node,cache,config);
772   GC_CHECK_ERROR(ctx);
773   mapcache_configuration_add_cache(config,cache,name);
774   return;
775 }
776 
parseTileset(mapcache_context * ctx,ezxml_t node,mapcache_cfg * config)777 void parseTileset(mapcache_context *ctx, ezxml_t node, mapcache_cfg *config)
778 {
779   char *name = NULL;
780   mapcache_tileset *tileset = NULL;
781   ezxml_t cur_node;
782   char* value;
783   int havewgs84bbox=0;
784 
785   if(config->mode == MAPCACHE_MODE_NORMAL) {
786     name = (char*)ezxml_attr(node,"name");
787   } else {
788     name = "mirror";
789   }
790   if(!name || !strlen(name)) {
791     ctx->set_error(ctx, 400, "mandatory attribute \"name\" not found in <tileset>");
792     return;
793   } else {
794     name = apr_pstrdup(ctx->pool, name);
795     /* check we don't already have a cache defined with this name */
796     if(mapcache_configuration_get_tileset(config, name)) {
797       ctx->set_error(ctx, 400, "duplicate tileset with name \"%s\"",name);
798       return;
799     }
800   }
801   tileset = mapcache_tileset_create(ctx);
802   tileset->name = name;
803 
804   if ((cur_node = ezxml_child(node,"read-only")) != NULL) {
805     if(cur_node->txt && !strcmp(cur_node->txt,"true"))
806       tileset->read_only = 1;
807   }
808 
809   if ((cur_node = ezxml_child(node,"metadata")) != NULL) {
810     parseMetadata(ctx, cur_node, tileset->metadata);
811     GC_CHECK_ERROR(ctx);
812   }
813 
814 
815   if ((value = (char*)apr_table_get(tileset->metadata,"wgs84boundingbox")) != NULL) {
816     double *values;
817     int nvalues;
818     value = apr_pstrdup(ctx->pool,value);
819     if(MAPCACHE_SUCCESS != mapcache_util_extract_double_list(ctx, value, NULL, &values, &nvalues) ||
820         nvalues != 4) {
821       ctx->set_error(ctx, 400, "failed to parse extent array %s."
822                      "(expecting 4 space separated numbers, got %d (%f %f %f %f)"
823                      "eg <wgs84bbox>-180 -90 180 90</wgs84bbox>",
824                      value,nvalues,values[0],values[1],values[2],values[3]);
825       return;
826     }
827     tileset->wgs84bbox.minx = values[0];
828     tileset->wgs84bbox.miny = values[1];
829     tileset->wgs84bbox.maxx = values[2];
830     tileset->wgs84bbox.maxy = values[3];
831     havewgs84bbox = 1;
832   }
833 
834   if ((cur_node = ezxml_child(node,"format")) != NULL) {
835     mapcache_image_format *format = mapcache_configuration_get_image_format(config,cur_node->txt);
836     if(!format) {
837       ctx->set_error(ctx, 400, "tileset \"%s\" references format \"%s\","
838                      " but it is not configured",name,cur_node->txt);
839       return;
840     }
841     tileset->format = format;
842   }
843 
844   for(cur_node = ezxml_child(node,"grid"); cur_node; cur_node = cur_node->next) {
845     mapcache_grid *grid;
846     mapcache_grid_link *gridlink;
847     mapcache_ruleset *ruleset = NULL;
848     char *restrictedExtent = NULL, *sTolerance = NULL, *ruleset_name = NULL;
849     mapcache_extent *extent;
850     int tolerance;
851     int i;
852 
853     if (tileset->grid_links == NULL) {
854       tileset->grid_links = apr_array_make(ctx->pool,1,sizeof(mapcache_grid_link*));
855     }
856     grid = mapcache_configuration_get_grid(config, cur_node->txt);
857     if(!grid) {
858       ctx->set_error(ctx, 400, "tileset \"%s\" references grid \"%s\","
859                      " but it is not configured", name, cur_node->txt);
860       return;
861     }
862     gridlink = apr_pcalloc(ctx->pool,sizeof(mapcache_grid_link));
863     gridlink->grid = grid;
864     gridlink->minz = 0;
865     gridlink->maxz = grid->nlevels;
866     gridlink->grid_limits = (mapcache_extent_i*)apr_pcalloc(ctx->pool,grid->nlevels*sizeof(mapcache_extent_i));
867     gridlink->outofzoom_strategy = MAPCACHE_OUTOFZOOM_NOTCONFIGURED;
868     gridlink->intermediate_grids = apr_array_make(ctx->pool,1,sizeof(mapcache_grid_link*));
869     gridlink->rules = apr_array_make(ctx->pool,0,sizeof(mapcache_rule*));
870 
871     ruleset_name = (char*)ezxml_attr(cur_node,"ruleset");
872     if(ruleset_name) {
873       ruleset = mapcache_configuration_get_ruleset(config, ruleset_name);
874       if(!ruleset) {
875         ctx->set_error(ctx, 400, "grid  \"%s\" in tileset \"%s\" references ruleset \"%s\","
876                      " but it is not configured", cur_node->txt, name, ruleset_name);
877         return;
878       }
879     }
880 
881     restrictedExtent = (char*)ezxml_attr(cur_node,"restricted_extent");
882     if(restrictedExtent) {
883       int nvalues;
884       double *values;
885       restrictedExtent = apr_pstrdup(ctx->pool,restrictedExtent);
886       if(MAPCACHE_SUCCESS != mapcache_util_extract_double_list(ctx, restrictedExtent, NULL, &values, &nvalues) ||
887           nvalues != 4) {
888         ctx->set_error(ctx, 400, "failed to parse extent array %s."
889                        "(expecting 4 space separated numbers, "
890                        "eg <grid restricted_extent=\"-180 -90 180 90\">foo</grid>",
891                        restrictedExtent);
892         return;
893       }
894       gridlink->restricted_extent = (mapcache_extent*) apr_pcalloc(ctx->pool, sizeof(mapcache_extent));
895       gridlink->restricted_extent->minx = values[0];
896       gridlink->restricted_extent->miny = values[1];
897       gridlink->restricted_extent->maxx = values[2];
898       gridlink->restricted_extent->maxy = values[3];
899       extent = gridlink->restricted_extent;
900     } else {
901       extent = &grid->extent;
902     }
903 
904     tolerance = 5;
905     sTolerance = (char*)ezxml_attr(cur_node,"tolerance");
906     if(sTolerance) {
907       char *endptr;
908       tolerance = (int)strtol(sTolerance,&endptr,10);
909       if(*endptr != 0 || tolerance < 0) {
910         ctx->set_error(ctx, 400, "failed to parse grid tolerance %s (expecting a positive integer)",
911                        sTolerance);
912         return;
913       }
914     }
915     sTolerance = (char*)ezxml_attr(cur_node,"use_wms_intermediate_resolutions");
916     if(sTolerance && !strcmp(sTolerance,"true")) {
917       mapcache_grid_link *intermediate_gridlink = apr_pcalloc(ctx->pool,sizeof(mapcache_grid_link));
918       APR_ARRAY_PUSH(gridlink->intermediate_grids,mapcache_grid_link*) = intermediate_gridlink;
919     }
920 
921     mapcache_grid_compute_limits(grid,extent,gridlink->grid_limits,tolerance);
922 
923     // setup zoom level rules if configured
924     if(ruleset) {
925       mapcache_buffer *last_hidden_tile = NULL;
926       unsigned int last_hidden_color;
927 
928       // prepare one rule per zoom level. if rule is missing it will be NULL
929       for(i = 0; i < grid->nlevels; i++) {
930         mapcache_rule *rule = mapcache_ruleset_rule_find(ruleset->rules, i);
931 
932         if(rule) {
933           mapcache_rule *rule_clone = mapcache_ruleset_rule_clone(ctx->pool, rule);
934 
935           if(rule->visible_extents) {
936             int j;
937 
938             // create blank tile in configured format to return when outside visible extent
939             // try to reuse last tile if possible
940             if(last_hidden_tile == NULL || last_hidden_color != rule->hidden_color) {
941               if(tileset->format) {
942                 last_hidden_tile = tileset->format->create_empty_image(ctx, tileset->format, grid->tile_sx, grid->tile_sy, rule->hidden_color);
943               } else {
944                 last_hidden_tile = config->default_image_format->create_empty_image(ctx, config->default_image_format, grid->tile_sx, grid->tile_sy, rule->hidden_color);
945               }
946 
947               if(GC_HAS_ERROR(ctx)) {
948                 return;
949               }
950 
951               last_hidden_color = rule->hidden_color;
952             }
953 
954             rule_clone->hidden_tile = last_hidden_tile;
955 
956             // compute limits for extents
957             for(j = 0; j < rule->visible_extents->nelts; j++) {
958               mapcache_extent *visible_extent = APR_ARRAY_IDX(rule->visible_extents, j, mapcache_extent*);
959               mapcache_extent_i *visible_limit = apr_pcalloc(ctx->pool, sizeof(mapcache_extent_i));
960               mapcache_grid_compute_limits_at_level(grid,visible_extent,visible_limit,tolerance,i);
961               APR_ARRAY_PUSH(rule_clone->visible_limits, mapcache_extent_i*) = visible_limit;
962             }
963           }
964           APR_ARRAY_PUSH(gridlink->rules, mapcache_rule*) = rule_clone;
965         } else {
966           // no rule for zoom level
967           APR_ARRAY_PUSH(gridlink->rules, mapcache_rule*) = NULL;
968         }
969       }
970     }
971 
972     sTolerance = (char*)ezxml_attr(cur_node,"minzoom");
973     if(sTolerance) {
974       char *endptr;
975       tolerance = (int)strtol(sTolerance,&endptr,10);
976       if(*endptr != 0 || tolerance < 0) {
977         ctx->set_error(ctx, 400, "failed to parse grid minzoom %s (expecting a positive integer)",
978                        sTolerance);
979         return;
980       }
981       gridlink->minz = tolerance;
982     }
983 
984     sTolerance = (char*)ezxml_attr(cur_node,"maxzoom");
985     if(sTolerance) {
986       char *endptr;
987       tolerance = (int)strtol(sTolerance,&endptr,10);
988       if(*endptr != 0 || tolerance < 0) {
989         ctx->set_error(ctx, 400, "failed to parse grid maxzoom %s (expecting a positive integer)",
990                        sTolerance);
991         return;
992       }
993       gridlink->maxz = tolerance + 1;
994     }
995 
996     if(gridlink->minz<0 || gridlink->maxz>grid->nlevels || gridlink->minz>=gridlink->maxz) {
997       ctx->set_error(ctx, 400, "invalid grid maxzoom/minzoom %d/%d", gridlink->minz,gridlink->maxz);
998       return;
999     }
1000 
1001     sTolerance = (char*)ezxml_attr(cur_node,"max-cached-zoom");
1002     /* RFC97 implementation: check for a maximum zoomlevel to cache */
1003     if(sTolerance) {
1004       char *endptr;
1005       tolerance = (int)strtol(sTolerance,&endptr,10);
1006       if(*endptr != 0 || tolerance < 0) {
1007         ctx->set_error(ctx, 400, "failed to parse grid max-cached-zoom %s (expecting a positive integer)",
1008                        sTolerance);
1009         return;
1010       }
1011 
1012       if(tolerance > gridlink->maxz) {
1013         ctx->set_error(ctx, 400, "failed to parse grid max-cached-zoom %s (max cached zoom is greater than grid's max zoom)",
1014                        sTolerance);
1015         return;
1016       }
1017       gridlink->max_cached_zoom = tolerance;
1018 
1019       /* default to reassembling */
1020       gridlink->outofzoom_strategy = MAPCACHE_OUTOFZOOM_REASSEMBLE;
1021       sTolerance = (char*)ezxml_attr(cur_node,"out-of-zoom-strategy");
1022       if(sTolerance) {
1023         if(!strcasecmp(sTolerance,"reassemble")) {
1024           gridlink->outofzoom_strategy = MAPCACHE_OUTOFZOOM_REASSEMBLE;
1025         }
1026         else if(!strcasecmp(sTolerance,"proxy")) {
1027           gridlink->outofzoom_strategy = MAPCACHE_OUTOFZOOM_PROXY;
1028         } else {
1029           ctx->set_error(ctx, 400, "failed to parse grid out-of-zoom-strategy %s (expecting \"reassemble\" or \"proxy\")",
1030                         sTolerance);
1031           return;
1032         }
1033       }
1034     }
1035 
1036     /* compute wgs84 bbox if it wasn't supplied already */
1037     if(!havewgs84bbox && !strcasecmp(grid->srs,"EPSG:4326")) {
1038       tileset->wgs84bbox = *extent;
1039     }
1040 
1041     if(gridlink->intermediate_grids->nelts > 0) {
1042       double factor = 0.5, unitheight,unitwidth;
1043       int i;
1044       mapcache_grid_link *igl = APR_ARRAY_IDX(gridlink->intermediate_grids, 0, mapcache_grid_link*);
1045       igl->restricted_extent = gridlink->restricted_extent;
1046       igl->minz = gridlink->minz;
1047       igl->max_cached_zoom = gridlink->max_cached_zoom - 1;
1048       igl->maxz = gridlink->maxz - 1;
1049       igl->outofzoom_strategy = gridlink->outofzoom_strategy;
1050       igl->grid = mapcache_grid_create(ctx->pool);
1051       igl->grid->extent = gridlink->grid->extent;
1052       igl->grid->name = apr_psprintf(ctx->pool,"%s_intermediate_%g",gridlink->grid->name,factor);
1053       igl->grid->nlevels = gridlink->grid->nlevels - 1;
1054       igl->grid->origin = gridlink->grid->origin;
1055       igl->grid->srs = gridlink->grid->srs;
1056       igl->grid->srs_aliases = gridlink->grid->srs_aliases;
1057       igl->grid->unit = gridlink->grid->unit;
1058       igl->grid->tile_sx = gridlink->grid->tile_sx + gridlink->grid->tile_sx * factor;
1059       igl->grid->tile_sy = gridlink->grid->tile_sy + gridlink->grid->tile_sy * factor;
1060       igl->grid->levels = (mapcache_grid_level**)apr_pcalloc(ctx->pool, igl->grid->nlevels*sizeof(mapcache_grid_level*));
1061       for(i=0; i<igl->grid->nlevels; i++) {
1062         mapcache_grid_level *level = (mapcache_grid_level*)apr_pcalloc(ctx->pool,sizeof(mapcache_grid_level));
1063         level->resolution = gridlink->grid->levels[i]->resolution + (gridlink->grid->levels[i+1]->resolution - gridlink->grid->levels[i]->resolution) * factor;
1064         unitheight = igl->grid->tile_sy * level->resolution;
1065         unitwidth = igl->grid->tile_sx * level->resolution;
1066 
1067         level->maxy = ceil((igl->grid->extent.maxy-igl->grid->extent.miny - 0.01* unitheight)/unitheight);
1068         level->maxx = ceil((igl->grid->extent.maxx-igl->grid->extent.minx - 0.01* unitwidth)/unitwidth);
1069         igl->grid->levels[i] = level;
1070       }
1071       igl->grid_limits = (mapcache_extent_i*)apr_pcalloc(ctx->pool,igl->grid->nlevels*sizeof(mapcache_extent_i));
1072       mapcache_grid_compute_limits(igl->grid,extent,igl->grid_limits,tolerance);
1073     }
1074     APR_ARRAY_PUSH(tileset->grid_links,mapcache_grid_link*) = gridlink;
1075   }
1076 
1077   if ((cur_node = ezxml_child(node,"dimensions")) != NULL) {
1078     parseDimensions(ctx, cur_node, tileset);
1079     GC_CHECK_ERROR(ctx);
1080   }
1081 
1082   if ((cur_node = ezxml_child(node,"cache")) != NULL) {
1083     mapcache_cache *cache = mapcache_configuration_get_cache(config, cur_node->txt);
1084     if(!cache) {
1085       ctx->set_error(ctx, 400, "tileset \"%s\" references cache \"%s\","
1086                      " but it is not configured", name, cur_node->txt);
1087       return;
1088     }
1089     tileset->_cache = cache;
1090   }
1091 
1092   if ((cur_node = ezxml_child(node,"source")) != NULL) {
1093     mapcache_source *source = mapcache_configuration_get_source(config, cur_node->txt);
1094     if(!source) {
1095       ctx->set_error(ctx, 400, "tileset \"%s\" references source \"%s\","
1096                      " but it is not configured", name, cur_node->txt);
1097       return;
1098     }
1099     tileset->source = source;
1100   }
1101 
1102   if ((cur_node = ezxml_child(node,"metatile")) != NULL) {
1103     int *values, nvalues;
1104     value = apr_pstrdup(ctx->pool,cur_node->txt);
1105 
1106     if(MAPCACHE_SUCCESS != mapcache_util_extract_int_list(ctx, cur_node->txt, NULL,
1107         &values, &nvalues) ||
1108         nvalues != 2) {
1109       ctx->set_error(ctx, 400, "failed to parse metatile dimension %s."
1110                      "(expecting 2 space separated integers, "
1111                      "eg <metatile>5 5</metatile>",
1112                      cur_node->txt);
1113       return;
1114     }
1115     tileset->metasize_x = values[0];
1116     tileset->metasize_y = values[1];
1117   }
1118 
1119   if ((cur_node = ezxml_child(node,"watermark")) != NULL) {
1120     if(!*cur_node->txt) {
1121       ctx->set_error(ctx,400, "watermark config entry empty");
1122       return;
1123     }
1124     mapcache_tileset_add_watermark(ctx,tileset,cur_node->txt);
1125     GC_CHECK_ERROR(ctx);
1126   }
1127 
1128 
1129   if ((cur_node = ezxml_child(node,"expires")) != NULL) {
1130     char *endptr;
1131     tileset->expires = (int)strtol(cur_node->txt,&endptr,10);
1132     if(*endptr != 0) {
1133       ctx->set_error(ctx, 400, "failed to parse expires %s."
1134                      "(expecting an  integer, "
1135                      "eg <expires>3600</expires>",
1136                      cur_node->txt);
1137       return;
1138     }
1139   }
1140   if ((cur_node = ezxml_child(node,"auto_expire")) != NULL) {
1141     char *endptr;
1142     tileset->auto_expire = (int)strtol(cur_node->txt,&endptr,10);
1143     if(*endptr != 0) {
1144       ctx->set_error(ctx, 400, "failed to parse auto_expire %s."
1145                      "(expecting an  integer, "
1146                      "eg <auto_expire>3600</auto_expire>",
1147                      cur_node->txt);
1148       return;
1149     }
1150   }
1151 
1152   if ((cur_node = ezxml_child(node,"metabuffer")) != NULL) {
1153     char *endptr;
1154     tileset->metabuffer = (int)strtol(cur_node->txt,&endptr,10);
1155     if(*endptr != 0) {
1156       ctx->set_error(ctx, 400, "failed to parse metabuffer %s."
1157                      "(expecting an  integer, "
1158                      "eg <metabuffer>1</metabuffer>",
1159                      cur_node->txt);
1160       return;
1161     }
1162   }
1163 
1164   mapcache_tileset_configuration_check(ctx,tileset);
1165   GC_CHECK_ERROR(ctx);
1166   mapcache_configuration_add_tileset(config,tileset,name);
1167   return;
1168 }
1169 
parseServices(mapcache_context * ctx,ezxml_t root,mapcache_cfg * config)1170 void parseServices(mapcache_context *ctx, ezxml_t root, mapcache_cfg *config)
1171 {
1172   ezxml_t node;
1173   if ((node = ezxml_child(root,"wms")) != NULL) {
1174     if(!node->txt || !*node->txt || strcmp(node->txt, "false")) {
1175       config->services[MAPCACHE_SERVICE_WMS] = mapcache_service_wms_create(ctx);
1176     }
1177   }
1178   if ((node = ezxml_child(root,"wmts")) != NULL) {
1179     if(!node->txt || !*node->txt || strcmp(node->txt, "false")) {
1180       config->services[MAPCACHE_SERVICE_WMTS] = mapcache_service_wmts_create(ctx);
1181     }
1182   }
1183   if ((node = ezxml_child(root,"ve")) != NULL) {
1184     if(!node->txt || !*node->txt || strcmp(node->txt, "false")) {
1185       config->services[MAPCACHE_SERVICE_VE] = mapcache_service_ve_create(ctx);
1186     }
1187   }
1188   if ((node = ezxml_child(root,"tms")) != NULL) {
1189     if(!node->txt || !*node->txt || strcmp(node->txt, "false")) {
1190       config->services[MAPCACHE_SERVICE_TMS] = mapcache_service_tms_create(ctx);
1191     }
1192   }
1193   if ((node = ezxml_child(root,"kml")) != NULL) {
1194     if(!node->txt || !*node->txt || strcmp(node->txt, "false")) {
1195       if(!config->services[MAPCACHE_SERVICE_TMS]) {
1196         ctx->set_error(ctx,400,"kml service requires the tms service to be active");
1197         return;
1198       }
1199       config->services[MAPCACHE_SERVICE_KML] = mapcache_service_kml_create(ctx);
1200     }
1201   }
1202 
1203   if ((node = ezxml_child(root,"gmaps")) != NULL) {
1204     if(!node->txt || !*node->txt || strcmp(node->txt, "false")) {
1205       config->services[MAPCACHE_SERVICE_GMAPS] = mapcache_service_gmaps_create(ctx);
1206     }
1207   }
1208   if ((node = ezxml_child(root,"demo")) != NULL) {
1209     if(!node->txt || !*node->txt || strcmp(node->txt, "false")) {
1210       config->services[MAPCACHE_SERVICE_DEMO] = mapcache_service_demo_create(ctx);
1211       if(!config->services[MAPCACHE_SERVICE_WMS])
1212         config->services[MAPCACHE_SERVICE_WMS] = mapcache_service_wms_create(ctx);
1213     }
1214   }
1215 
1216   if(!config->services[MAPCACHE_SERVICE_WMS] &&
1217       !config->services[MAPCACHE_SERVICE_TMS] &&
1218       !config->services[MAPCACHE_SERVICE_WMTS]) {
1219     ctx->set_error(ctx, 400, "no services configured."
1220                    " You must add a <services> tag with <wmts/> <wms/> or <tms/> children");
1221     return;
1222   }
1223 }
1224 
1225 
1226 
mapcache_configuration_parse_xml(mapcache_context * ctx,const char * filename,mapcache_cfg * config)1227 void mapcache_configuration_parse_xml(mapcache_context *ctx, const char *filename, mapcache_cfg *config)
1228 {
1229   ezxml_t doc, node;
1230   const char *mode;
1231   doc = ezxml_parse_file(filename);
1232   if (doc == NULL) {
1233     ctx->set_error(ctx,400, "failed to parse file %s. Is it valid XML?", filename);
1234     goto cleanup;
1235   } else {
1236     const char *err = ezxml_error(doc);
1237     if(err && *err) {
1238       ctx->set_error(ctx,400, "failed to parse file %s: %s", filename, err);
1239       goto cleanup;
1240     }
1241   }
1242 
1243   if(strcmp(doc->name,"mapcache")) {
1244     ctx->set_error(ctx,400, "failed to parse file %s. first node is not <mapcache>", filename);
1245     goto cleanup;
1246   }
1247   mode = ezxml_attr(doc,"mode");
1248   if(mode) {
1249     if(!strcmp(mode,"combined_mirror")) {
1250       config->mode = MAPCACHE_MODE_MIRROR_COMBINED;
1251     } else if(!strcmp(mode,"split_mirror")) {
1252       config->mode = MAPCACHE_MODE_MIRROR_SPLIT;
1253     } else if(!strcmp(mode,"normal")) {
1254       config->mode = MAPCACHE_MODE_NORMAL;
1255     } else {
1256       ctx->set_error(ctx,400,"unknown mode \"%s\" for <mapcache>",mode);
1257       goto cleanup;
1258     }
1259   } else {
1260     config->mode = MAPCACHE_MODE_NORMAL;
1261   }
1262 
1263   for(node = ezxml_child(doc,"metadata"); node; node = node->next) {
1264     parseMetadata(ctx, node, config->metadata);
1265     if(GC_HAS_ERROR(ctx)) goto cleanup;
1266   }
1267 
1268   for(node = ezxml_child(doc,"source"); node; node = node->next) {
1269     parseSource(ctx, node, config);
1270     if(GC_HAS_ERROR(ctx)) goto cleanup;
1271   }
1272 
1273   for(node = ezxml_child(doc,"grid"); node; node = node->next) {
1274     parseGrid(ctx, node, config);
1275     if(GC_HAS_ERROR(ctx)) goto cleanup;
1276   }
1277 
1278   for(node = ezxml_child(doc,"format"); node; node = node->next) {
1279     parseFormat(ctx, node, config);
1280     if(GC_HAS_ERROR(ctx)) goto cleanup;
1281   }
1282 
1283   for(node = ezxml_child(doc,"cache"); node; node = node->next) {
1284     parseCache(ctx, node, config);
1285     if(GC_HAS_ERROR(ctx)) goto cleanup;
1286   }
1287 
1288   for(node = ezxml_child(doc,"ruleset"); node; node = node->next) {
1289     parseRuleset(ctx, node, config);
1290     if(GC_HAS_ERROR(ctx)) goto cleanup;
1291   }
1292 
1293   for(node = ezxml_child(doc,"tileset"); node; node = node->next) {
1294     parseTileset(ctx, node, config);
1295     if(GC_HAS_ERROR(ctx)) goto cleanup;
1296   }
1297 
1298   if ((node = ezxml_child(doc,"service")) != NULL) {
1299     ezxml_t service_node;
1300     for(service_node = node; service_node; service_node = service_node->next) {
1301       char *enabled = (char*)ezxml_attr(service_node,"enabled");
1302       char *type = (char*)ezxml_attr(service_node,"type");
1303       if(!strcasecmp(enabled,"true")) {
1304         if (!strcasecmp(type,"wms")) {
1305           mapcache_service *new_service = mapcache_service_wms_create(ctx);
1306           if(new_service->configuration_parse_xml) {
1307             new_service->configuration_parse_xml(ctx,service_node,new_service,config);
1308           }
1309           config->services[MAPCACHE_SERVICE_WMS] = new_service;
1310         } else if (!strcasecmp(type,"tms")) {
1311           mapcache_service *new_service = mapcache_service_tms_create(ctx);
1312           if(new_service->configuration_parse_xml) {
1313             new_service->configuration_parse_xml(ctx,service_node,new_service,config);
1314           }
1315           config->services[MAPCACHE_SERVICE_TMS] = new_service;
1316         } else if (!strcasecmp(type,"wmts")) {
1317           mapcache_service *new_service = mapcache_service_wmts_create(ctx);
1318           if(new_service->configuration_parse_xml) {
1319             new_service->configuration_parse_xml(ctx,service_node,new_service,config);
1320           }
1321           config->services[MAPCACHE_SERVICE_WMTS] = new_service;
1322         } else if (!strcasecmp(type,"kml")) {
1323           mapcache_service *new_service = mapcache_service_kml_create(ctx);
1324           if(new_service->configuration_parse_xml) {
1325             new_service->configuration_parse_xml(ctx,service_node,new_service,config);
1326           }
1327           config->services[MAPCACHE_SERVICE_KML] = new_service;
1328         } else if (!strcasecmp(type,"gmaps")) {
1329           mapcache_service *new_service = mapcache_service_gmaps_create(ctx);
1330           if(new_service->configuration_parse_xml) {
1331             new_service->configuration_parse_xml(ctx,service_node,new_service,config);
1332           }
1333           config->services[MAPCACHE_SERVICE_GMAPS] = new_service;
1334         } else if (!strcasecmp(type,"mapguide")) {
1335           mapcache_service *new_service = mapcache_service_mapguide_create(ctx);
1336           if(new_service->configuration_parse_xml) {
1337             new_service->configuration_parse_xml(ctx,service_node,new_service,config);
1338           }
1339           config->services[MAPCACHE_SERVICE_MAPGUIDE] = new_service;
1340         } else if (!strcasecmp(type,"ve")) {
1341           mapcache_service *new_service = mapcache_service_ve_create(ctx);
1342           if(new_service->configuration_parse_xml) {
1343             new_service->configuration_parse_xml(ctx,service_node,new_service,config);
1344           }
1345           config->services[MAPCACHE_SERVICE_VE] = new_service;
1346         } else if (!strcasecmp(type,"demo")) {
1347           mapcache_service *new_service = mapcache_service_demo_create(ctx);
1348           if(new_service->configuration_parse_xml) {
1349             new_service->configuration_parse_xml(ctx,service_node,new_service,config);
1350           }
1351           config->services[MAPCACHE_SERVICE_DEMO] = new_service;
1352         } else {
1353           ctx->set_error(ctx,400,"unknown <service> type %s",type);
1354         }
1355         if(GC_HAS_ERROR(ctx)) goto cleanup;
1356       }
1357     }
1358   } else if ((node = ezxml_child(doc,"services")) != NULL) {
1359     ctx->log(ctx,MAPCACHE_WARN,"<services> tag is deprecated, use <service type=\"wms\" enabled=\"true|false\">");
1360     parseServices(ctx, node, config);
1361   }
1362   if(GC_HAS_ERROR(ctx)) goto cleanup;
1363 
1364 
1365   node = ezxml_child(doc,"default_format");
1366   if(!node)
1367     node = ezxml_child(doc,"merge_format");
1368   if (node) {
1369     mapcache_image_format *format = mapcache_configuration_get_image_format(config,node->txt);
1370     if(!format) {
1371       ctx->set_error(ctx, 400, "default_format tag references format %s but it is not configured",
1372                      node->txt);
1373       goto cleanup;
1374     }
1375     config->default_image_format = format;
1376   }
1377 
1378   if ((node = ezxml_child(doc,"errors")) != NULL) {
1379     if(!strcmp(node->txt,"log")) {
1380       config->reporting = MAPCACHE_REPORT_LOG;
1381     } else if(!strcmp(node->txt,"report")) {
1382       config->reporting = MAPCACHE_REPORT_MSG;
1383     } else if(!strcmp(node->txt,"empty_img")) {
1384       config->reporting = MAPCACHE_REPORT_EMPTY_IMG;
1385       mapcache_image_create_empty(ctx, config);
1386       if(GC_HAS_ERROR(ctx)) goto cleanup;
1387     } else if(!strcmp(node->txt, "report_img")) {
1388       config->reporting = MAPCACHE_REPORT_ERROR_IMG;
1389       ctx->set_error(ctx,501,"<errors>: report_img not implemented");
1390       goto cleanup;
1391     } else {
1392       ctx->set_error(ctx,400,"<errors>: unknown value %s (allowed are log, report, empty_img, report_img)",
1393                      node->txt);
1394       goto cleanup;
1395     }
1396   }
1397 
1398   if((node = ezxml_child(doc,"locker")) != NULL) {
1399     mapcache_config_parse_locker(ctx,node,&config->locker);
1400     GC_CHECK_ERROR(ctx);
1401   } else {
1402     mapcache_config_parse_locker_old(ctx,doc,config);
1403     GC_CHECK_ERROR(ctx);
1404   }
1405 
1406   if((node = ezxml_child(doc,"threaded_fetching")) != NULL) {
1407     if(!strcasecmp(node->txt,"true")) {
1408       config->threaded_fetching = 1;
1409     } else if(strcasecmp(node->txt,"false")) {
1410       ctx->set_error(ctx, 400, "failed to parse threaded_fetching \"%s\". Expecting true or false",node->txt);
1411       return;
1412     }
1413   }
1414 
1415   if((node = ezxml_child(doc,"log_level")) != NULL) {
1416     if(!strcasecmp(node->txt,"debug")) {
1417       config->loglevel = MAPCACHE_DEBUG;
1418     } else if(!strcasecmp(node->txt,"info")) {
1419       config->loglevel = MAPCACHE_INFO;
1420     } else if(!strcasecmp(node->txt,"notice")) {
1421       config->loglevel = MAPCACHE_NOTICE;
1422     } else if(!strcasecmp(node->txt,"warn")) {
1423       config->loglevel = MAPCACHE_WARN;
1424     } else if(!strcasecmp(node->txt,"error")) {
1425       config->loglevel = MAPCACHE_ERROR;
1426     } else if(!strcasecmp(node->txt,"crit")) {
1427       config->loglevel = MAPCACHE_CRIT;
1428     } else if(!strcasecmp(node->txt,"alert")) {
1429       config->loglevel = MAPCACHE_ALERT;
1430     } else if(!strcasecmp(node->txt,"emerg")) {
1431       config->loglevel = MAPCACHE_EMERG;
1432     } else {
1433       ctx->set_error(ctx,500,"failed to parse <log_level> \"%s\". Expecting debug, info, notice, warn, error, crit, alert or emerg",node->txt);
1434       return;
1435     }
1436   }
1437   if((node = ezxml_child(doc,"auto_reload")) != NULL) {
1438     if(!strcasecmp(node->txt,"true")) {
1439       config->autoreload = 1;
1440     } else if(!strcasecmp(node->txt,"false")) {
1441       config->autoreload = 0;
1442     } else {
1443       ctx->set_error(ctx,500,"failed to parse <auto_reload> \"%s\". Expecting true or false",node->txt);
1444       return;
1445     }
1446   }
1447 
1448   config->cp_hmax = 1024;
1449   config->cp_ttl = 60*1000*1000;
1450   if((node = ezxml_child(doc,"connection_pool")) != NULL) {
1451     ezxml_t cp_param_node;
1452     char *endptr;
1453     if ((cp_param_node = ezxml_child(node,"max_connections")) != NULL) {
1454       config->cp_hmax = (int)strtol(cp_param_node->txt,&endptr,10);
1455       if (*endptr != 0 || config->cp_hmax < 0) {
1456         ctx->set_error(ctx, 400, "failed to parse max_connections %s "
1457             "(expecting a positive integer)", cp_param_node->txt);
1458         return;
1459       }
1460     }
1461     if ((cp_param_node = ezxml_child(node,"time_to_live_us")) != NULL) {
1462       config->cp_ttl = (int)strtol(cp_param_node->txt,&endptr,10);
1463       if (*endptr != 0 || config->cp_ttl < 0) {
1464         ctx->set_error(ctx, 400, "failed to parse time_to_live_us %s "
1465             "(expecting a positive integer)", cp_param_node->txt);
1466         return;
1467       }
1468     }
1469   }
1470 
1471 cleanup:
1472   ezxml_free(doc);
1473   return;
1474 }
1475 /* vim: ts=2 sts=2 et sw=2
1476 */
1477