multipart.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. #include <esp8266.h>
  2. #include <osapi.h>
  3. #include "multipart.h"
  4. #include "cgi.h"
  5. #define BOUNDARY_SIZE 128
  6. typedef enum {
  7. STATE_SEARCH_BOUNDARY = 0, // state: searching multipart boundary
  8. STATE_SEARCH_HEADER, // state: search multipart file header
  9. STATE_SEARCH_HEADER_END, // state: search the end of the file header
  10. STATE_UPLOAD_FILE, // state: read file content
  11. STATE_ERROR, // state: error (stop processing)
  12. } MultipartState;
  13. struct _MultipartCtx {
  14. MultipartCallback callBack; // callback for multipart events
  15. int position; // current file position
  16. int startTime; // timestamp when connection was initiated
  17. int recvPosition; // receive position (how many bytes was processed from the HTTP post)
  18. char * boundaryBuffer; // buffer used for boundary detection
  19. int boundaryBufferPtr; // pointer in the boundary buffer
  20. MultipartState state; // multipart processing state
  21. };
  22. // this method is responsible for creating the multipart context
  23. MultipartCtx * ICACHE_FLASH_ATTR multipartCreateContext(MultipartCallback callback)
  24. {
  25. MultipartCtx * ctx = (MultipartCtx *)os_malloc(sizeof(MultipartCtx));
  26. ctx->callBack = callback;
  27. ctx->position = ctx->startTime = ctx->recvPosition = ctx->boundaryBufferPtr = 0;
  28. ctx->boundaryBuffer = NULL;
  29. ctx->state = STATE_SEARCH_BOUNDARY;
  30. return ctx;
  31. }
  32. // for allocating buffer for multipart upload
  33. void ICACHE_FLASH_ATTR multipartAllocBoundaryBuffer(MultipartCtx * context)
  34. {
  35. if( context->boundaryBuffer == NULL )
  36. context->boundaryBuffer = (char *)os_malloc(3*BOUNDARY_SIZE + 1);
  37. context->boundaryBufferPtr = 0;
  38. }
  39. // for freeing multipart buffer
  40. void ICACHE_FLASH_ATTR multipartFreeBoundaryBuffer(MultipartCtx * context)
  41. {
  42. if( context->boundaryBuffer != NULL )
  43. {
  44. os_free(context->boundaryBuffer);
  45. context->boundaryBuffer = NULL;
  46. }
  47. }
  48. // for destroying the context
  49. void ICACHE_FLASH_ATTR multipartDestroyContext(MultipartCtx * context)
  50. {
  51. multipartFreeBoundaryBuffer(context);
  52. os_free(context);
  53. }
  54. // this is because of os_memmem is missing
  55. void * mp_memmem(const void *l, size_t l_len, const void *s, size_t s_len)
  56. {
  57. register char *cur, *last;
  58. const char *cl = (const char *)l;
  59. const char *cs = (const char *)s;
  60. /* we need something to compare */
  61. if (l_len == 0 || s_len == 0)
  62. return NULL;
  63. /* "s" must be smaller or equal to "l" */
  64. if (l_len < s_len)
  65. return NULL;
  66. /* special case where s_len == 1 */
  67. if (s_len == 1)
  68. return memchr(l, (int)*cs, l_len);
  69. /* the last position where its possible to find "s" in "l" */
  70. last = (char *)cl + l_len - s_len;
  71. for (cur = (char *)cl; cur <= last; cur++)
  72. if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
  73. return cur;
  74. return NULL;
  75. }
  76. // this method is for processing data coming from the HTTP post request
  77. // context: the multipart context
  78. // boundary: a string which indicates boundary
  79. // data: the received data
  80. // len: the received data length (can't be bigger than BOUNDARY_SIZE)
  81. // last: last packet indicator
  82. //
  83. // Detecting a boundary is not easy. One has to take care of boundaries which are splitted in 2 packets
  84. // [Packet 1, 5 bytes of the boundary][Packet 2, remaining 10 bytes of the boundary];
  85. //
  86. // Algorythm:
  87. // - create a buffer which size is 3*BOUNDARY_SIZE
  88. // - put data into the buffer as long as the buffer size is smaller than 2*BOUNDARY_SIZE
  89. // - search boundary in the received buffer, if found: boundary reached -> process data before boundary -> process boundary
  90. // - if not found -> process the first BOUNDARY_SIZE amount of bytes from the buffer
  91. // - remove processed data from the buffer
  92. // this algorythm guarantees that no boundary loss will happen
  93. int ICACHE_FLASH_ATTR multipartProcessData(MultipartCtx * context, char * boundary, char * data, int len, int last)
  94. {
  95. if( len != 0 ) // add data to the boundary buffer
  96. {
  97. os_memcpy(context->boundaryBuffer + context->boundaryBufferPtr, data, len);
  98. context->boundaryBufferPtr += len;
  99. context->boundaryBuffer[context->boundaryBufferPtr] = 0;
  100. }
  101. while( context->boundaryBufferPtr > 0 )
  102. {
  103. if( ! last && context->boundaryBufferPtr <= 2 * BOUNDARY_SIZE ) // return if buffer is too small and not the last packet is processed
  104. return 0;
  105. int dataSize = BOUNDARY_SIZE;
  106. char * boundaryLoc = mp_memmem( context->boundaryBuffer, context->boundaryBufferPtr, boundary, os_strlen(boundary) );
  107. if( boundaryLoc != NULL )
  108. {
  109. int pos = boundaryLoc - context->boundaryBuffer;
  110. if( pos > BOUNDARY_SIZE ) // process in the next call
  111. boundaryLoc = NULL;
  112. else
  113. dataSize = pos;
  114. }
  115. if( dataSize != 0 ) // data to process
  116. {
  117. switch( context->state )
  118. {
  119. case STATE_SEARCH_HEADER:
  120. case STATE_SEARCH_HEADER_END:
  121. {
  122. char * chr = os_strchr( context->boundaryBuffer, '\n' );
  123. if( chr != NULL )
  124. {
  125. // chop datasize to contain only one line
  126. int pos = chr - context->boundaryBuffer + 1;
  127. if( pos < dataSize ) // if chop smaller than the dataSize, delete the boundary
  128. {
  129. dataSize = pos;
  130. boundaryLoc = NULL; // process boundary next time
  131. }
  132. if( context->state == STATE_SEARCH_HEADER_END )
  133. {
  134. if( pos == 1 || ( ( pos == 2 ) && ( context->boundaryBuffer[0] == '\r' ) ) ) // empty line?
  135. {
  136. context->state = STATE_UPLOAD_FILE;
  137. context->position = 0;
  138. }
  139. }
  140. else if( os_strncmp( context->boundaryBuffer, "Content-Disposition:", 20 ) == 0 )
  141. {
  142. char * fnam = os_strstr( context->boundaryBuffer, "filename=" );
  143. if( fnam != NULL )
  144. {
  145. int pos = fnam - context->boundaryBuffer + 9;
  146. if( pos < dataSize )
  147. {
  148. while(context->boundaryBuffer[pos] == ' ') pos++; // skip spaces
  149. if( context->boundaryBuffer[pos] == '"' ) // quote start
  150. {
  151. pos++;
  152. int start = pos;
  153. while( pos < context->boundaryBufferPtr )
  154. {
  155. if( context->boundaryBuffer[pos] == '"' ) // quote end
  156. break;
  157. pos++;
  158. }
  159. if( pos < context->boundaryBufferPtr )
  160. {
  161. context->boundaryBuffer[pos] = 0; // terminating zero for the file name
  162. os_printf("Uploading file: %s\n", context->boundaryBuffer + start);
  163. if( context->callBack( FILE_START, context->boundaryBuffer + start, pos - start, 0 ) ) // FILE_START callback
  164. return 1; // if an error happened
  165. context->boundaryBuffer[pos] = '"'; // restore the original quote
  166. context->state = STATE_SEARCH_HEADER_END;
  167. }
  168. }
  169. }
  170. }
  171. }
  172. }
  173. }
  174. break;
  175. case STATE_UPLOAD_FILE:
  176. {
  177. char c = context->boundaryBuffer[dataSize];
  178. context->boundaryBuffer[dataSize] = 0; // add terminating zero (for easier handling)
  179. if( context->callBack( FILE_DATA, context->boundaryBuffer, dataSize, context->position ) ) // FILE_DATA callback
  180. return 1;
  181. context->boundaryBuffer[dataSize] = c;
  182. context->position += dataSize;
  183. }
  184. break;
  185. default:
  186. break;
  187. }
  188. }
  189. if( boundaryLoc != NULL ) // boundary found?
  190. {
  191. dataSize += os_strlen(boundary); // jump over the boundary
  192. if( context->state == STATE_UPLOAD_FILE )
  193. {
  194. if( context->callBack( FILE_DONE, NULL, 0, context->position ) ) // file done callback
  195. return 1; // if an error happened
  196. os_printf("File upload done\n");
  197. }
  198. context->state = STATE_SEARCH_HEADER; // search the next header
  199. }
  200. // move the buffer back with dataSize
  201. context->boundaryBufferPtr -= dataSize;
  202. os_memcpy(context->boundaryBuffer, context->boundaryBuffer + dataSize, context->boundaryBufferPtr);
  203. }
  204. return 0;
  205. }
  206. // for processing multipart requests
  207. int ICACHE_FLASH_ATTR multipartProcess(MultipartCtx * context, HttpdConnData * connData )
  208. {
  209. if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
  210. if (connData->requestType == HTTPD_METHOD_POST) {
  211. HttpdPostData *post = connData->post;
  212. if( post->multipartBoundary == NULL )
  213. {
  214. errorResponse(connData, 404, "Only multipart POST is supported");
  215. return HTTPD_CGI_DONE;
  216. }
  217. if( connData->startTime != context->startTime )
  218. {
  219. // reinitialize, as this is a new request
  220. context->position = 0;
  221. context->recvPosition = 0;
  222. context->startTime = connData->startTime;
  223. context->state = STATE_SEARCH_BOUNDARY;
  224. multipartAllocBoundaryBuffer(context);
  225. if( context->callBack( FILE_UPLOAD_START, NULL, 0, context->position ) ) // start uploading files
  226. context->state = STATE_ERROR;
  227. }
  228. if( context->state != STATE_ERROR )
  229. {
  230. int feed = 0;
  231. while( feed < post->buffLen )
  232. {
  233. int len = post->buffLen - feed;
  234. if( len > BOUNDARY_SIZE )
  235. len = BOUNDARY_SIZE;
  236. if( multipartProcessData(context, post->multipartBoundary, post->buff + feed, len, 0) )
  237. {
  238. context->state = STATE_ERROR;
  239. break;
  240. }
  241. feed += len;
  242. }
  243. }
  244. context->recvPosition += post->buffLen;
  245. if( context->recvPosition < post->len )
  246. return HTTPD_CGI_MORE;
  247. if( context->state != STATE_ERROR )
  248. {
  249. // this is the last package, process the remaining data
  250. if( multipartProcessData(context, post->multipartBoundary, NULL, 0, 1) )
  251. context->state = STATE_ERROR;
  252. else if( context->callBack( FILE_UPLOAD_DONE, NULL, 0, context->position ) ) // done with files
  253. context->state = STATE_ERROR;
  254. }
  255. multipartFreeBoundaryBuffer( context );
  256. if( context->state == STATE_ERROR )
  257. errorResponse(connData, 400, "Invalid file upload!");
  258. else
  259. {
  260. httpdStartResponse(connData, 204);
  261. httpdEndHeaders(connData);
  262. }
  263. return HTTPD_CGI_DONE;
  264. }
  265. else {
  266. errorResponse(connData, 404, "Only multipart POST is supported");
  267. return HTTPD_CGI_DONE;
  268. }
  269. }