|
|
This example can be found in the examples/c/alias directory.
/*
* This example module implements a URL->file aliasing extension to the
* AOLserver using the Ns_SetUrlToFileProc function from the C API. Loading
* this module enables you to define mappings such as
* /oldplace/foo.html=newplace/foo.html. This module maintains compatibility
* with the existing (default) AOLserver UrlToFile function by supporting
* UserMapDir and PageRoot configuration variables, both of which are
* subsumed by the expressive capabilities of this module.
*
* You can load and configure this module by editing your nsd.ini file as
* follows:
* 1) In your [ns\server\server-name\modules] section, add the
* following:
* alias=alias.dll ;or alias.so on Unix platforms
* 2) Add a section for the alias module that contains your aliases. E.g.:
* [ns\server\server-name\module\alias]
* /oldplace/foo.html=newplace/foo.html
* ...
*
* Note that you can accomplish the above with the setup interface by using the
* "List AOLserver Configuration Sections" available from the "Setup Home"
* page in expert mode, or you can edit the nsd.ini file with your favorite
* text editor.
*
* When defining maps with key/value pairs, keep the following in mind:
* 1) The key (`/oldplace/foo.html' above) is in "URI space" and the value
* (`newplace/foo.html' above) is in "file space".
* 2) By default, the file space value (`newplace/foo.html' above) is assumed
* to be relative to the page root defined for the "server-name"
* virtual server. In "file space" (right side of =), you can also
* specify an absolute pathname such as /home/user/html/index.html
* or c:\html\index.html (NT). So, be careful not to use a leading `/'
* on the right side of the `=' unless you mean it.
* 3) In "file space", `~username' expands to the user's default directory, and
* `~' looks for a user name in the second element of the URI, substituting
* the user's home directory appropriately.
* 4) In "URI space", a leading `/~' mapped to a leading `~' in file space supports
* the common ~user usage in URI space.
*
* Now, let's clear things up with an example. Note that the comments following each
* mapping show a sample URI input followed by sample output generated by this module.
* [ns\server\server-name\module\alias]
* /user1=~godzilla ;/user1 -> /home/godzilla
* /user2=~/pages ;/user2/bambi -> /home/bambi/pages
* ;OR /user2/bambi/foo -> /home/bambi/pages/foo
* /user3=~ ;/user3/bambi -> /home/bambi
* ;OR /user3/bambi/html -> /home/bambi/html
* /~=~/pages ;/~foot -> /home/foot/pages
* ;OR /~foot/section -> /home/foot/pages/section
*
* Note that the last entry obviates the need for the `UserMapDir' configuration
* parameter. To provide compatibility with the default Ns_UrlToFile function, we'll
* insert a mapping to reflect the UserMapDir if it is set in the nsd.ini file.
* If UserMapDir is defined and there is a mapping of the form: /~=~/otherplace,
* the mapping takes precedence.
*
* Another mapping, more pervasive than the previous examples, is the following:
* /=/home/newroot
* This overrides the PageRoot parameter defined in the server-specific section
* of nsd.ini. As you would expect, it also defines the implicit root for all other
* relative "file space" (right side of `=') values that appear in other aliases.
*
*/
#include "ns.h"
#include <assert.h>
#include <string.h>
#define MAPMETHOD "GET" /* placeholder in the general-purpose URL-space routines */
/* The following string is appended to user directories (for compatibility) */
#define CONFIG_USERMAPDIR "UserMapDir"
#define MAX_USERNAME 16
/*
* This data will be made available to our custom UrlToFile function
* (AliasedUrlToFile)
*/
typedef struct {
char *fromUri;
char *toUri;
} UriMap;
static Ns_UrlToFileProc AliasedUrlToFile;
static void UriMapFree(UriMap * map);
static int TildeReplace(char *hServer, Ns_DString * dsOut, char *in, char *uri);
static UriMap *NewMap(char *from, char *to);
/*
* The Ns_ModuleVersion exported integer is used to verify this module
* version when loaded. For AOLserver 2.0, 1 (one) is the only valid value
* for this variable.
*/
DllExport int Ns_ModuleVersion = 1;
/*
* The following are global IDs used for server-specific storage and
* retrieval of data
*/
static int idAliases = -1;
/*
* The Ns_ModuleInit function is the function the AOLserver will call each
* time the module is loaded into a virtual server. The function is passed
* two parameters:
*
* hServer: The server `handle' as a string. This is the short name given to
* the virutal server such as `server1'.
*
* hModule: The module `handle' as a string. This is the short name given to
* the module such as `alias'
*
* For example, if this module is known as `alias' and loaded into the `server1'
* server with entries similar to the following in the nsd.ini file:
*
* [ns\servers]
* server1=My First Server
*
* [ns\server1\modules]
* alias=alias.dll
*
* This function would be called with "server1" and "alias" as its arguments.
*
*/
DllExport int
Ns_ModuleInit(char *hServer, char *hModule)
{
char *moduleConfigPath;
char *serverConfigPath;
char *userMapDir;
Ns_Set *aliases;
int i;
if (idAliases < 0) {
idAliases = Ns_ServerSpecificAlloc();
}
serverConfigPath = Ns_ConfigGetPath(hServer, NULL, NULL);
/*
* for compatibility with the default UrlToFile, create entry for UserMapDir
* if it is defined
*/
if ((userMapDir = Ns_ConfigGetValue(serverConfigPath, CONFIG_USERMAPDIR)) !=
NULL) {
UriMap *map;
Ns_DString dsFilePattern;
Ns_DStringInit(&dsFilePattern);
Ns_DStringVarAppend(&dsFilePattern, "~/", userMapDir, NULL);
map = NewMap("/~", Ns_DStringExport(&dsFilePattern));
Ns_Log(Notice, "Ns_ModuleInit(%s,%s): Mapping %s to %s", hServer, hModule,
map->fromUri, map->toUri);
Ns_UrlSpecificSet(hServer, MAPMETHOD, map->fromUri, idAliases,
map, 0, (void (*) (void *)) UriMapFree);
}
if ((moduleConfigPath = Ns_ConfigGetPath(hServer, hModule, NULL)) == NULL) {
Ns_Log(Warning,
"Ns_ModuleInit(%s,%s): No file aliases section found in config file",
hServer, hModule);
} else {
if ((aliases = Ns_ConfigGetSection(moduleConfigPath)) != NULL) {
if (Ns_SetSize(aliases) > 0) {
Ns_DString dsNormalizedKey;
Ns_DStringInit(&dsNormalizedKey);
/*
* The following function causes the
* AOLserver to call our AliasedUrlToFile
* function instead of its default when
* mapping a URI to a filename.
*/
Ns_SetUrlToFileProc(hServer, AliasedUrlToFile);
for (i = 0; i < Ns_SetSize(aliases); ++i) {
UriMap *map;
char *value;
/*
* Normalize the path so that we can
* use it for matching in
* AliasedUrlToFile
*/
Ns_NormalizePath(&dsNormalizedKey, Ns_SetKey(aliases, i));
value = Ns_SetValue(aliases, i);
if ((strncmp(dsNormalizedKey.string, "/~",2)==0) &&
(value[0] != `~')) {
Ns_Log(Warning,
"AliasedUrlToFile(%s): %s->%s ignored, %s must start with `~'",
hServer, dsNormalizedKey.string, value, value);
} else {
map = NewMap(ns_strdup(dsNormalizedKey.string),
ns_strdup(value));
Ns_Log(Notice, "Ns_ModuleInit(%s,%s): Mapping %s to %s",
hServer, hModule, map->fromUri, map->toUri);
Ns_UrlSpecificSet(hServer, MAPMETHOD, map->fromUri,
idAliases, map, 0, (void (*) (void *)) UriMapFree);
}
Ns_DStringTrunc(&dsNormalizedKey, 0);
}
Ns_DStringFree(&dsNormalizedKey);
}
}
}
return NS_OK;
}
/*
* This function, registered above via Ns_SetUrlToFileProc, will be called by
* the AOLserver when it maps a URL to a file.
*/
static int
AliasedUrlToFile(Ns_DString * dest, char *hServer, char *relpath)
{
int retval = NS_OK;
Ns_DString dsAliasedPath;
UriMap *map;
assert(relpath != NULL);
assert(dest != NULL);
Ns_DStringInit(&dsAliasedPath);
/* special handling of `/~' pattern in URI */
if (relpath[0]=='/' && relpath[1]=='~') {
if ((map = Ns_UrlSpecificGet(hServer, MAPMETHOD, "/~", idAliases)) == NULL) {
Ns_Log(Error, "AliasedUrlToFile(%s): URI %s has not been aliased",
hServer, relpath);
retval = NS_ERROR;
} else if (map->toUri[0]=='~') {
retval = TildeReplace(hServer, &dsAliasedPath, map->toUri, relpath);
} else {
Ns_Log(Bug,
"AliasedUrlToFile(%s): In mapping %s->%s, %s must contain a `~'",
hServer, map->fromUri, map->toUri, map->toUri);
retval = NS_ERROR;
}
} else {
/*
* Check whether the URI (relpath) has been registered for
* aliasing...
*/
if ((map = Ns_UrlSpecificGet(hServer, MAPMETHOD, relpath, idAliases))
== NULL) {
/* no match, copy relpath to destination */
Ns_DStringAppend(&dsAliasedPath, relpath);
} else {
/* matched, check for `~', exact match or initial subpath match of file
pattern*/
int relpathLen, fromUriLen;
if (map->toUri[0] == `~') {
retval = TildeReplace(hServer, &dsAliasedPath, map->toUri, relpath);
} else {
fromUriLen = strlen(map->fromUri);
relpathLen = strlen(relpath);
if (fromUriLen == relpathLen) {
/* exact match, use simple substitution */
Ns_DStringAppend(&dsAliasedPath, map->toUri);
} else if (fromUriLen < relpathLen) {
/*
* initial subpath match, substitute only for length
* of map->fromUri
*/
Ns_DStringVarAppend(&dsAliasedPath, map->toUri,
relpath[fromUriLen]=='/' ? "" : "/",
&relpath[fromUriLen], NULL);
} else {
Ns_Log(Error, "AliasedUrlToFile(%s): %s to %s mapping is not valid",
hServer, map->fromUri, relpath);
retval = NS_ERROR;
}
}
}
}
if (retval == NS_OK) {
if (Ns_PathIsAbsolute(dsAliasedPath.string) && (map != NULL)) {
Ns_MakePath(dest, dsAliasedPath.string, NULL);
} else { /* relative path, use pageroot or / from URI space */
UriMap *map;
char *root;
/* URI `/' takes precedence over pageroot */
if ((map = Ns_UrlSpecificGet(hServer, MAPMETHOD, "/", idAliases))
== NULL) {
root = Ns_PageRoot(hServer);
} else {
root = map->toUri;
}
Ns_MakePath(dest, root, dsAliasedPath.string, NULL);
}
}
Ns_DStringFree(&dsAliasedPath);
return retval;
}
/*
* This function performs the expansion and substitution related to
* the `~' character as it appears in the URI and file patterns
* defined in the nsd.ini file, and in the URI input. In this function,
* remember that `filePattern' is the string that appeared on the right
* side of the `=' in the nsd.ini file and `uri' is the URI from our current
* request, not to be confused with the URI (left of `=') defined in the
* nsd.ini file which is used for pattern matching.
*
*/
static int
TildeReplace(char *hServer, Ns_DString * dsOut, char *filePattern, char *uri)
{
int retval = NS_OK;
char *pathRemainder;
char *nameEnd;
char *uriTail = `\0';
int found;
char user[MAX_USERNAME+1];
int len;
assert (filePattern[0] == `~');
if (filePattern[1] == `\0' || filePattern[1] == `/') { /*~ or ~/ in filePattern */
char *nameStart;
/* parse out user name, second element in uri OR string following /~ */
if (strncmp(uri, "/~", 2) == 0) { /* special case mapping of /~* = ~ */
nameStart = &uri[2];
if (*nameStart == `\0') {
nameStart = NULL;
}
} else {
/* skip past first element */
if ((nameStart = strchr(&uri[1], `/')) != NULL) {
++nameStart;
}
}
if (nameStart == NULL) {
Ns_Log(Error,
"AliasedUrlToFile:TildeReplace(%s): user name not found in URI: %s",
hServer, uri);
retval = NS_ERROR;
} else {
uriTail = strchr(nameStart, `/');
len = (uriTail==NULL) ? strlen(nameStart) : uriTail - nameStart;
if (len >= sizeof(user)) {
Ns_Log(Warning,
"AliasedUrlToFile:TildeReplace(%s): URI User name too long",
hServer);
retval = NS_ERROR;
} else {
strncpy(user, nameStart, len);
user[len] = `\0';
found = Ns_GetUserHome(dsOut, user);
pathRemainder = (filePattern[1]=='\0') ? "" : &filePattern[1];
}
}
} else {
uriTail = strchr(&uri[1], `/'); /* skip past substituted field in URI */
if ((nameEnd = strchr(&filePattern[1], `/')) == NULL) { /* ~user */
found = Ns_GetUserHome(dsOut, &filePattern[1]);
pathRemainder = "";
} else { /* ~user/more/stuff */
len = nameEnd - filePattern - 1;
if (len >= sizeof(user)) {
Ns_Log(Warning,
"AliasedUrlToFile:TildeReplace(%s): Mapped User name too long",
hServer);
retval = NS_ERROR;
} else {
strncpy(user, &filePattern[1], len);
user[len] = `\0';
found = Ns_GetUserHome(dsOut, user);
pathRemainder = nameEnd;
}
}
}
if (retval == NS_OK) {
if (!found) {
Ns_Log(Warning, "AliasedUrlToFile:TildeReplace(%s): Unknown user",
hServer);
retval = NS_ERROR;
} else {
Ns_DStringAppend(dsOut, pathRemainder);
if (uriTail != NULL) {
Ns_DStringAppend(dsOut, uriTail);
}
}
}
return retval;
}
static UriMap *
NewMap(char *from, char *to)
{
UriMap *map;
map = ns_malloc(sizeof(UriMap));
map->fromUri = from;
map->toUri = to;
return map;
}
static void
UriMapFree(UriMap * map)
{
ns_free(map->fromUri);
ns_free(map->toUri);
ns_free(map);
}