/* * mod_mapserver.c -- Apache sample wms module */ #include "httpd.h" #include "http_config.h" #include "http_protocol.h" #include "http_log.h" #include "ap_config.h" #include "apr.h" #include "apr_strings.h" #include "apr_tables.h" #include "apr_file_info.h" #include "mapserver.h" /* for mapObj */ #include "cgiutil.h" #include "mapserv.h" module AP_MODULE_DECLARE_DATA mapserver_module; typedef struct { apr_pool_t *config_pool; apr_time_t mtime; char *mapfile_name; char *uri; mapObj *map; } mapserver_dir_config; /* These are the IO redirection hooks. They are mostly copied over from * mapio.c. Note that cbData contains Apache's request_rec! */ static int msIO_apacheWrite (void *cbData, void *data, int byteCount) { /* simply use the block writing function which is very similiar to fwrite */ return ap_rwrite (data, byteCount, (request_rec*) cbData); } static int msIO_apacheError (void *cbData, void *data, int byteCount) { /* error reporting is done through the log file... */ ap_log_error (APLOG_MARK, APLOG_ERR, 0, NULL, (char*) data); return strlen ((char*) data); } int msIO_installApacheRedirect (request_rec *r) { msIOContext stdout_ctx, stderr_ctx; stdout_ctx.label = "apache"; stdout_ctx.write_channel = MS_TRUE; stdout_ctx.readWriteFunc = msIO_apacheWrite; stdout_ctx.cbData = (void*) r; stderr_ctx.label = "apache"; stderr_ctx.write_channel = MS_TRUE; stderr_ctx.readWriteFunc = msIO_apacheError; stderr_ctx.cbData = (void*) r; msIO_installHandlers (NULL, &stdout_ctx, &stderr_ctx); return MS_TRUE; } /* The maximum number of arguments we support. The number is taken from * cgiutil.h I think. It is only used for easier param decoding. */ #define WMS_MAX_ARGS 100 /* This is our simple query decoding function. Should be improved but for now * it works... */ static int mapserver_decode_args (apr_pool_t *p, char *args, char ***ParamNames, char ***ParamValues) { char **argv = NULL; int i; int n; int argc = 0; char *sep; /* alloc the name/value pointer list */ argv = (char**) apr_pcalloc (p, (WMS_MAX_ARGS + 1) * 2 * sizeof (char*)); *ParamNames = argv; *ParamValues = argv + WMS_MAX_ARGS + 1; /* No arguments? Then we're done */ if (!args) return 0; argv [0] = args; /* separate the arguments */ for (i = 1, n = 0; args [n] && (i < WMS_MAX_ARGS); n++) if (args [n] == '&') { argv [i++] = args + n + 1; args [n ] = '\0'; } /* eleminate empty args */ for (n = 0, i = 0; argv [i]; i++) if (*(argv [i]) != '\0') argv [n++] = argv [i]; else argv [i ] = NULL; /* argument count is the number of non-zero arguments! */ argc = n; /* split the name/value pairs */ for (i = 0; argv [i]; i++) { sep = strchr (argv [i], '='); if (!sep) continue; *sep = '\0'; argv [i + WMS_MAX_ARGS + 1] = (char*) apr_pstrdup (p, sep + 1); if (ap_unescape_url (argv [i + WMS_MAX_ARGS + 1]) == HTTP_BAD_REQUEST) { ap_log_error (APLOG_MARK, APLOG_ERR, 0, NULL, "%s: malformed URI, couldn't unescape parm %s", __func__, argv [i]); argv [i + WMS_MAX_ARGS + 1] = NULL; } else { plustospace (argv [i + WMS_MAX_ARGS + 1]); } } return argc; } static char* mapserver_read_post_data (request_rec *r) { int status; int size; long blen, rsize, rpos = 0; char *buffer = NULL; char buf [512]; if ((status = ap_setup_client_block (r, REQUEST_CHUNKED_ERROR)) != OK) return NULL; if (!ap_should_client_block (r)) return NULL; buffer = (char*) apr_palloc (r->pool, r->remaining + 1); size = r->remaining; buffer [size] = '\0'; if (!buffer) return NULL; while ((blen = ap_get_client_block (r, buf, sizeof (buf))) > 0) { if (rpos + blen > size) { rsize = blen - rpos; } else { rsize = blen; } memcpy ((char*) buffer + rpos, buf, rsize); rpos += rsize; } return buffer; } /* ** Extract Map File name from params and load it. ** Returns map object or NULL on error. */ static mapObj* msModuleLoadMap(mapservObj *mapserv, mapserver_dir_config *conf) { int i; /* OK, here's the magic: we take the mapObj from our stored config. * We will use a copy of it created by msCopyMap since MapServer * modifies the object at several places during request processing */ mapObj *map = msNewMapObj (); if(!map) return NULL; msCopyMap (map, conf->map); /* check for any %variable% substitutions here, also do any map_ changes, we do this here so WMS/WFS */ /* services can take advantage of these "vendor specific" extensions */ for(i=0;irequest->NumParams;i++) { /* ** a few CGI variables should be skipped altogether ** ** qstring: there is separate per layer validation for attribute queries and the substitution checks ** below conflict with that so we avoid it here */ if(strncasecmp(mapserv->request->ParamNames[i],"qstring",7) == 0) continue; if(strncasecmp(mapserv->request->ParamNames[i],"map_",4) == 0 || strncasecmp(mapserv->request->ParamNames[i],"map.",4) == 0) { /* check to see if there are any additions to the mapfile */ if(msUpdateMapFromURL(map, mapserv->request->ParamNames[i], mapserv->request->ParamValues[i]) != MS_SUCCESS) { msFreeMap(map); return NULL; } continue; } } msApplySubstitutions(map, mapserv->request->ParamNames, mapserv->request->ParamValues, mapserv->request->NumParams); msApplyDefaultSubstitutions(map); /* check to see if a ogc map context is passed as argument. if there */ /* is one load it */ for(i=0;irequest->NumParams;i++) { if(strcasecmp(mapserv->request->ParamNames[i],"context") == 0) { if(mapserv->request->ParamValues[i] && strlen(mapserv->request->ParamValues[i]) > 0) { if(strncasecmp(mapserv->request->ParamValues[i],"http",4) == 0) { if(msGetConfigOption(map, "CGI_CONTEXT_URL")) msLoadMapContextURL(map, mapserv->request->ParamValues[i], MS_FALSE); } else msLoadMapContext(map, mapserv->request->ParamValues[i], MS_FALSE); } } } /* * RFC-42 HTTP Cookie Forwarding * Here we set the http_cookie_data metadata to handle the * HTTP Cookie Forwarding. The content of this metadata is the cookie * content. In the future, this metadata will probably be replaced * by an object that is part of the mapObject that would contain * information on the application status (such as cookie). */ if( mapserv->request->httpcookiedata != NULL ) { msInsertHashTable( &(map->web.metadata), "http_cookie_data", mapserv->request->httpcookiedata ); } return map; } /*** * The main request handler. **/ static int mapserver_handler (request_rec *r) { /* aquire the apropriate configuration for this directory */ mapserver_dir_config *conf; conf = (mapserver_dir_config*) ap_get_module_config (r->per_dir_config, &mapserver_module); /* decline the request if there's no map configured */ if (!conf || !conf->map) return DECLINED; apr_finfo_t mapstat; if (apr_stat (&mapstat, conf->mapfile_name, APR_FINFO_MTIME, r->pool) == APR_SUCCESS) { if (apr_time_sec (mapstat.mtime) > apr_time_sec (conf->mtime)) { mapObj *newmap = msLoadMap (conf->mapfile_name, NULL); if (newmap) { msFreeMap (conf->map); conf->map = newmap; conf->mtime = mapstat.mtime; } else { ap_log_error (APLOG_MARK, APLOG_WARNING, 0, NULL, "unable to reload map file %s", conf->mapfile_name); } } } else { ap_log_error (APLOG_MARK, APLOG_WARNING, 0, NULL, "%s: unable to stat file %s", __func__, conf->mapfile_name); } /* make a copy of the URI so we can modify it safely */ char *uri = apr_pstrdup (r->pool, r->uri); int len = strlen (uri); int conf_uri_len = strlen (conf->uri); /* If the URI points to a subdirectory we want to decline. */ if (len > conf_uri_len) return DECLINED; int argc = 0; char **ParamNames = NULL; char **ParamValues = NULL; char *post_data = NULL; int szMethod = -1; char *szContentType = NULL; mapservObj *mapserv = NULL; /* Try decoding the query string */ if (r->method_number == M_GET) { argc = mapserver_decode_args (r->pool, (char*) apr_pstrdup (r->pool, r->args), &ParamNames, &ParamValues); szMethod = MS_GET_REQUEST; } else if (r->method_number == M_POST) { szContentType = (char*) apr_table_get (r->headers_in, "Content-type"); post_data = mapserver_read_post_data (r); szMethod = MS_POST_REQUEST; if (strcmp (szContentType, "application/x-www-form-urlencoded") == 0) { argc = mapserver_decode_args (r->pool, (char*) apr_pstrdup (r->pool, r->args), &ParamNames, &ParamValues); } } else return HTTP_METHOD_NOT_ALLOWED; if (!argc && !post_data) return HTTP_BAD_REQUEST; /* Now we install the IO redirection. */ if (msIO_installApacheRedirect (r) != MS_TRUE) ap_log_error (APLOG_MARK, APLOG_ERR, 0, NULL, "%s: could not install apache redirect", __func__); mapserv = msAllocMapServObj(); mapserv->request->NumParams = argc; mapserv->request->ParamNames = ParamNames; mapserv->request->ParamValues = ParamValues; mapserv->request->type = (enum MS_REQUEST_TYPE) szMethod; mapserv->request->postrequest = post_data; mapserv->request->contenttype = szContentType; //mapserv->map = msModuleLoadMap(mapserv,conf); mapserv->map = conf->map; if(!mapserv->map) { msCGIWriteError(mapserv); goto end_request; } if(msCGIDispatchRequest(mapserv) != MS_SUCCESS) { msCGIWriteError(mapserv); goto end_request; } end_request: if(mapserv) { msCGIWriteLog(mapserv,MS_FALSE); mapserv->request->ParamNames = NULL; mapserv->request->ParamValues = NULL; mapserv->request->postrequest = NULL; mapserv->request->contenttype = NULL; mapserv->map = NULL; msFreeMapServObj(mapserv); } msResetErrorList(); /* Check if status was set inside MapServer functions. If it was, we * return it's value instead of simply OK. This is to support redirects * from maptemplate.c */ if (r->status == HTTP_MOVED_TEMPORARILY) return r->status; return OK; } /* This function will be called on startup to allow us to register our * handler. We don't do anything else yet... */ static void mapserver_register_hooks (apr_pool_t *p) { ap_hook_handler (mapserver_handler, NULL, NULL, APR_HOOK_MIDDLE); } /* That's the second part of the overall magic. This function will be called * when the configuration reaches the "WMS_Map" parameter. It will be passed * in as argument and should contain the full path and name of the map file. * We try to load it and store the resulting mapObj in the config of the * current directory. If we fail we throw a message... */ static const char* mapserver_set_map (cmd_parms *cmd, void *config, const char *arg) { mapserver_dir_config *conf = (mapserver_dir_config*) config; /* if the mapObj already exists the WMS_Map was given more than once - * may be the user forgot to comment something out... */ if (conf->map) { msWriteError (stderr); return (char*) apr_psprintf (cmd->temp_pool, "An MAP-file has already been registered for " "this URI - not accepting '%s'.", arg ); } /* Simply try loading the argument as map file. */ conf->mapfile_name = apr_pstrdup(cmd->pool, arg); conf->map = msLoadMap ((char*) arg, NULL); /* Ooops - we failed. We report it and fail. So beware: Always do a * configcheck before really restarting your web server! */ if (!conf->map) { msWriteError (stderr); return (char*) apr_psprintf (cmd->temp_pool, "The given MAP-file '%s' could not be loaded", arg); } apr_finfo_t status; if (apr_stat (&status, conf->mapfile_name, APR_FINFO_MTIME, cmd->pool) != APR_SUCCESS) { ap_log_error (APLOG_MARK, APLOG_WARNING, 0, NULL, "%s: unable to stat file %s", __func__, conf->mapfile_name); } conf->mtime = status.mtime; return NULL; } /* This function creates the (default) directory configuration. Well, we have * no defaults to set so we simply alloc the memory... */ static void* mapserver_create_dir_config (apr_pool_t *p, char *dir) { mapserver_dir_config *newconf; newconf = (mapserver_dir_config*) apr_pcalloc (p, sizeof (mapserver_dir_config)); newconf->config_pool = p; newconf->uri = apr_pstrdup (p, dir); newconf->map = NULL; newconf->mapfile_name = NULL; newconf->mtime = apr_time_from_sec (0); if (dir) { int len = strlen (dir); if (len > 1 && dir [len - 1] == '/' ) newconf->uri [len - 1] = '\0'; } return newconf; } /* This structure defines our single config option which takes exactly one * argument. */ static const command_rec mapserver_options[] = { AP_INIT_TAKE1( "Mapfile", mapserver_set_map, NULL, ACCESS_CONF, "WMS_Map " ), {NULL} }; /* The following structure declares everything Apache needs to treat us as * a module. Merging would mean inheriting from the parent directory but that * doesn't make much sense here since we would only inherit the map file... */ /* Dispatch list for API hooks */ module AP_MODULE_DECLARE_DATA mapserver_module = { STANDARD20_MODULE_STUFF, mapserver_create_dir_config, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ NULL, /* create per-server config structures */ NULL, /* merge per-server config structures */ mapserver_options, /* table of config file commands */ mapserver_register_hooks /* register hooks */ };