route.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. /*
  2. route.c -- Route Management
  3. This module implements the loading of a route configuration file
  4. and the routing of requests.
  5. The route configuration is loaded form a text file that uses the schema (see route.txt)
  6. uri: type: uri: method: ability [ability...]: redirect
  7. user: name: password: role [role...]
  8. role: name: ability [ability...]
  9. Copyright (c) All Rights Reserved. See details at the end of the file.
  10. */
  11. /********************************* Includes ***********************************/
  12. #include "goahead.h"
  13. /*********************************** Locals ***********************************/
  14. static WebsRoute **routes = 0;
  15. static WebsHash handlers = -1;
  16. static int routeCount = 0;
  17. static int routeMax = 0;
  18. #define WEBS_MAX_ROUTE 16 /* Maximum passes over route set */
  19. /********************************** Forwards **********************************/
  20. static bool continueHandler(Webs *wp);
  21. static void freeRoute(WebsRoute *route);
  22. static void growRoutes();
  23. static int lookupRoute(char *uri);
  24. static bool redirectHandler(Webs *wp);
  25. /************************************ Code ************************************/
  26. /*
  27. Route the request. If wp->route is already set, test routes after that route
  28. */
  29. PUBLIC void websRouteRequest(Webs *wp)
  30. {
  31. WebsRoute *route;
  32. WebsHandler *handler;
  33. ssize plen, len;
  34. bool safeMethod;
  35. int i;
  36. assert(wp);
  37. assert(wp->path);
  38. assert(wp->method);
  39. assert(wp->protocol);
  40. safeMethod = smatch(wp->method, "POST") || smatch(wp->method, "GET") || smatch(wp->method, "HEAD");
  41. plen = slen(wp->path);
  42. /*
  43. Resume routine from last matched route. This permits the legacy service() callbacks to return false
  44. and continue routing.
  45. */
  46. if (wp->route && !(wp->flags & WEBS_REROUTE)) {
  47. for (i = 0; i < routeCount; i++) {
  48. if (wp->route == routes[i]) {
  49. i++;
  50. break;
  51. }
  52. }
  53. if (i >= routeCount) {
  54. i = 0;
  55. }
  56. } else {
  57. i = 0;
  58. }
  59. wp->route = 0;
  60. for (; i < routeCount; i++) {
  61. route = routes[i];
  62. assert(route->prefix && route->prefixLen > 0);
  63. if (plen < route->prefixLen) continue;
  64. len = min(route->prefixLen, plen);
  65. trace(5, "Examine route %s", route->prefix);
  66. /*
  67. Match route
  68. */
  69. if (strncmp(wp->path, route->prefix, len) != 0) {
  70. continue;
  71. }
  72. if (route->protocol && !smatch(route->protocol, wp->protocol)) {
  73. trace(5, "Route %s does not match protocol %s", route->prefix, wp->protocol);
  74. continue;
  75. }
  76. if (route->methods >= 0) {
  77. if (!hashLookup(route->methods, wp->method)) {
  78. trace(5, "Route %s does not match method %s", route->prefix, wp->method);
  79. continue;
  80. }
  81. } else if (!safeMethod) {
  82. continue;
  83. }
  84. if (route->extensions >= 0 && (wp->ext == 0 || !hashLookup(route->extensions, &wp->ext[1]))) {
  85. trace(5, "Route %s doesn match extension %s", route->prefix, wp->ext ? wp->ext : "");
  86. continue;
  87. }
  88. wp->route = route;
  89. #if ME_GOAHEAD_AUTH
  90. if (route->authType && !websAuthenticate(wp)) {
  91. return;
  92. }
  93. if (route->abilities >= 0 && !websCan(wp, route->abilities)) {
  94. return;
  95. }
  96. #endif
  97. if ((handler = route->handler) == 0) {
  98. continue;
  99. }
  100. if (!handler->match || (*handler->match)(wp)) {
  101. /* Handler matches */
  102. return;
  103. }
  104. wp->route = 0;
  105. if (wp->flags & WEBS_REROUTE) {
  106. wp->flags &= ~WEBS_REROUTE;
  107. if (++wp->routeCount >= WEBS_MAX_ROUTE) {
  108. break;
  109. }
  110. i = 0;
  111. }
  112. }
  113. if (wp->routeCount >= WEBS_MAX_ROUTE) {
  114. error("Route loop for %s", wp->url);
  115. }
  116. websError(wp, HTTP_CODE_NOT_FOUND, "Cannot find suitable route for request.");
  117. assert(wp->route == 0);
  118. }
  119. PUBLIC bool websRunRequest(Webs *wp)
  120. {
  121. WebsRoute *route;
  122. assert(wp);
  123. assert(wp->path);
  124. assert(wp->method);
  125. assert(wp->protocol);
  126. assert(wp->route);
  127. if ((route = wp->route) == 0) {
  128. websError(wp, HTTP_CODE_INTERNAL_SERVER_ERROR, "Configuration error - no route for request");
  129. return 1;
  130. }
  131. if (!route->handler) {
  132. websError(wp, HTTP_CODE_INTERNAL_SERVER_ERROR, "Configuration error - no handler for route");
  133. return 1;
  134. }
  135. if (!wp->filename || route->dir) {
  136. wfree(wp->filename);
  137. wp->filename = sfmt("%s%s", route->dir ? route->dir : websGetDocuments(), wp->path);
  138. }
  139. if (!(wp->flags & WEBS_VARS_ADDED)) {
  140. if (wp->query && *wp->query) {
  141. websSetQueryVars(wp);
  142. }
  143. if (wp->flags & WEBS_FORM) {
  144. websSetFormVars(wp);
  145. }
  146. wp->flags |= WEBS_VARS_ADDED;
  147. }
  148. wp->state = WEBS_RUNNING;
  149. trace(5, "Route %s calls handler %s", route->prefix, route->handler->name);
  150. #if ME_GOAHEAD_LEGACY
  151. if (route->handler->flags & WEBS_LEGACY_HANDLER) {
  152. return (*(WebsLegacyHandlerProc) route->handler->service)(wp, route->prefix, route->dir, route->flags) == 0;
  153. } else
  154. #endif
  155. if (!route->handler->service) {
  156. websError(wp, HTTP_CODE_INTERNAL_SERVER_ERROR, "Configuration error - no handler service callback");
  157. return 1;
  158. }
  159. return (*route->handler->service)(wp);
  160. }
  161. #if ME_GOAHEAD_AUTH
  162. static bool can(Webs *wp, char *ability)
  163. {
  164. assert(wp);
  165. assert(ability && *ability);
  166. if (wp->user && hashLookup(wp->user->abilities, ability)) {
  167. return 1;
  168. }
  169. return 0;
  170. }
  171. PUBLIC bool websCan(Webs *wp, WebsHash abilities)
  172. {
  173. WebsKey *key;
  174. char *ability, *cp, *start, abuf[ME_GOAHEAD_LIMIT_STRING];
  175. assert(wp);
  176. assert(abilities >= 0);
  177. if (!wp->user) {
  178. if (wp->authType) {
  179. if (!wp->username) {
  180. websError(wp, HTTP_CODE_UNAUTHORIZED, "Access Denied. User not logged in.");
  181. return 0;
  182. }
  183. if ((wp->user = websLookupUser(wp->username)) == 0) {
  184. websError(wp, HTTP_CODE_UNAUTHORIZED, "Access Denied. Unknown user.");
  185. return 0;
  186. }
  187. }
  188. }
  189. if (abilities >= 0) {
  190. if (!wp->user && wp->username) {
  191. wp->user = websLookupUser(wp->username);
  192. }
  193. assert(abilities);
  194. for (key = hashFirst(abilities); key; key = hashNext(abilities, key)) {
  195. ability = key->name.value.string;
  196. if ((cp = strchr(ability, '|')) != 0) {
  197. /*
  198. Examine a set of alternative abilities. Need only one to match
  199. */
  200. start = ability;
  201. do {
  202. sncopy(abuf, sizeof(abuf), start, cp - start);
  203. if (can(wp, abuf)) {
  204. break;
  205. }
  206. start = &cp[1];
  207. } while ((cp = strchr(start, '|')) != 0);
  208. if (!cp) {
  209. websError(wp, HTTP_CODE_UNAUTHORIZED, "Access Denied. Insufficient capabilities.");
  210. return 0;
  211. }
  212. } else if (!can(wp, ability)) {
  213. websError(wp, HTTP_CODE_UNAUTHORIZED, "Access Denied. Insufficient capabilities.");
  214. return 0;
  215. }
  216. }
  217. }
  218. return 1;
  219. }
  220. #endif
  221. #if KEEP
  222. PUBLIC bool websCanString(Webs *wp, char *abilities)
  223. {
  224. WebsUser *user;
  225. char *ability, *tok;
  226. if (!wp->user) {
  227. if (!wp->username) {
  228. return 0;
  229. }
  230. if ((user = websLookupUser(wp->username)) == 0) {
  231. trace(2, "Cannot find user %s", wp->username);
  232. return 0;
  233. }
  234. }
  235. abilities = sclone(abilities);
  236. for (ability = stok(abilities, " \t,", &tok); ability; ability = stok(NULL, " \t,", &tok)) {
  237. if (hashLookup(wp->user->abilities, ability) == 0) {
  238. wfree(abilities);
  239. return 0;
  240. }
  241. }
  242. wfree(abilities);
  243. return 1;
  244. }
  245. #endif
  246. /*
  247. If pos is < 0, then add to the end. Otherwise insert at specified position
  248. */
  249. WebsRoute *websAddRoute(char *uri, char *handler, int pos)
  250. {
  251. WebsRoute *route;
  252. WebsKey *key;
  253. if (uri == 0 || *uri == '\0') {
  254. error("Route has bad URI");
  255. return 0;
  256. }
  257. if ((route = walloc(sizeof(WebsRoute))) == 0) {
  258. return 0;
  259. }
  260. memset(route, 0, sizeof(WebsRoute));
  261. route->prefix = sclone(uri);
  262. route->prefixLen = slen(uri);
  263. route->abilities = route->extensions = route->methods = route->redirects = -1;
  264. if (!handler) {
  265. handler = "file";
  266. }
  267. if ((key = hashLookup(handlers, handler)) == 0) {
  268. error("Cannot find route handler %s", handler);
  269. wfree(route->prefix);
  270. wfree(route);
  271. return 0;
  272. }
  273. route->handler = key->content.value.symbol;
  274. #if ME_GOAHEAD_AUTH
  275. route->verify = websGetPasswordStoreVerify();
  276. #endif
  277. growRoutes();
  278. if (pos < 0) {
  279. pos = routeCount;
  280. }
  281. if (pos < routeCount) {
  282. memmove(&routes[pos + 1], &routes[pos], sizeof(WebsRoute*) * routeCount - pos);
  283. }
  284. routes[pos] = route;
  285. routeCount++;
  286. return route;
  287. }
  288. PUBLIC int websSetRouteMatch(WebsRoute *route, char *dir, char *protocol, WebsHash methods, WebsHash extensions,
  289. WebsHash abilities, WebsHash redirects)
  290. {
  291. assert(route);
  292. if (dir) {
  293. route->dir = sclone(dir);
  294. }
  295. route->protocol = protocol ? sclone(protocol) : 0;
  296. route->abilities = abilities;
  297. route->extensions = extensions;
  298. route->methods = methods;
  299. route->redirects = redirects;
  300. return 0;
  301. }
  302. static void growRoutes()
  303. {
  304. if (routeCount >= routeMax) {
  305. routeMax += 16;
  306. if ((routes = wrealloc(routes, sizeof(WebsRoute*) * routeMax)) == 0) {
  307. error("Cannot grow routes");
  308. }
  309. }
  310. }
  311. static int lookupRoute(char *uri)
  312. {
  313. WebsRoute *route;
  314. int i;
  315. assert(uri && *uri);
  316. for (i = 0; i < routeCount; i++) {
  317. route = routes[i];
  318. if (smatch(route->prefix, uri)) {
  319. return i;
  320. }
  321. }
  322. return -1;
  323. }
  324. static void freeRoute(WebsRoute *route)
  325. {
  326. assert(route);
  327. if (route->abilities >= 0) {
  328. hashFree(route->abilities);
  329. }
  330. if (route->extensions >= 0) {
  331. hashFree(route->extensions);
  332. }
  333. if (route->methods >= 0) {
  334. hashFree(route->methods);
  335. }
  336. if (route->redirects >= 0) {
  337. hashFree(route->redirects);
  338. }
  339. wfree(route->prefix);
  340. wfree(route->dir);
  341. wfree(route->protocol);
  342. wfree(route->authType);
  343. wfree(route);
  344. }
  345. PUBLIC int websRemoveRoute(char *uri)
  346. {
  347. int i;
  348. assert(uri && *uri);
  349. if ((i = lookupRoute(uri)) < 0) {
  350. return -1;
  351. }
  352. freeRoute(routes[i]);
  353. for (; i < routeCount; i++) {
  354. routes[i] = routes[i+1];
  355. }
  356. routeCount--;
  357. return 0;
  358. }
  359. PUBLIC int websOpenRoute()
  360. {
  361. if ((handlers = hashCreate(-1)) < 0) {
  362. return -1;
  363. }
  364. websDefineHandler("continue", continueHandler, 0, 0, 0);
  365. websDefineHandler("redirect", redirectHandler, 0, 0, 0);
  366. return 0;
  367. }
  368. PUBLIC void websCloseRoute()
  369. {
  370. WebsHandler *handler;
  371. WebsKey *key;
  372. int i;
  373. if (handlers >= 0) {
  374. for (key = hashFirst(handlers); key; key = hashNext(handlers, key)) {
  375. handler = key->content.value.symbol;
  376. if (handler->close) {
  377. (*handler->close)();
  378. }
  379. wfree(handler->name);
  380. wfree(handler);
  381. }
  382. hashFree(handlers);
  383. handlers = -1;
  384. }
  385. if (routes) {
  386. for (i = 0; i < routeCount; i++) {
  387. freeRoute(routes[i]);
  388. }
  389. wfree(routes);
  390. routes = 0;
  391. }
  392. routeCount = routeMax = 0;
  393. }
  394. PUBLIC int websDefineHandler(char *name, WebsHandlerProc match, WebsHandlerProc service, WebsHandlerClose close, int flags)
  395. {
  396. WebsHandler *handler;
  397. assert(name && *name);
  398. if ((handler = walloc(sizeof(WebsHandler))) == 0) {
  399. return -1;
  400. }
  401. memset(handler, 0, sizeof(WebsHandler));
  402. handler->name = sclone(name);
  403. handler->match = match;
  404. handler->service = service;
  405. handler->close = close;
  406. handler->flags = flags;
  407. hashEnter(handlers, name, valueSymbol(handler), 0);
  408. return 0;
  409. }
  410. static void addOption(WebsHash *hash, char *keys, char *value)
  411. {
  412. char *sep, *key, *tok;
  413. if (*hash < 0) {
  414. *hash = hashCreate(-1);
  415. }
  416. sep = " \t,|";
  417. for (key = stok(keys, sep, &tok); key; key = stok(0, sep, &tok)) {
  418. if (strcmp(key, "none") == 0) {
  419. continue;
  420. }
  421. if (value == 0) {
  422. hashEnter(*hash, key, valueInteger(0), 0);
  423. } else {
  424. hashEnter(*hash, key, valueString(value, VALUE_ALLOCATE), 0);
  425. }
  426. }
  427. }
  428. /*
  429. Load route and authentication configuration files
  430. */
  431. PUBLIC int websLoad(char *path)
  432. {
  433. WebsRoute *route;
  434. WebsHash abilities, extensions, methods, redirects;
  435. char *buf, *line, *kind, *next, *auth, *dir, *handler, *protocol, *uri, *option, *key, *value, *status;
  436. char *redirectUri, *token;
  437. int rc;
  438. assert(path && *path);
  439. rc = 0;
  440. if ((buf = websReadWholeFile(path)) == 0) {
  441. error("Cannot open config file %s", path);
  442. return -1;
  443. }
  444. for (line = stok(buf, "\r\n", &token); line; line = stok(NULL, "\r\n", &token)) {
  445. kind = stok(line, " \t", &next);
  446. if (kind == 0 || *kind == '\0' || *kind == '#') {
  447. continue;
  448. }
  449. if (smatch(kind, "route")) {
  450. auth = dir = handler = protocol = uri = 0;
  451. abilities = extensions = methods = redirects = -1;
  452. while ((option = stok(NULL, " \t\r\n", &next)) != 0) {
  453. key = stok(option, "=", &value);
  454. if (smatch(key, "abilities")) {
  455. addOption(&abilities, value, 0);
  456. } else if (smatch(key, "auth")) {
  457. auth = value;
  458. } else if (smatch(key, "dir")) {
  459. dir = value;
  460. } else if (smatch(key, "extensions")) {
  461. addOption(&extensions, value, 0);
  462. } else if (smatch(key, "handler")) {
  463. handler = value;
  464. } else if (smatch(key, "methods")) {
  465. addOption(&methods, value, 0);
  466. } else if (smatch(key, "redirect")) {
  467. if (strchr(value, '@')) {
  468. status = stok(value, "@", &redirectUri);
  469. if (smatch(status, "*")) {
  470. status = "0";
  471. }
  472. } else {
  473. status = "0";
  474. redirectUri = value;
  475. }
  476. if (smatch(redirectUri, "https")) redirectUri = "https://";
  477. if (smatch(redirectUri, "http")) redirectUri = "http://";
  478. addOption(&redirects, status, redirectUri);
  479. } else if (smatch(key, "protocol")) {
  480. protocol = value;
  481. } else if (smatch(key, "uri")) {
  482. uri = value;
  483. } else {
  484. error("Bad route keyword %s", key);
  485. continue;
  486. }
  487. }
  488. if ((route = websAddRoute(uri, handler, -1)) == 0) {
  489. rc = -1;
  490. break;
  491. }
  492. websSetRouteMatch(route, dir, protocol, methods, extensions, abilities, redirects);
  493. #if ME_GOAHEAD_AUTH
  494. if (auth && websSetRouteAuth(route, auth) < 0) {
  495. rc = -1;
  496. break;
  497. }
  498. } else if (smatch(kind, "user")) {
  499. char *name, *password, *roles;
  500. name = password = roles = 0;
  501. while ((option = stok(NULL, " \t\r\n", &next)) != 0) {
  502. key = stok(option, "=", &value);
  503. if (smatch(key, "name")) {
  504. name = value;
  505. } else if (smatch(key, "password")) {
  506. password = value;
  507. } else if (smatch(key, "roles")) {
  508. roles = value;
  509. } else {
  510. error("Bad user keyword %s", key);
  511. continue;
  512. }
  513. }
  514. if (websAddUser(name, password, roles) == 0) {
  515. rc = -1;
  516. break;
  517. }
  518. } else if (smatch(kind, "role")) {
  519. char *name;
  520. name = 0;
  521. abilities = -1;
  522. while ((option = stok(NULL, " \t\r\n", &next)) != 0) {
  523. key = stok(option, "=", &value);
  524. if (smatch(key, "name")) {
  525. name = value;
  526. } else if (smatch(key, "abilities")) {
  527. addOption(&abilities, value, 0);
  528. }
  529. }
  530. if (websAddRole(name, abilities) == 0) {
  531. rc = -1;
  532. break;
  533. }
  534. #endif
  535. } else {
  536. error("Unknown route keyword %s", kind);
  537. rc = -1;
  538. break;
  539. }
  540. }
  541. wfree(buf);
  542. #if ME_GOAHEAD_AUTH
  543. websComputeAllUserAbilities();
  544. #endif
  545. return rc;
  546. }
  547. /*
  548. Handler to just continue matching other routes
  549. */
  550. static bool continueHandler(Webs *wp)
  551. {
  552. return 0;
  553. }
  554. /*
  555. Handler to redirect to the default (code zero) URI
  556. */
  557. static bool redirectHandler(Webs *wp)
  558. {
  559. return websRedirectByStatus(wp, 0) == 0;
  560. }
  561. #if ME_GOAHEAD_LEGACY
  562. PUBLIC int websUrlHandlerDefine(char *prefix, char *dir, int arg, WebsLegacyHandlerProc handler, int flags)
  563. {
  564. WebsRoute *route;
  565. static int legacyCount = 0;
  566. char name[ME_GOAHEAD_LIMIT_STRING];
  567. assert(prefix && *prefix);
  568. assert(handler);
  569. fmt(name, sizeof(name), "%s-%d", prefix, legacyCount);
  570. if (websDefineHandler(name, 0, (WebsHandlerProc) handler, 0, WEBS_LEGACY_HANDLER) < 0) {
  571. return -1;
  572. }
  573. if ((route = websAddRoute(prefix, name, 0)) == 0) {
  574. return -1;
  575. }
  576. if (dir) {
  577. route->dir = sclone(dir);
  578. }
  579. return 0;
  580. }
  581. PUBLIC int websPublish(char *prefix, char *dir)
  582. {
  583. WebsRoute *route;
  584. if ((route = websAddRoute(prefix, 0, 0)) == 0) {
  585. return -1;
  586. }
  587. route->dir = sclone(dir);
  588. return 0;
  589. }
  590. #endif
  591. /*
  592. Copyright (c) Embedthis Software. All Rights Reserved.
  593. This software is distributed under commercial and open source licenses.
  594. You may use the Embedthis GoAhead open source license or you may acquire
  595. a commercial license from Embedthis Software. You agree to be fully bound
  596. by the terms of either license. Consult the LICENSE.md distributed with
  597. this software for full details and other copyrights.
  598. */