123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681 |
- /*
- route.c -- Route Management
- This module implements the loading of a route configuration file
- and the routing of requests.
- The route configuration is loaded form a text file that uses the schema (see route.txt)
- uri: type: uri: method: ability [ability...]: redirect
- user: name: password: role [role...]
- role: name: ability [ability...]
- Copyright (c) All Rights Reserved. See details at the end of the file.
- */
- /********************************* Includes ***********************************/
- #include "goahead.h"
- /*********************************** Locals ***********************************/
- static WebsRoute **routes = 0;
- static WebsHash handlers = -1;
- static int routeCount = 0;
- static int routeMax = 0;
- #define WEBS_MAX_ROUTE 16 /* Maximum passes over route set */
- /********************************** Forwards **********************************/
- static bool continueHandler(Webs *wp);
- static void freeRoute(WebsRoute *route);
- static void growRoutes();
- static int lookupRoute(char *uri);
- static bool redirectHandler(Webs *wp);
- /************************************ Code ************************************/
- /*
- Route the request. If wp->route is already set, test routes after that route
- */
- PUBLIC void websRouteRequest(Webs *wp)
- {
- WebsRoute *route;
- WebsHandler *handler;
- ssize plen, len;
- bool safeMethod;
- int i;
- assert(wp);
- assert(wp->path);
- assert(wp->method);
- assert(wp->protocol);
- safeMethod = smatch(wp->method, "POST") || smatch(wp->method, "GET") || smatch(wp->method, "HEAD");
- plen = slen(wp->path);
- /*
- Resume routine from last matched route. This permits the legacy service() callbacks to return false
- and continue routing.
- */
- if (wp->route && !(wp->flags & WEBS_REROUTE)) {
- for (i = 0; i < routeCount; i++) {
- if (wp->route == routes[i]) {
- i++;
- break;
- }
- }
- if (i >= routeCount) {
- i = 0;
- }
- } else {
- i = 0;
- }
- wp->route = 0;
- for (; i < routeCount; i++) {
- route = routes[i];
- assert(route->prefix && route->prefixLen > 0);
- if (plen < route->prefixLen) continue;
- len = min(route->prefixLen, plen);
- trace(5, "Examine route %s", route->prefix);
- /*
- Match route
- */
- if (strncmp(wp->path, route->prefix, len) != 0) {
- continue;
- }
- if (route->protocol && !smatch(route->protocol, wp->protocol)) {
- trace(5, "Route %s does not match protocol %s", route->prefix, wp->protocol);
- continue;
- }
- if (route->methods >= 0) {
- if (!hashLookup(route->methods, wp->method)) {
- trace(5, "Route %s does not match method %s", route->prefix, wp->method);
- continue;
- }
- } else if (!safeMethod) {
- continue;
- }
- if (route->extensions >= 0 && (wp->ext == 0 || !hashLookup(route->extensions, &wp->ext[1]))) {
- trace(5, "Route %s doesn match extension %s", route->prefix, wp->ext ? wp->ext : "");
- continue;
- }
- wp->route = route;
- #if ME_GOAHEAD_AUTH
- if (route->authType && !websAuthenticate(wp)) {
- return;
- }
- if (route->abilities >= 0 && !websCan(wp, route->abilities)) {
- return;
- }
- #endif
- if ((handler = route->handler) == 0) {
- continue;
- }
- if (!handler->match || (*handler->match)(wp)) {
- /* Handler matches */
- return;
- }
- wp->route = 0;
- if (wp->flags & WEBS_REROUTE) {
- wp->flags &= ~WEBS_REROUTE;
- if (++wp->routeCount >= WEBS_MAX_ROUTE) {
- break;
- }
- i = 0;
- }
- }
- if (wp->routeCount >= WEBS_MAX_ROUTE) {
- error("Route loop for %s", wp->url);
- }
- websError(wp, HTTP_CODE_NOT_FOUND, "Cannot find suitable route for request.");
- assert(wp->route == 0);
- }
- PUBLIC bool websRunRequest(Webs *wp)
- {
- WebsRoute *route;
- assert(wp);
- assert(wp->path);
- assert(wp->method);
- assert(wp->protocol);
- assert(wp->route);
- if ((route = wp->route) == 0) {
- websError(wp, HTTP_CODE_INTERNAL_SERVER_ERROR, "Configuration error - no route for request");
- return 1;
- }
- if (!route->handler) {
- websError(wp, HTTP_CODE_INTERNAL_SERVER_ERROR, "Configuration error - no handler for route");
- return 1;
- }
- if (!wp->filename || route->dir) {
- wfree(wp->filename);
- wp->filename = sfmt("%s%s", route->dir ? route->dir : websGetDocuments(), wp->path);
- }
- if (!(wp->flags & WEBS_VARS_ADDED)) {
- if (wp->query && *wp->query) {
- websSetQueryVars(wp);
- }
- if (wp->flags & WEBS_FORM) {
- websSetFormVars(wp);
- }
- wp->flags |= WEBS_VARS_ADDED;
- }
- wp->state = WEBS_RUNNING;
- trace(5, "Route %s calls handler %s", route->prefix, route->handler->name);
- #if ME_GOAHEAD_LEGACY
- if (route->handler->flags & WEBS_LEGACY_HANDLER) {
- return (*(WebsLegacyHandlerProc) route->handler->service)(wp, route->prefix, route->dir, route->flags) == 0;
- } else
- #endif
- if (!route->handler->service) {
- websError(wp, HTTP_CODE_INTERNAL_SERVER_ERROR, "Configuration error - no handler service callback");
- return 1;
- }
- return (*route->handler->service)(wp);
- }
- #if ME_GOAHEAD_AUTH
- static bool can(Webs *wp, char *ability)
- {
- assert(wp);
- assert(ability && *ability);
- if (wp->user && hashLookup(wp->user->abilities, ability)) {
- return 1;
- }
- return 0;
- }
- PUBLIC bool websCan(Webs *wp, WebsHash abilities)
- {
- WebsKey *key;
- char *ability, *cp, *start, abuf[ME_GOAHEAD_LIMIT_STRING];
- assert(wp);
- assert(abilities >= 0);
- if (!wp->user) {
- if (wp->authType) {
- if (!wp->username) {
- websError(wp, HTTP_CODE_UNAUTHORIZED, "Access Denied. User not logged in.");
- return 0;
- }
- if ((wp->user = websLookupUser(wp->username)) == 0) {
- websError(wp, HTTP_CODE_UNAUTHORIZED, "Access Denied. Unknown user.");
- return 0;
- }
- }
- }
- if (abilities >= 0) {
- if (!wp->user && wp->username) {
- wp->user = websLookupUser(wp->username);
- }
- assert(abilities);
- for (key = hashFirst(abilities); key; key = hashNext(abilities, key)) {
- ability = key->name.value.string;
- if ((cp = strchr(ability, '|')) != 0) {
- /*
- Examine a set of alternative abilities. Need only one to match
- */
- start = ability;
- do {
- sncopy(abuf, sizeof(abuf), start, cp - start);
- if (can(wp, abuf)) {
- break;
- }
- start = &cp[1];
- } while ((cp = strchr(start, '|')) != 0);
- if (!cp) {
- websError(wp, HTTP_CODE_UNAUTHORIZED, "Access Denied. Insufficient capabilities.");
- return 0;
- }
- } else if (!can(wp, ability)) {
- websError(wp, HTTP_CODE_UNAUTHORIZED, "Access Denied. Insufficient capabilities.");
- return 0;
- }
- }
- }
- return 1;
- }
- #endif
- #if KEEP
- PUBLIC bool websCanString(Webs *wp, char *abilities)
- {
- WebsUser *user;
- char *ability, *tok;
- if (!wp->user) {
- if (!wp->username) {
- return 0;
- }
- if ((user = websLookupUser(wp->username)) == 0) {
- trace(2, "Cannot find user %s", wp->username);
- return 0;
- }
- }
- abilities = sclone(abilities);
- for (ability = stok(abilities, " \t,", &tok); ability; ability = stok(NULL, " \t,", &tok)) {
- if (hashLookup(wp->user->abilities, ability) == 0) {
- wfree(abilities);
- return 0;
- }
- }
- wfree(abilities);
- return 1;
- }
- #endif
- /*
- If pos is < 0, then add to the end. Otherwise insert at specified position
- */
- WebsRoute *websAddRoute(char *uri, char *handler, int pos)
- {
- WebsRoute *route;
- WebsKey *key;
- if (uri == 0 || *uri == '\0') {
- error("Route has bad URI");
- return 0;
- }
- if ((route = walloc(sizeof(WebsRoute))) == 0) {
- return 0;
- }
- memset(route, 0, sizeof(WebsRoute));
- route->prefix = sclone(uri);
- route->prefixLen = slen(uri);
- route->abilities = route->extensions = route->methods = route->redirects = -1;
- if (!handler) {
- handler = "file";
- }
- if ((key = hashLookup(handlers, handler)) == 0) {
- error("Cannot find route handler %s", handler);
- wfree(route->prefix);
- wfree(route);
- return 0;
- }
- route->handler = key->content.value.symbol;
- #if ME_GOAHEAD_AUTH
- route->verify = websGetPasswordStoreVerify();
- #endif
- growRoutes();
- if (pos < 0) {
- pos = routeCount;
- }
- if (pos < routeCount) {
- memmove(&routes[pos + 1], &routes[pos], sizeof(WebsRoute*) * routeCount - pos);
- }
- routes[pos] = route;
- routeCount++;
- return route;
- }
- PUBLIC int websSetRouteMatch(WebsRoute *route, char *dir, char *protocol, WebsHash methods, WebsHash extensions,
- WebsHash abilities, WebsHash redirects)
- {
- assert(route);
- if (dir) {
- route->dir = sclone(dir);
- }
- route->protocol = protocol ? sclone(protocol) : 0;
- route->abilities = abilities;
- route->extensions = extensions;
- route->methods = methods;
- route->redirects = redirects;
- return 0;
- }
- static void growRoutes()
- {
- if (routeCount >= routeMax) {
- routeMax += 16;
- if ((routes = wrealloc(routes, sizeof(WebsRoute*) * routeMax)) == 0) {
- error("Cannot grow routes");
- }
- }
- }
- static int lookupRoute(char *uri)
- {
- WebsRoute *route;
- int i;
- assert(uri && *uri);
- for (i = 0; i < routeCount; i++) {
- route = routes[i];
- if (smatch(route->prefix, uri)) {
- return i;
- }
- }
- return -1;
- }
- static void freeRoute(WebsRoute *route)
- {
- assert(route);
- if (route->abilities >= 0) {
- hashFree(route->abilities);
- }
- if (route->extensions >= 0) {
- hashFree(route->extensions);
- }
- if (route->methods >= 0) {
- hashFree(route->methods);
- }
- if (route->redirects >= 0) {
- hashFree(route->redirects);
- }
- wfree(route->prefix);
- wfree(route->dir);
- wfree(route->protocol);
- wfree(route->authType);
- wfree(route);
- }
- PUBLIC int websRemoveRoute(char *uri)
- {
- int i;
- assert(uri && *uri);
- if ((i = lookupRoute(uri)) < 0) {
- return -1;
- }
- freeRoute(routes[i]);
- for (; i < routeCount; i++) {
- routes[i] = routes[i+1];
- }
- routeCount--;
- return 0;
- }
- PUBLIC int websOpenRoute()
- {
- if ((handlers = hashCreate(-1)) < 0) {
- return -1;
- }
- websDefineHandler("continue", continueHandler, 0, 0, 0);
- websDefineHandler("redirect", redirectHandler, 0, 0, 0);
- return 0;
- }
- PUBLIC void websCloseRoute()
- {
- WebsHandler *handler;
- WebsKey *key;
- int i;
- if (handlers >= 0) {
- for (key = hashFirst(handlers); key; key = hashNext(handlers, key)) {
- handler = key->content.value.symbol;
- if (handler->close) {
- (*handler->close)();
- }
- wfree(handler->name);
- wfree(handler);
- }
- hashFree(handlers);
- handlers = -1;
- }
- if (routes) {
- for (i = 0; i < routeCount; i++) {
- freeRoute(routes[i]);
- }
- wfree(routes);
- routes = 0;
- }
- routeCount = routeMax = 0;
- }
- PUBLIC int websDefineHandler(char *name, WebsHandlerProc match, WebsHandlerProc service, WebsHandlerClose close, int flags)
- {
- WebsHandler *handler;
- assert(name && *name);
- if ((handler = walloc(sizeof(WebsHandler))) == 0) {
- return -1;
- }
- memset(handler, 0, sizeof(WebsHandler));
- handler->name = sclone(name);
- handler->match = match;
- handler->service = service;
- handler->close = close;
- handler->flags = flags;
- hashEnter(handlers, name, valueSymbol(handler), 0);
- return 0;
- }
- static void addOption(WebsHash *hash, char *keys, char *value)
- {
- char *sep, *key, *tok;
- if (*hash < 0) {
- *hash = hashCreate(-1);
- }
- sep = " \t,|";
- for (key = stok(keys, sep, &tok); key; key = stok(0, sep, &tok)) {
- if (strcmp(key, "none") == 0) {
- continue;
- }
- if (value == 0) {
- hashEnter(*hash, key, valueInteger(0), 0);
- } else {
- hashEnter(*hash, key, valueString(value, VALUE_ALLOCATE), 0);
- }
- }
- }
- /*
- Load route and authentication configuration files
- */
- PUBLIC int websLoad(char *path)
- {
- WebsRoute *route;
- WebsHash abilities, extensions, methods, redirects;
- char *buf, *line, *kind, *next, *auth, *dir, *handler, *protocol, *uri, *option, *key, *value, *status;
- char *redirectUri, *token;
- int rc;
- assert(path && *path);
- rc = 0;
- if ((buf = websReadWholeFile(path)) == 0) {
- error("Cannot open config file %s", path);
- return -1;
- }
- for (line = stok(buf, "\r\n", &token); line; line = stok(NULL, "\r\n", &token)) {
- kind = stok(line, " \t", &next);
- if (kind == 0 || *kind == '\0' || *kind == '#') {
- continue;
- }
- if (smatch(kind, "route")) {
- auth = dir = handler = protocol = uri = 0;
- abilities = extensions = methods = redirects = -1;
- while ((option = stok(NULL, " \t\r\n", &next)) != 0) {
- key = stok(option, "=", &value);
- if (smatch(key, "abilities")) {
- addOption(&abilities, value, 0);
- } else if (smatch(key, "auth")) {
- auth = value;
- } else if (smatch(key, "dir")) {
- dir = value;
- } else if (smatch(key, "extensions")) {
- addOption(&extensions, value, 0);
- } else if (smatch(key, "handler")) {
- handler = value;
- } else if (smatch(key, "methods")) {
- addOption(&methods, value, 0);
- } else if (smatch(key, "redirect")) {
- if (strchr(value, '@')) {
- status = stok(value, "@", &redirectUri);
- if (smatch(status, "*")) {
- status = "0";
- }
- } else {
- status = "0";
- redirectUri = value;
- }
- if (smatch(redirectUri, "https")) redirectUri = "https://";
- if (smatch(redirectUri, "http")) redirectUri = "http://";
- addOption(&redirects, status, redirectUri);
- } else if (smatch(key, "protocol")) {
- protocol = value;
- } else if (smatch(key, "uri")) {
- uri = value;
- } else {
- error("Bad route keyword %s", key);
- continue;
- }
- }
- if ((route = websAddRoute(uri, handler, -1)) == 0) {
- rc = -1;
- break;
- }
- websSetRouteMatch(route, dir, protocol, methods, extensions, abilities, redirects);
- #if ME_GOAHEAD_AUTH
- if (auth && websSetRouteAuth(route, auth) < 0) {
- rc = -1;
- break;
- }
- } else if (smatch(kind, "user")) {
- char *name, *password, *roles;
- name = password = roles = 0;
- while ((option = stok(NULL, " \t\r\n", &next)) != 0) {
- key = stok(option, "=", &value);
- if (smatch(key, "name")) {
- name = value;
- } else if (smatch(key, "password")) {
- password = value;
- } else if (smatch(key, "roles")) {
- roles = value;
- } else {
- error("Bad user keyword %s", key);
- continue;
- }
- }
- if (websAddUser(name, password, roles) == 0) {
- rc = -1;
- break;
- }
- } else if (smatch(kind, "role")) {
- char *name;
- name = 0;
- abilities = -1;
- while ((option = stok(NULL, " \t\r\n", &next)) != 0) {
- key = stok(option, "=", &value);
- if (smatch(key, "name")) {
- name = value;
- } else if (smatch(key, "abilities")) {
- addOption(&abilities, value, 0);
- }
- }
- if (websAddRole(name, abilities) == 0) {
- rc = -1;
- break;
- }
- #endif
- } else {
- error("Unknown route keyword %s", kind);
- rc = -1;
- break;
- }
- }
- wfree(buf);
- #if ME_GOAHEAD_AUTH
- websComputeAllUserAbilities();
- #endif
- return rc;
- }
- /*
- Handler to just continue matching other routes
- */
- static bool continueHandler(Webs *wp)
- {
- return 0;
- }
- /*
- Handler to redirect to the default (code zero) URI
- */
- static bool redirectHandler(Webs *wp)
- {
- return websRedirectByStatus(wp, 0) == 0;
- }
- #if ME_GOAHEAD_LEGACY
- PUBLIC int websUrlHandlerDefine(char *prefix, char *dir, int arg, WebsLegacyHandlerProc handler, int flags)
- {
- WebsRoute *route;
- static int legacyCount = 0;
- char name[ME_GOAHEAD_LIMIT_STRING];
- assert(prefix && *prefix);
- assert(handler);
- fmt(name, sizeof(name), "%s-%d", prefix, legacyCount);
- if (websDefineHandler(name, 0, (WebsHandlerProc) handler, 0, WEBS_LEGACY_HANDLER) < 0) {
- return -1;
- }
- if ((route = websAddRoute(prefix, name, 0)) == 0) {
- return -1;
- }
- if (dir) {
- route->dir = sclone(dir);
- }
- return 0;
- }
- PUBLIC int websPublish(char *prefix, char *dir)
- {
- WebsRoute *route;
- if ((route = websAddRoute(prefix, 0, 0)) == 0) {
- return -1;
- }
- route->dir = sclone(dir);
- return 0;
- }
- #endif
- /*
- Copyright (c) Embedthis Software. All Rights Reserved.
- This software is distributed under commercial and open source licenses.
- You may use the Embedthis GoAhead open source license or you may acquire
- a commercial license from Embedthis Software. You agree to be fully bound
- by the terms of either license. Consult the LICENSE.md distributed with
- this software for full details and other copyrights.
- */
|