ssi.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. /*
  2. * Copyright (C) 2001-2004 by Ole Reinhardt <ole.reinhardt@embedded-it.de>.
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions
  7. * are met:
  8. *
  9. * 1. Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * 2. Redistributions in binary form must reproduce the above copyright
  12. * notice, this list of conditions and the following disclaimer in the
  13. * documentation and/or other materials provided with the distribution.
  14. * 3. Neither the name of the copyright holders nor the names of
  15. * contributors may be used to endorse or promote products derived
  16. * from this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  21. * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  22. * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  23. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  24. * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  25. * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
  26. * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  27. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
  28. * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  29. * SUCH DAMAGE.
  30. *
  31. * For additional information see http://www.ethernut.de/
  32. */
  33. /*
  34. * $Log$
  35. * Revision 1.14 2009/02/13 14:43:11 haraldkipp
  36. * Memory overflow bug fixed.
  37. *
  38. * Revision 1.13 2009/02/06 15:40:29 haraldkipp
  39. * Using newly available strdup() and calloc().
  40. * Replaced NutHeap routines by standard malloc/free.
  41. * Replaced pointer value 0 by NULL.
  42. *
  43. * Revision 1.12 2008/08/22 09:23:57 haraldkipp
  44. * GCC specific implementation removed.
  45. *
  46. * Revision 1.11 2008/08/18 11:00:39 olereinhardt
  47. * Fixed a memory leak. A filename was never freed again
  48. *
  49. * Revision 1.10 2008/08/11 07:00:36 haraldkipp
  50. * BSD types replaced by stdint types (feature request #1282721).
  51. *
  52. * Revision 1.9 2008/07/26 14:09:29 haraldkipp
  53. * Fixed another problem with ICCAVR.
  54. *
  55. * Revision 1.8 2008/07/25 12:17:26 olereinhardt
  56. * Imagecraft compilers does not support alloca (to allocate memory from the
  57. * stack). So we use NutHeapAlloc / NutHeapClear instead for Imagecraft.
  58. *
  59. * Revision 1.7 2008/07/17 11:36:34 olereinhardt
  60. * - Moved some functions used in httpd.c as well as in ssi.c into httpd_p.c
  61. * - Implemeted $QUERY_STRING parameter in for CGIs included by a ssi file
  62. *
  63. * Revision 1.6 2008/05/13 21:24:55 thiagocorrea
  64. * Fix a few documentation typos.
  65. *
  66. * Revision 1.5 2008/04/21 22:16:25 olereinhardt
  67. * 2008-04-21 Ole Reinhardt <ole.reinhardt@embedded-it.de>
  68. * * pro/ssi.c: Nicer Implement query string feature and save some
  69. * memory
  70. *
  71. * Revision 1.4 2008/04/18 13:22:57 haraldkipp
  72. * Fixed ICCAVR compile errors. No chance to implement GCC's PSTR().
  73. *
  74. * Revision 1.3 2008/02/15 17:09:03 haraldkipp
  75. * Quick hack provided by Niels. No idea what this is for, but
  76. * according to the author it is a dirty solution. We urgently
  77. * need it to get the Elektor Radio working. Sorry in advance
  78. * for any trouble this change may cause.
  79. *
  80. * Revision 1.2 2006/03/16 15:25:39 haraldkipp
  81. * Changed human readable strings from u_char to char to stop GCC 4 from
  82. * nagging about signedness.
  83. *
  84. * Revision 1.1 2005/08/05 11:22:14 olereinhardt
  85. * Added Server side include support. Initial checkin
  86. *
  87. */
  88. /*!
  89. * \addtogroup xgHTTPD
  90. */
  91. /*@{*/
  92. #include <cfg/arch.h>
  93. #include <sys/types.h>
  94. #include <memdebug.h>
  95. #include <string.h>
  96. #include <stdlib.h>
  97. #include <io.h>
  98. #include <ctype.h>
  99. #include <stdio.h>
  100. #include <unistd.h>
  101. #include <fcntl.h>
  102. #include <sys/heap.h>
  103. #include <sys/version.h>
  104. #include <pro/httpd.h>
  105. #include <pro/ssi.h>
  106. #include "httpd_p.h"
  107. #include "dencode.h"
  108. #define BUFSIZE 512
  109. #define MIN(a,b) (a<b?a:b)
  110. #define SSI_TYPE_FILE 0x01
  111. #define SSI_TYPE_VIRTUAL 0x02
  112. #define SSI_TYPE_EXEC 0x03
  113. #define SSI_TYPE_ECHO 0x04
  114. static char * (*ssivar_handler)(char *, REQUEST *);
  115. static const char rsp_not_found_P[] PROGMEM = "404 Not found: %s\r\n";
  116. static const char rsp_intern_err_P[] PROGMEM = "500 Internal error\r\n";
  117. static const char rsp_bad_req_P[] PROGMEM = "400 Bad request\r\n";
  118. extern char *cgiBinPath;
  119. /*!
  120. * \brief Send included file to the stream
  121. *
  122. * Load a file from filesystem and send ("include") it to the http-stream
  123. *
  124. * \param stream Stream of the socket connection, previously opened for
  125. * binary read and write.
  126. * \param filename Name of the included file. e.g."UROM:test.txt"
  127. */
  128. static void NutSsiProcessFile(FILE * stream, char *filename)
  129. {
  130. int fd;
  131. int n;
  132. char *data;
  133. int size;
  134. long file_len;
  135. fd = _open(filename, _O_BINARY | _O_RDONLY);
  136. if (fd == -1) { // No such file found... send a 404 string.
  137. fprintf_P(stream, rsp_not_found_P, filename);
  138. return;
  139. }
  140. file_len = _filelength(fd);
  141. size = 512;
  142. if ((data = malloc(size)) != NULL) {
  143. while (file_len) {
  144. if (file_len < 512L)
  145. size = (int) file_len;
  146. n = _read(fd, data, size);
  147. if (fwrite(data, 1, n, stream) == 0)
  148. break;
  149. file_len -= (long) n;
  150. }
  151. free(data);
  152. }
  153. _close(fd);
  154. }
  155. /*!
  156. * \brief Send a file or cgi with a path relativ to http-root
  157. *
  158. * Processes an included local url with a path relativ to http-root. This could also be a
  159. * cgi script. Nearly the same as NutHttpProcessFileRequest
  160. *
  161. * \param stream Stream of the socket connection, previously opened for
  162. * binary read and write.
  163. * \param url URL of the file to be included e.g. "/include/test.js"
  164. * \param http_root The root path of the http-deamon
  165. * \param orig_req The http request struct of the top most http_request
  166. */
  167. static void NutSsiProcessVirtual(FILE * stream, char *url, char* http_root, REQUEST *orig_req)
  168. {
  169. int fd;
  170. int i;
  171. int n;
  172. char *data;
  173. int size;
  174. long file_len;
  175. char *filename = NULL;
  176. void (*handler)(FILE *stream, int fd, int file_len, char *http_root, REQUEST *req);
  177. char *cp;
  178. REQUEST * req;
  179. const char *cgi_bin = cgiBinPath ? cgiBinPath : "cgi-bin/";
  180. const char * tmp;
  181. size_t len;
  182. tmp = cgi_bin;
  183. if (NutDecodePath(url) == 0) {
  184. fprintf_P(stream, rsp_bad_req_P);
  185. return;
  186. }
  187. /*
  188. * Process CGI.
  189. */
  190. while (tmp) {
  191. /* Skip leading path separators. */
  192. while (*cgi_bin == ';')
  193. cgi_bin++;
  194. /* Determine the length of the next path component. */
  195. for (len = 0, cp = (char *)cgi_bin; *cp && *cp != ';'; len++, cp++);
  196. tmp = cgi_bin;
  197. if (len && strncasecmp(url, tmp, len) == 0) {
  198. if ((req = calloc(1, sizeof(REQUEST))) == 0) {
  199. fprintf_P(stream, rsp_intern_err_P);
  200. return;
  201. }
  202. req->req_method = METHOD_GET;
  203. req->req_version = orig_req->req_version;
  204. req->req_length = 0;
  205. if (orig_req->req_agent != NULL) {
  206. if ((req->req_agent = strdup(orig_req->req_agent)) == NULL) {
  207. fprintf_P(stream, rsp_intern_err_P);
  208. DestroyRequestInfo(req);
  209. return;
  210. }
  211. }
  212. if (orig_req->req_cookie!= NULL) {
  213. if ((req->req_cookie = strdup(orig_req->req_cookie)) == NULL) {
  214. fprintf_P(stream, rsp_intern_err_P);
  215. DestroyRequestInfo(req);
  216. return;
  217. }
  218. }
  219. if ((cp = strchr(url, '?')) != 0) {
  220. *cp++ = 0;
  221. if (strcmp(cp, "$QUERY_STRING") == 0) {
  222. uint16_t size;
  223. size = 1; /* At least 1 for empty requests. */
  224. for (i = 0; i < orig_req->req_numqptrs*2; i ++) {
  225. size += strlen(orig_req->req_qptrs[i]) + 1;
  226. }
  227. if ((req->req_query = malloc(size)) == NULL) {
  228. fprintf_P(stream, rsp_intern_err_P);
  229. DestroyRequestInfo(req);
  230. return;
  231. }
  232. req->req_query[0] = 0;
  233. for (i = 0; i < (orig_req->req_numqptrs * 2); i++) {
  234. if(i) {
  235. strcat (req->req_query, "&");
  236. }
  237. strcat (req->req_query, orig_req->req_qptrs[i]);
  238. strcat (req->req_query, "=");
  239. i++;
  240. strcat (req->req_query, orig_req->req_qptrs[i]);
  241. }
  242. } else {
  243. if ((req->req_query = strdup(cp)) == NULL) {
  244. fprintf_P(stream, rsp_intern_err_P);
  245. DestroyRequestInfo(req);
  246. return;
  247. }
  248. }
  249. NutHttpProcessQueryString(req);
  250. }
  251. if ((req->req_url = strdup(url)) == NULL) {
  252. fprintf_P(stream, rsp_intern_err_P);
  253. DestroyRequestInfo(req);
  254. return;
  255. }
  256. NutCgiProcessRequest(stream, req, len);
  257. DestroyRequestInfo(req);
  258. return;
  259. }
  260. cgi_bin += len;
  261. if (*cgi_bin == '\0') {
  262. break;
  263. }
  264. }
  265. /*
  266. * Process file.
  267. */
  268. for (n = 0, fd = -1; default_files[n]; n++) {
  269. filename = CreateFilePath(url, default_files[n]);
  270. if (filename == NULL) {
  271. fprintf_P(stream, rsp_intern_err_P);
  272. return;
  273. }
  274. /*
  275. * Note, that simple file systems may not provide stat() or access(),
  276. * thus trying to open the file is the only way to check for existence.
  277. * Another problem is, that PHAT allows to open directories. We use
  278. * the file length to ensure, that we got a normal file.
  279. */
  280. if ((fd = _open(filename, _O_BINARY | _O_RDONLY)) != -1) {
  281. if (_filelength(fd)) {
  282. break;
  283. }
  284. _close(fd);
  285. }
  286. free(filename);
  287. }
  288. if (fd == -1) {
  289. fprintf_P(stream, rsp_not_found_P, url);
  290. return;
  291. }
  292. file_len = _filelength(fd);
  293. handler = NutGetMimeHandler(filename);
  294. free(filename);
  295. if (handler == NULL) {
  296. size = 512; // If we have not registered a mime handler handle default.
  297. if ((data = malloc(size)) != NULL) {
  298. while (file_len) {
  299. if (file_len < 512L) {
  300. size = (int) file_len;
  301. }
  302. n = _read(fd, data, size);
  303. if (fwrite(data, 1, n, stream) == 0) {
  304. break;
  305. }
  306. file_len -= (long) n;
  307. }
  308. free(data);
  309. }
  310. } else handler(stream, fd, file_len, http_root, orig_req);
  311. _close(fd);
  312. return;
  313. }
  314. /*!
  315. * \brief Send variable content to the stream.
  316. *
  317. * \param stream Stream of the socket connection, previously opened for
  318. * binary read and write.
  319. * \param varname Name of the variable.
  320. */
  321. static void NutSsiProcessEcho(FILE * stream, char *name, REQUEST *req)
  322. {
  323. if (ssivar_handler) {
  324. char *value = (*ssivar_handler)(name, req);
  325. if (value) {
  326. fputs(value, stream);
  327. }
  328. }
  329. }
  330. /*!
  331. * \brief Skip whitespaces in a string from the given position on
  332. *
  333. * Whitespaces are defined as \n \r \t and space. Pos will be set to the first character which is not a whitespace
  334. *
  335. * \param buffer String to work on
  336. * \param pos Start position of the search
  337. * \param end last position we should check for
  338. * \return Nothing. pos is set to the first character which is not a white space
  339. */
  340. static void NutSsiSkipWhitespace(char *buffer, uint16_t *pos, uint16_t end)
  341. {
  342. while ((*pos < end) && (
  343. (buffer[*pos] == '\n') || (buffer[*pos] == '\r') ||
  344. (buffer[*pos] == '\t') || (buffer[*pos] == ' ')))
  345. (*pos) ++;
  346. }
  347. /*!
  348. * \brief Check if a comment is a ssi directive
  349. *
  350. * Check if a comment is a ssi directive and replace the directive by the included data.
  351. * Allowed directives are:
  352. *
  353. * <!--#include virtual="/news/news.htm" -->
  354. * <!--#include file="UROM:/news/news.htm" -->
  355. * <!--#exec cgi="/cgi-bin/counter.cgi" -->
  356. *
  357. * \param stream Stream of the socket connection, previously opened for
  358. * binary read and write.
  359. * \param buffer Current file buffer so search in. The buffer is set to the start of a html comment
  360. * \param end End position of the comment.
  361. * \param http_root The root path of the http-deamon
  362. * \param req The http request struct of the top most http_request
  363. */
  364. static uint8_t NutSsiCheckForSsi(FILE *stream, char *buffer, uint16_t end, char* http_root, REQUEST *req)
  365. {
  366. uint16_t pos = 4; // First character after comment start
  367. char * filename;
  368. uint8_t type;
  369. pos = 4;
  370. NutSsiSkipWhitespace(buffer, &pos, end); // Skip whitespaces after comment start
  371. if (pos == end) return 0;
  372. if (strncasecmp(&buffer[pos], "#include", 8) == 0) { // Search include directive
  373. pos += 8;
  374. type = SSI_TYPE_VIRTUAL;
  375. } else
  376. if (strncasecmp(&buffer[pos], "#exec", 5) == 0) { // Search include or exec directive
  377. pos += 5;
  378. type = SSI_TYPE_EXEC;
  379. } else
  380. if (strncasecmp(&buffer[pos], "#echo", 5) == 0) { // Search echo directive
  381. pos += 5;
  382. type = SSI_TYPE_ECHO;
  383. } else return 0; // No include or exec found. Skip the rest of this comment...
  384. if (pos >= end) return 0;
  385. NutSsiSkipWhitespace(buffer, &pos, end); // Skip whitespaces after #include directive
  386. if (pos == end) return 0;
  387. if (type == SSI_TYPE_VIRTUAL) {
  388. if (strncasecmp(&buffer[pos], "virtual", 7) == 0) { // Search virtual directive
  389. pos += 7;
  390. } else // No virtual found. Test for file...
  391. if (strncasecmp(&buffer[pos], "file", 4) == 0) { // Search file directive
  392. pos += 4;
  393. type = SSI_TYPE_FILE;
  394. } else return 0; // No file found. Test for file...
  395. }
  396. else if (type == SSI_TYPE_ECHO) {
  397. if (strncasecmp(&buffer[pos], "var", 3) == 0) { // Search var directive
  398. pos += 3;
  399. } else return 0; // No file found. Test for file...
  400. } else {
  401. if (strncasecmp(&buffer[pos], "cgi", 3) == 0) { // Search cgi directive
  402. pos += 3;
  403. } else return 0; // No cgi found. return...
  404. }
  405. if (pos >= end) return 0;
  406. NutSsiSkipWhitespace(buffer, &pos, end); // Skip whitespaces after virtual, file or cgi directive
  407. if (pos == end) return 0;
  408. if (buffer[pos] != '=') return 0; // check for assertion
  409. pos ++;
  410. NutSsiSkipWhitespace(buffer, &pos, end); // Skip whitespaces after assertion
  411. if (pos == end) return 0;
  412. if (buffer[pos] == '"') { // Search for filename and pass to output function
  413. pos ++;
  414. if (pos == end) return 0;
  415. filename = &buffer[pos];
  416. while (buffer[pos] != '"') {
  417. pos ++;
  418. if (pos == end) return 0;
  419. }
  420. buffer[pos] = '\0';
  421. switch (type) {
  422. case SSI_TYPE_FILE:
  423. NutSsiProcessFile(stream, filename);
  424. break;
  425. case SSI_TYPE_VIRTUAL:
  426. NutSsiProcessVirtual(stream, filename, http_root, req);
  427. break;
  428. case SSI_TYPE_EXEC:
  429. NutSsiProcessVirtual(stream, filename, http_root, req);
  430. break;
  431. case SSI_TYPE_ECHO:
  432. NutSsiProcessEcho(stream, filename, req);
  433. break;
  434. }
  435. }
  436. return 1;
  437. }
  438. /*!
  439. * \brief Check a file for html comments
  440. *
  441. * Check a file for html comments and then call NutSsiCheckForSsi to seach a ssi directive
  442. * Allowed diretives are:
  443. *
  444. * <!--#include virtual="/news/news.htm" -->
  445. * <!--#include file="UROM:/news/news.htm" -->
  446. * <!--#exec cgi="/cgi-bin/counter.cgi" -->
  447. *
  448. * \param stream Stream of the socket connection, previously opened for
  449. * binary read and write.
  450. * \param fd Filedescriptor pointing to a just opened file.
  451. * \param file_len length of this file
  452. * \param http_root The root path of the http-deamon
  453. * \param req The http request struct of the top most http_request
  454. */
  455. static void NutHttpProcessSHTML(FILE * stream, int fd, int file_len, char* http_root, REQUEST *req)
  456. {
  457. char * buffer;
  458. uint8_t in_comment;
  459. int buffsize;
  460. int fpos;
  461. int n;
  462. char *index;
  463. uint8_t found;
  464. buffsize = MIN(BUFSIZE, file_len);
  465. buffer = malloc(buffsize+1);
  466. in_comment = 0;
  467. fpos = 0;
  468. while (file_len != fpos) {
  469. memset(buffer, 0, buffsize+1);
  470. n = _read(fd, buffer, MIN(buffsize, file_len-fpos));
  471. if (!in_comment) {
  472. index = strstr(buffer, "<!--");
  473. if (index == NULL) { // Nothing found. print next 412 characters, seek to next startpoint.
  474. if (file_len > buffsize) {
  475. fwrite(buffer, 1, MIN(buffsize-100, n), stream);
  476. fpos += MIN(buffsize-100, n);
  477. _seek(fd, fpos, SEEK_SET);
  478. } else {
  479. fwrite(buffer, 1, n, stream);
  480. fpos += n;
  481. }
  482. } else {
  483. found = (int)index - (int)buffer; // We have found a comment initializer. Seek to the startpoint and print the beginning of the buffer.
  484. fwrite (buffer, 1, found, stream);
  485. fpos += found;
  486. _seek(fd, fpos, SEEK_SET);
  487. in_comment = 1;
  488. }
  489. } else { // Ok, we assume we are "into" a comment.
  490. index = strstr(buffer, "-->");
  491. if (index == NULL) { // We have not found the end of the comment in the next 512 characters. Byepass this comment.
  492. fwrite(buffer, 1, MIN(buffsize, n), stream);
  493. fpos += MIN(buffsize, n);
  494. in_comment = 0;
  495. } else { // Ok. This seems to be a comment with maximum length of 512 bytes. We now search for ssi code.
  496. found = (int)index - (int)buffer;
  497. if (!NutSsiCheckForSsi(stream, buffer, found, http_root, req)) {
  498. fwrite(buffer, 1, found+3, stream);
  499. }
  500. fpos += found+3;
  501. _seek(fd, fpos, SEEK_SET);
  502. in_comment = 0;
  503. }
  504. }
  505. }
  506. free(buffer);
  507. }
  508. /*!
  509. * \brief Register SSI handler for shtml files.
  510. *
  511. * shtml files may use the following ssi commands:
  512. *
  513. * <!--#include virtual="/news/news.htm" -->
  514. * <!--#include file="UROM:/news/news.htm" -->
  515. * <!--#exec cgi="/cgi-bin/counter.cgi" -->
  516. * <!--#echo var="counter" -->
  517. */
  518. void NutRegisterSsi(void)
  519. {
  520. NutSetMimeHandler(".shtml", NutHttpProcessSHTML);
  521. }
  522. /*!
  523. * \brief Register SSI handler for variables.
  524. */
  525. int NutRegisterSsiVarHandler(char * (*handler)(char *name, REQUEST *req))
  526. {
  527. ssivar_handler = handler;
  528. return 0;
  529. }
  530. /*@}*/