httpdespfs.c 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. /*
  2. Connector to let httpd use the espfs filesystem to serve the files in it.
  3. */
  4. /*
  5. * ----------------------------------------------------------------------------
  6. * "THE BEER-WARE LICENSE" (Revision 42):
  7. * Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
  8. * this notice you can do whatever you want with this stuff. If we meet some day,
  9. * and you think this stuff is worth it, you can buy me a beer in return.
  10. * ----------------------------------------------------------------------------
  11. * Modified and enhanced by Thorsten von Eicken in 2015
  12. * ----------------------------------------------------------------------------
  13. */
  14. #include "httpdespfs.h"
  15. #define MAX_URL_LEN 255
  16. // The static files marked with FLAG_GZIP are compressed and will be served with GZIP compression.
  17. // If the client does not advertise that he accepts GZIP send following warning message (telnet users for e.g.)
  18. static const char *gzipNonSupportedMessage = "HTTP/1.0 501 Not implemented\r\nServer: esp8266-httpd/"HTTPDVER"\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: 52\r\n\r\nYour browser does not accept gzip-compressed data.\r\n";
  19. //This is a catch-all cgi function. It takes the url passed to it, looks up the corresponding
  20. //path in the filesystem and if it exists, passes the file through. This simulates what a normal
  21. //webserver would do with static files.
  22. int ICACHE_FLASH_ATTR
  23. cgiEspFsHook(HttpdConnData *connData) {
  24. EspFsFile *file=connData->cgiData;
  25. int len;
  26. char buff[1024];
  27. char acceptEncodingBuffer[64];
  28. int isGzip;
  29. //os_printf("cgiEspFsHook conn=%p conn->conn=%p file=%p\n", connData, connData->conn, file);
  30. if (connData->conn==NULL) {
  31. //Connection aborted. Clean up.
  32. espFsClose(file);
  33. return HTTPD_CGI_DONE;
  34. }
  35. if (file==NULL) {
  36. //First call to this cgi. Open the file so we can read it.
  37. file=espFsOpen(espLinkCtx, connData->url);
  38. if (file==NULL) {
  39. if( espFsIsValid(userPageCtx) )
  40. {
  41. int maxLen = strlen(connData->url) * 2 + 1;
  42. if( maxLen > MAX_URL_LEN )
  43. maxLen = MAX_URL_LEN;
  44. char decodedURL[maxLen];
  45. httpdUrlDecode(connData->url, strlen(connData->url), decodedURL, maxLen);
  46. file = espFsOpen(userPageCtx, decodedURL );
  47. if( file == NULL )
  48. return HTTPD_CGI_NOTFOUND;
  49. }
  50. else
  51. return HTTPD_CGI_NOTFOUND;
  52. }
  53. // The gzip checking code is intentionally without #ifdefs because checking
  54. // for FLAG_GZIP (which indicates gzip compressed file) is very easy, doesn't
  55. // mean additional overhead and is actually safer to be on at all times.
  56. // If there are no gzipped files in the image, the code bellow will not cause any harm.
  57. // Check if requested file was GZIP compressed
  58. isGzip = espFsFlags(file) & FLAG_GZIP;
  59. if (isGzip) {
  60. // Check the browser's "Accept-Encoding" header. If the client does not
  61. // advertise that he accepts GZIP send a warning message (telnet users for e.g.)
  62. httpdGetHeader(connData, "Accept-Encoding", acceptEncodingBuffer, 64);
  63. if (os_strstr(acceptEncodingBuffer, "gzip") == NULL) {
  64. //No Accept-Encoding: gzip header present
  65. httpdSend(connData, gzipNonSupportedMessage, -1);
  66. espFsClose(file);
  67. return HTTPD_CGI_DONE;
  68. }
  69. }
  70. connData->cgiData=file;
  71. httpdStartResponse(connData, 200);
  72. httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url));
  73. if (isGzip) {
  74. httpdHeader(connData, "Content-Encoding", "gzip");
  75. }
  76. httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate");
  77. if(connData->cgiArg != NULL)
  78. httpdHeader(connData, "Set-Cookie", connData->cgiArg);
  79. httpdEndHeaders(connData);
  80. return HTTPD_CGI_MORE;
  81. }
  82. len=espFsRead(file, buff, 1024);
  83. if (len>0) espconn_sent(connData->conn, (uint8 *)buff, len);
  84. if (len!=1024) {
  85. //We're done.
  86. espFsClose(file);
  87. return HTTPD_CGI_DONE;
  88. } else {
  89. //Ok, till next time.
  90. return HTTPD_CGI_MORE;
  91. }
  92. }
  93. #if 0
  94. //cgiEspFsHtml is a simple HTML file that gets prefixed by head.tpl
  95. int ICACHE_FLASH_ATTR
  96. cgiEspFsHtml(HttpdConnData *connData) {
  97. EspFsFile *file = connData->cgiData;
  98. char buff[2048];
  99. if (connData->conn==NULL) {
  100. // Connection aborted. Clean up.
  101. if (file != NULL) espFsClose(file);
  102. return HTTPD_CGI_DONE;
  103. }
  104. // The first time around we send the head template in one go and we open the file
  105. if (file == NULL) {
  106. int status = 200;
  107. // open file, return error on failure
  108. file = espFsOpen("/head.tpl");
  109. if (file == NULL) {
  110. os_strcpy(buff, "Header file 'head.tpl' not found\n");
  111. os_printf(buff);
  112. status = 500;
  113. goto error;
  114. }
  115. // read file and return it
  116. int len = espFsRead(file, buff, sizeof(buff));
  117. espFsClose(file);
  118. if (len == sizeof(buff)) {
  119. os_sprintf(buff, "Header file 'head.tpl' too large (%d>%d)!\n", len, sizeof(buff));
  120. os_printf(buff);
  121. status = 500;
  122. goto error;
  123. }
  124. // before returning, open the real file for next time around
  125. file = espFsOpen(connData->url);
  126. if (file == NULL) {
  127. os_strcpy(buff, connData->url);
  128. os_strcat(buff, " not found\n");
  129. os_printf(buff);
  130. status = 404;
  131. goto error;
  132. }
  133. connData->cgiData = file;
  134. httpdStartResponse(connData, status);
  135. httpdHeader(connData, "Content-Type", "text/html; charset=UTF-8");
  136. httpdEndHeaders(connData);
  137. httpdSend(connData, buff, len);
  138. printGlobalJSON(connData);
  139. return HTTPD_CGI_MORE;
  140. error: // error response
  141. httpdStartResponse(connData, status);
  142. httpdHeader(connData, "Content-Type", "text/html; charset=UTF-8");
  143. httpdEndHeaders(connData);
  144. httpdSend(connData, buff, -1);
  145. return HTTPD_CGI_DONE;
  146. }
  147. // The second time around send actual file
  148. int len = espFsRead(file, buff, sizeof(buff));
  149. httpdSend(connData, buff, len);
  150. if (len == sizeof(buff)) {
  151. return HTTPD_CGI_MORE;
  152. } else {
  153. connData->cgiData = NULL;
  154. espFsClose(file);
  155. return HTTPD_CGI_DONE;
  156. }
  157. }
  158. #endif
  159. #if 0
  160. //cgiEspFsTemplate can be used as a template.
  161. typedef struct {
  162. EspFsFile *file;
  163. void *tplArg;
  164. char token[64];
  165. int tokenPos;
  166. } TplData;
  167. typedef void (* TplCallback)(HttpdConnData *connData, char *token, void **arg);
  168. int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) {
  169. TplData *tpd=connData->cgiData;
  170. int len;
  171. int x, sp=0;
  172. char *e=NULL;
  173. char buff[1025];
  174. if (connData->conn==NULL) {
  175. //Connection aborted. Clean up.
  176. ((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg);
  177. espFsClose(tpd->file);
  178. os_free(tpd);
  179. return HTTPD_CGI_DONE;
  180. }
  181. if (tpd==NULL) {
  182. //First call to this cgi. Open the file so we can read it.
  183. tpd=(TplData *)os_malloc(sizeof(TplData));
  184. tpd->file=espFsOpen(connData->url);
  185. tpd->tplArg=NULL;
  186. tpd->tokenPos=-1;
  187. if (tpd->file==NULL) {
  188. espFsClose(tpd->file);
  189. os_free(tpd);
  190. return HTTPD_CGI_NOTFOUND;
  191. }
  192. if (espFsFlags(tpd->file) & FLAG_GZIP) {
  193. os_printf("cgiEspFsTemplate: Trying to use gzip-compressed file %s as template!\n", connData->url);
  194. espFsClose(tpd->file);
  195. os_free(tpd);
  196. return HTTPD_CGI_NOTFOUND;
  197. }
  198. connData->cgiData=tpd;
  199. httpdStartResponse(connData, 200);
  200. httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url));
  201. httpdEndHeaders(connData);
  202. return HTTPD_CGI_MORE;
  203. }
  204. len=espFsRead(tpd->file, buff, 1024);
  205. if (len>0) {
  206. sp=0;
  207. e=buff;
  208. for (x=0; x<len; x++) {
  209. if (tpd->tokenPos==-1) {
  210. //Inside ordinary text.
  211. if (buff[x]=='%') {
  212. //Send raw data up to now
  213. if (sp!=0) httpdSend(connData, e, sp);
  214. sp=0;
  215. //Go collect token chars.
  216. tpd->tokenPos=0;
  217. } else {
  218. sp++;
  219. }
  220. } else {
  221. if (buff[x]=='%') {
  222. if (tpd->tokenPos==0) {
  223. //This is the second % of a %% escape string.
  224. //Send a single % and resume with the normal program flow.
  225. httpdSend(connData, "%", 1);
  226. } else {
  227. //This is an actual token.
  228. tpd->token[tpd->tokenPos++]=0; //zero-terminate token
  229. ((TplCallback)(connData->cgiArg))(connData, tpd->token, &tpd->tplArg);
  230. }
  231. //Go collect normal chars again.
  232. e=&buff[x+1];
  233. tpd->tokenPos=-1;
  234. } else {
  235. if (tpd->tokenPos<(sizeof(tpd->token)-1)) tpd->token[tpd->tokenPos++]=buff[x];
  236. }
  237. }
  238. }
  239. }
  240. //Send remaining bit.
  241. if (sp!=0) httpdSend(connData, e, sp);
  242. if (len!=1024) {
  243. //We're done.
  244. ((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg);
  245. espFsClose(tpd->file);
  246. os_free(tpd);
  247. return HTTPD_CGI_DONE;
  248. } else {
  249. //Ok, till next time.
  250. return HTTPD_CGI_MORE;
  251. }
  252. }
  253. #endif