|  | @@ -4,6 +4,9 @@
 | 
	
		
			
				|  |  |  #include "fw.h"
 | 
	
		
			
				|  |  |  #include "httpd.h"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +#include <incbin.h>
 | 
	
		
			
				|  |  | +#include <unzipLIB.h>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  static httpd_handle_t httpd;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  esp_err_t httpd_firmware_upgrade_handler(httpd_req_t *req)
 | 
	
	
		
			
				|  | @@ -16,7 +19,7 @@ esp_err_t httpd_firmware_upgrade_handler(httpd_req_t *req)
 | 
	
		
			
				|  |  |  	   req->content_len, req->uri);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      if (!req->content_len) {
 | 
	
		
			
				|  |  | -	return httpd_resp_send_err(req, 411, NULL);
 | 
	
		
			
				|  |  | +	return httpd_resp_send_err(req, 411, "Length required");
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      rv = firmware_update((read_func_t)httpd_req_recv, (token_t)req);
 | 
	
	
		
			
				|  | @@ -40,51 +43,203 @@ esp_err_t httpd_firmware_upgrade_handler(httpd_req_t *req)
 | 
	
		
			
				|  |  |  		   reboot_delayed());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /* 200 and text/html are the response defaults, no need to set */
 | 
	
		
			
				|  |  | +    httpd_resp_set_hdr(req, "Connection", "close");
 | 
	
		
			
				|  |  |      err = httpd_resp_send(req, response, len);
 | 
	
		
			
				|  |  |      free(response);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      return err;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -static esp_err_t httpd_get_handler(httpd_req_t *req)
 | 
	
		
			
				|  |  | +INCBIN(wwwzip, "data/www.zip");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +struct mime_type {
 | 
	
		
			
				|  |  | +    const char *ext;
 | 
	
		
			
				|  |  | +    size_t ext_len;
 | 
	
		
			
				|  |  | +    const char *mime;
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +static const struct mime_type mime_types[] = {
 | 
	
		
			
				|  |  | +    { ".html", 5, "text/html"                },
 | 
	
		
			
				|  |  | +    { ".css",  4, "text/css"                  },
 | 
	
		
			
				|  |  | +    { ".txt",  4, "text/plain; charset=UTF-8" },
 | 
	
		
			
				|  |  | +    { ".bin",  4, "application/octet-stream"  },
 | 
	
		
			
				|  |  | +    { ".fw",   3, "application/octet-stream"  },
 | 
	
		
			
				|  |  | +    { NULL,    0, "text/html"  } /* Default */
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +static esp_err_t httpd_static_handler(httpd_req_t *req)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -    char *response;
 | 
	
		
			
				|  |  | -    int len;
 | 
	
		
			
				|  |  | -    esp_err_t err;
 | 
	
		
			
				|  |  | +    static const char index_filename[] = "index.html";
 | 
	
		
			
				|  |  | +    const size_t buffer_size = UNZ_BUFSIZE;
 | 
	
		
			
				|  |  | +    const char *uri, *enduri;
 | 
	
		
			
				|  |  | +    bool add_index;
 | 
	
		
			
				|  |  | +    char *buffer = NULL;
 | 
	
		
			
				|  |  | +    ZIPFILE *zip = NULL;
 | 
	
		
			
				|  |  | +    unzFile unz  = NULL;
 | 
	
		
			
				|  |  | +    bool file_open = false;
 | 
	
		
			
				|  |  | +    int err = 0;
 | 
	
		
			
				|  |  | +    size_t len;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    len = asprintf(&response,
 | 
	
		
			
				|  |  | -		   "<!DOCTYPE html>\n"
 | 
	
		
			
				|  |  | -		   "<html>\n"
 | 
	
		
			
				|  |  | -		   "<head>\n"
 | 
	
		
			
				|  |  | -		   "<title>Hello, World!</title>\n"
 | 
	
		
			
				|  |  | -		   "</head>\n"
 | 
	
		
			
				|  |  | -		   "<body>\n"
 | 
	
		
			
				|  |  | -		   "<p>Hello, World!</p>\n"
 | 
	
		
			
				|  |  | -		   "<p>GET uri = <tt>%s</tt></p>\n"
 | 
	
		
			
				|  |  | -		   "</pre>\n"
 | 
	
		
			
				|  |  | -		   "</body>\n"
 | 
	
		
			
				|  |  | -		   "</html>\n",
 | 
	
		
			
				|  |  | -		   req->uri);
 | 
	
		
			
				|  |  | +    uri = req->uri;
 | 
	
		
			
				|  |  | +    while (*uri == '/')
 | 
	
		
			
				|  |  | +	uri++;			/* Skip leading slashes */
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    enduri = strchr(uri, '\0');
 | 
	
		
			
				|  |  | +    if (enduri == uri) {
 | 
	
		
			
				|  |  | +	uri = index_filename;	/* Empty string */
 | 
	
		
			
				|  |  | +    } else if (enduri[0] == '/') {
 | 
	
		
			
				|  |  | +	add_index = true;
 | 
	
		
			
				|  |  | +	enduri--;		/* Drop terminal slash */
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    /* 200 and text/html are the response defaults, no need to set */
 | 
	
		
			
				|  |  | -    err = httpd_resp_send(req, response, len);
 | 
	
		
			
				|  |  | -    free(response);
 | 
	
		
			
				|  |  | +    buffer = malloc(buffer_size);
 | 
	
		
			
				|  |  | +    zip = malloc(sizeof *zip);
 | 
	
		
			
				|  |  | +    if (!buffer || !zip) {
 | 
	
		
			
				|  |  | +	httpd_resp_send_err(req, 503, "Out of memory");
 | 
	
		
			
				|  |  | +	goto out;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (enduri - req->uri + 1 + sizeof index_filename >= buffer_size) {
 | 
	
		
			
				|  |  | +	err = httpd_resp_send_err(req, 414, "URI too long");
 | 
	
		
			
				|  |  | +	goto out;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    char *p = mempcpy(buffer, req->uri, enduri - req->uri);
 | 
	
		
			
				|  |  | +    *p = '\0';
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    int found;
 | 
	
		
			
				|  |  | +    while (1) {
 | 
	
		
			
				|  |  | +	if (add_index) {
 | 
	
		
			
				|  |  | +	    if (p > buffer)
 | 
	
		
			
				|  |  | +		*p++ = '/';
 | 
	
		
			
				|  |  | +	    p = mempcpy(p, index_filename, sizeof index_filename);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	if (unzLocateFile(unz, buffer, 1) == UNZ_OK)
 | 
	
		
			
				|  |  | +	    break;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	if (add_index) {
 | 
	
		
			
				|  |  | +	    err = httpd_resp_send_404(req);
 | 
	
		
			
				|  |  | +	    goto out;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	add_index = true;	/* Try again with the index file */
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /* Note: p points to the end of the filename string */
 | 
	
		
			
				|  |  | +    size_t filelen = p - buffer;
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    const struct mime_type *mime_type = mime_types;
 | 
	
		
			
				|  |  | +    /* The default entry with extension "" will always match */
 | 
	
		
			
				|  |  | +    while (mime_type->ext_len) {
 | 
	
		
			
				|  |  | +	len = mime_type->ext_len;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	if (len < filelen && !memcmp(p - len, mime_type->ext, len))
 | 
	
		
			
				|  |  | +	    break;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	mime_type++;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    unz = unzOpen(NULL, (void *)gwwwzipData, gwwwzipSize,
 | 
	
		
			
				|  |  | +		  zip, NULL, NULL, NULL, NULL);
 | 
	
		
			
				|  |  | +    if (!unz) {
 | 
	
		
			
				|  |  | +	err = httpd_resp_send_err(req, 500, "Cannot open content archive");
 | 
	
		
			
				|  |  | +	goto out;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    unz_file_info fileinfo;
 | 
	
		
			
				|  |  | +    memset(&fileinfo, 0, sizeof fileinfo);
 | 
	
		
			
				|  |  | +    unzGetCurrentFileInfo(unz, &fileinfo, NULL, 0, NULL, 0, NULL, 0);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /*
 | 
	
		
			
				|  |  | +     * This is kind of brain dead, but it seems like the only sane
 | 
	
		
			
				|  |  | +     * way to not have to build the whole response in memory even
 | 
	
		
			
				|  |  | +     * though the length is known a priori.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    len = snprintf(buffer, buffer_size,
 | 
	
		
			
				|  |  | +		   "HTTP/1.1 200 OK\r\n"
 | 
	
		
			
				|  |  | +		   "Content-Type: %s\r\n"
 | 
	
		
			
				|  |  | +		   "Content-Length: %u\r\n"
 | 
	
		
			
				|  |  | +		   "Allow: GET, HEAD\r\n"
 | 
	
		
			
				|  |  | +		   "Etag: \"%08x:%08x\"\r\n"
 | 
	
		
			
				|  |  | +		   "Connection: close\r\n"
 | 
	
		
			
				|  |  | +		   "\r\n",
 | 
	
		
			
				|  |  | +		   mime_type->mime,
 | 
	
		
			
				|  |  | +		   fileinfo.uncompressed_size,
 | 
	
		
			
				|  |  | +		   /*
 | 
	
		
			
				|  |  | +		    * Hopefully the combination of date and CRC
 | 
	
		
			
				|  |  | +		    * is strong enough to quality for a "strong" ETag
 | 
	
		
			
				|  |  | +		    */
 | 
	
		
			
				|  |  | +		   fileinfo.dosDate, fileinfo.crc);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (len >= buffer_size) {
 | 
	
		
			
				|  |  | +	err = httpd_resp_send_err(req, 500, "buffer_size too small");
 | 
	
		
			
				|  |  | +	goto out;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    err = httpd_send(req, buffer, len);
 | 
	
		
			
				|  |  | +    if (err != len || req->method == HTTP_HEAD ||
 | 
	
		
			
				|  |  | +	!fileinfo.uncompressed_size)
 | 
	
		
			
				|  |  | +	goto out;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (unzOpenCurrentFile(unz) != UNZ_OK) {
 | 
	
		
			
				|  |  | +	err = httpd_resp_send_err(req, 500, "Cannot open file in archive");
 | 
	
		
			
				|  |  | +	goto out;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    file_open = true;
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    len = fileinfo.uncompressed_size;
 | 
	
		
			
				|  |  | +    while (len) {
 | 
	
		
			
				|  |  | +	size_t chunk = len;
 | 
	
		
			
				|  |  | +	if (chunk > buffer_size)
 | 
	
		
			
				|  |  | +	    chunk = buffer_size;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	if (unzReadCurrentFile(unz, buffer, chunk) != chunk) {
 | 
	
		
			
				|  |  | +	    err = ESP_ERR_HTTPD_RESULT_TRUNC;
 | 
	
		
			
				|  |  | +	    goto out;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	err = httpd_send(req, buffer, chunk);
 | 
	
		
			
				|  |  | +	if (err != chunk)
 | 
	
		
			
				|  |  | +	    goto out;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	len -= chunk;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    err = ESP_OK;		/* All good! */
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +out:
 | 
	
		
			
				|  |  | +    if (file_open)
 | 
	
		
			
				|  |  | +	unzCloseCurrentFile(unz);
 | 
	
		
			
				|  |  | +    if (unz)
 | 
	
		
			
				|  |  | +	unzClose(unz);
 | 
	
		
			
				|  |  | +    if (zip)
 | 
	
		
			
				|  |  | +	free(zip);
 | 
	
		
			
				|  |  | +    if (buffer)
 | 
	
		
			
				|  |  | +	free(buffer);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      return err;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -static const httpd_uri_t uri_get = {
 | 
	
		
			
				|  |  | -    .uri      = "/",
 | 
	
		
			
				|  |  | -    .method   = HTTP_GET,
 | 
	
		
			
				|  |  | -    .handler  = httpd_get_handler,
 | 
	
		
			
				|  |  | -    .user_ctx = NULL
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -static const httpd_uri_t uri_firmware_upgrade = {
 | 
	
		
			
				|  |  | -    .uri      = "/firmware-upgrade",
 | 
	
		
			
				|  |  | -    .method   = HTTP_POST,
 | 
	
		
			
				|  |  | -    .handler  = httpd_firmware_upgrade_handler,
 | 
	
		
			
				|  |  | -    .user_ctx = NULL
 | 
	
		
			
				|  |  | +static const httpd_uri_t uri_handlers[] = {
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +	.uri      = "/",
 | 
	
		
			
				|  |  | +	.method   = HTTP_GET,
 | 
	
		
			
				|  |  | +	.handler  = httpd_static_handler,
 | 
	
		
			
				|  |  | +	.user_ctx = NULL
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +	.uri      = "/",
 | 
	
		
			
				|  |  | +	.method   = HTTP_HEAD,
 | 
	
		
			
				|  |  | +	.handler  = httpd_static_handler,
 | 
	
		
			
				|  |  | +	.user_ctx = NULL
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +	.uri      = "/firmware-upgrade",
 | 
	
		
			
				|  |  | +	.method   = HTTP_POST,
 | 
	
		
			
				|  |  | +	.handler  = httpd_firmware_upgrade_handler,
 | 
	
		
			
				|  |  | +	.user_ctx = NULL
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  void my_httpd_stop(void)
 | 
	
	
		
			
				|  | @@ -114,8 +269,9 @@ void my_httpd_start(void)
 | 
	
		
			
				|  |  |        return;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      httpd = server;
 | 
	
		
			
				|  |  | -    httpd_register_uri_handler(httpd, &uri_get);
 | 
	
		
			
				|  |  | -    httpd_register_uri_handler(httpd, &uri_firmware_upgrade);
 | 
	
		
			
				|  |  | +    for (size_t i = 0; i < ARRAY_SIZE(uri_handlers); i++)
 | 
	
		
			
				|  |  | +	httpd_register_uri_handler(httpd, &uri_handlers[i]);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      esp_register_shutdown_handler(my_httpd_stop);
 | 
	
		
			
				|  |  |      printf("[HTTP] httpd started\n");
 | 
	
		
			
				|  |  |  }
 |