|  | @@ -10,7 +10,7 @@
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static httpd_handle_t httpd;
 | 
	
		
			
				|  |  |  static const char fallback_language[] = "en"; /* For unknown language */
 | 
	
		
			
				|  |  | -static const char index_filename[] = "index.html"; /* For a directory "file" */
 | 
	
		
			
				|  |  | +static const char redir_filename[] = "_redir"; /* For a directory "file" */
 | 
	
		
			
				|  |  |  #define MAX_LANG_LEN 16
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /* Looping version of httpd_send(); this is a hidden function in the server */
 | 
	
	
		
			
				|  | @@ -160,32 +160,48 @@ static esp_err_t httpd_send_plain(httpd_req_t *req,
 | 
	
		
			
				|  |  |      int hlen;
 | 
	
		
			
				|  |  |      const char *now = http_now();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    MSG("http_send_plain %u \"%.*s\"\n", rcode, (int)blen, body);
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  |      if (rcode > 499)
 | 
	
		
			
				|  |  |  	flags |= HSP_CLOSE;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      const char *closer = flags & HSP_CLOSE ? "Connection: close\r\n" : "";
 | 
	
		
			
				|  |  |      bool redirect = rcode >= 300 && rcode <= 399;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    char *refresher = NULL;
 | 
	
		
			
				|  |  | +    char *refresher = (char *)"";
 | 
	
		
			
				|  |  |      if (refresh) {
 | 
	
		
			
				|  |  |  	size_t referer_length = httpd_req_get_hdr_value_len(req, "Referer");
 | 
	
		
			
				|  |  | -	if (referer_length) {
 | 
	
		
			
				|  |  | -	    size_t refbufsize = sizeof("Refresh: ;url=\r\n")
 | 
	
		
			
				|  |  | -		+ 3*sizeof(unsigned int) + referer_length;
 | 
	
		
			
				|  |  | -	    refresher = malloc(refbufsize);
 | 
	
		
			
				|  |  | -	    size_t rlen = snprintf(refresher, refbufsize, "Refresh: %u;url=", refresh);
 | 
	
		
			
				|  |  | -	    httpd_req_get_hdr_value_str(req, "Referer", refresher+rlen,
 | 
	
		
			
				|  |  | -					refbufsize-rlen);
 | 
	
		
			
				|  |  | -	    memcpy(refresher+rlen+referer_length, "\r\n", 3);
 | 
	
		
			
				|  |  | +	const size_t refhdrsize = sizeof("Refresh: ;url=") +
 | 
	
		
			
				|  |  | +	    3*sizeof(unsigned int);
 | 
	
		
			
				|  |  | +	refresher = malloc(refhdrsize + referer_length + 4);
 | 
	
		
			
				|  |  | +	if (!refresher) {
 | 
	
		
			
				|  |  | +	    refresher = (char *)"";
 | 
	
		
			
				|  |  | +	} else {
 | 
	
		
			
				|  |  | +	    size_t rlen = snprintf(refresher, refhdrsize,
 | 
	
		
			
				|  |  | +				   "Refresh: %u;url=", refresh);
 | 
	
		
			
				|  |  | +	    if (referer_length) {
 | 
	
		
			
				|  |  | +		httpd_req_get_hdr_value_str(req, "Referer", refresher+rlen,
 | 
	
		
			
				|  |  | +					    referer_length+1);
 | 
	
		
			
				|  |  | +		rlen += referer_length;
 | 
	
		
			
				|  |  | +	    } else {
 | 
	
		
			
				|  |  | +		refresher[rlen++] = '/';
 | 
	
		
			
				|  |  | +	    }
 | 
	
		
			
				|  |  | +	    memcpy(refresher+rlen, "\r\n", 3);
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    if (!refresher)
 | 
	
		
			
				|  |  | -	refresher = (char *)"";
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      if (redirect) {
 | 
	
		
			
				|  |  |  	size_t blenadj = sizeof("3xx Redirect \r"); /* \0 -> \n so don't include it */
 | 
	
		
			
				|  |  |  	flags |= HSP_CRLF;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +	/* Drop any CR LF already in the redirect string */
 | 
	
		
			
				|  |  | +	for (size_t bchk = 0; bchk < blen; bchk++) {
 | 
	
		
			
				|  |  | +	    if (body[bchk] == '\r' || body[bchk] == '\n') {
 | 
	
		
			
				|  |  | +		blen = bchk;
 | 
	
		
			
				|  |  | +		break;
 | 
	
		
			
				|  |  | +	    }
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	hlen = asprintf(&header,
 | 
	
		
			
				|  |  |  			"HTTP/1.1 %u\r\n"
 | 
	
		
			
				|  |  |  			"Content-Type: %s\r\n"
 | 
	
	
		
			
				|  | @@ -204,7 +220,7 @@ static esp_err_t httpd_send_plain(httpd_req_t *req,
 | 
	
		
			
				|  |  |  			"HTTP/1.1 %u\r\n"
 | 
	
		
			
				|  |  |  			"Content-Type: %s\r\n"
 | 
	
		
			
				|  |  |  			"Content-Length: %zu\r\n"
 | 
	
		
			
				|  |  | -			"Cache-Control: no-store\r\n"
 | 
	
		
			
				|  |  | +			"Cache-Control: no-cache\r\n"
 | 
	
		
			
				|  |  |  			"Date: %s\r\n"
 | 
	
		
			
				|  |  |  			"%s%s"
 | 
	
		
			
				|  |  |  			"\r\n",
 | 
	
	
		
			
				|  | @@ -408,6 +424,7 @@ struct mime_type {
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #define MT_CHARSET	1	/* Add charset to Content-Type */
 | 
	
		
			
				|  |  | +#define MT_REDIR	2	/* It is a redirect */
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static const struct mime_type mime_types[] = {
 | 
	
		
			
				|  |  |      { ".html",  5, MT_CHARSET, "text/html"                 },
 | 
	
	
		
			
				|  | @@ -429,6 +446,7 @@ static const struct mime_type mime_types[] = {
 | 
	
		
			
				|  |  |      { ".xml",   4, MT_CHARSET, "text/xml"                  },
 | 
	
		
			
				|  |  |      { ".bin",   4, 0,          "application/octet-stream"  },
 | 
	
		
			
				|  |  |      { ".fw",    3, 0,          "application/octet-stream"  },
 | 
	
		
			
				|  |  | +    { "_redir", 6, MT_REDIR,   NULL                        },
 | 
	
		
			
				|  |  |      { NULL,     0, MT_CHARSET, "text/plain"                }  /* default */
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -436,7 +454,7 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |      size_t buffer_size = UNZ_BUFSIZE;
 | 
	
		
			
				|  |  |      const char *uri, *enduri;
 | 
	
		
			
				|  |  | -    bool add_index;
 | 
	
		
			
				|  |  | +    bool is_dir;
 | 
	
		
			
				|  |  |      char *buffer = NULL;
 | 
	
		
			
				|  |  |      ZIPFILE *zip = NULL;
 | 
	
		
			
				|  |  |      unzFile unz  = NULL;
 | 
	
	
		
			
				|  | @@ -460,22 +478,18 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      if (enduri == uri) {
 | 
	
		
			
				|  |  | -	add_index = true;
 | 
	
		
			
				|  |  | +	is_dir = true;
 | 
	
		
			
				|  |  |      } else if (last_slash == enduri-1) {
 | 
	
		
			
				|  |  | -	add_index = true;
 | 
	
		
			
				|  |  | +	is_dir = true;
 | 
	
		
			
				|  |  |  	enduri--;		/* Drop terminal slash */
 | 
	
		
			
				|  |  |  	if (first_slash == last_slash)
 | 
	
		
			
				|  |  |  	    first_slash = NULL;
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  | -	add_index = false;	/* Try the plain filename first */
 | 
	
		
			
				|  |  | +	is_dir = false;	/* Try the plain filename first */
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    const char *lang = getenv_def("LANG", "");
 | 
	
		
			
				|  |  | -    const size_t lang_size = lang ? strlen(lang)+1 : 0;
 | 
	
		
			
				|  |  | -    const size_t lang_space = (lang_size < sizeof fallback_language
 | 
	
		
			
				|  |  | -			       ? sizeof fallback_language : lang_size) + 1;
 | 
	
		
			
				|  |  |      const size_t filename_buffer_size =
 | 
	
		
			
				|  |  | -	(enduri - uri) + lang_space + 2 + sizeof index_filename;
 | 
	
		
			
				|  |  | +	(enduri - uri) + 2 + sizeof redir_filename;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      if (buffer_size < filename_buffer_size)
 | 
	
		
			
				|  |  |  	buffer_size = filename_buffer_size;
 | 
	
	
		
			
				|  | @@ -487,7 +501,7 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
 | 
	
		
			
				|  |  |  	goto out;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    char * const filebase = buffer + lang_space;
 | 
	
		
			
				|  |  | +    char * const filebase = buffer + 1;
 | 
	
		
			
				|  |  |      char * const endbase  = mempcpy(filebase, uri, enduri - uri);
 | 
	
		
			
				|  |  |      filebase[-1] = '/';
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -499,76 +513,97 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
 | 
	
		
			
				|  |  |  	goto out;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    char *filename, *endfile;
 | 
	
		
			
				|  |  | +    char * const filename = filebase; /* Separate for future needs */
 | 
	
		
			
				|  |  | +    char *endfile = endbase;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      unsigned int m;
 | 
	
		
			
				|  |  |      bool found = false;
 | 
	
		
			
				|  |  | -    for (m = add_index; m < 6; m += (add_index+1)) {
 | 
	
		
			
				|  |  | -	if (m & 1) {
 | 
	
		
			
				|  |  | -	    char *sx = endbase - (endbase[-1] == '/');
 | 
	
		
			
				|  |  | -	    *sx++ = '/';
 | 
	
		
			
				|  |  | -	    endfile = mempcpy(sx, index_filename, sizeof index_filename) - 1;
 | 
	
		
			
				|  |  | -	} else {
 | 
	
		
			
				|  |  | +    for (m = is_dir ? 2 : 0; m < 3; m++) {
 | 
	
		
			
				|  |  | +	char *sx;
 | 
	
		
			
				|  |  | +	switch (m) {
 | 
	
		
			
				|  |  | +	default:		/* filename = /url */
 | 
	
		
			
				|  |  |  	    endfile = endbase;
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	*endfile = '\0';
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	switch (m >> 1) {
 | 
	
		
			
				|  |  | -	case 1:
 | 
	
		
			
				|  |  | -	    if (!lang) {
 | 
	
		
			
				|  |  | -		filename = NULL;
 | 
	
		
			
				|  |  | -	    } else {
 | 
	
		
			
				|  |  | -		filename = filebase - lang_size;
 | 
	
		
			
				|  |  | -		memcpy(filename, lang, lang_size-1);
 | 
	
		
			
				|  |  | -	    }
 | 
	
		
			
				|  |  |  	    break;
 | 
	
		
			
				|  |  | -	case 2:
 | 
	
		
			
				|  |  | -	    filename = filebase - sizeof fallback_language;
 | 
	
		
			
				|  |  | -	    memcpy(filename, fallback_language, sizeof fallback_language - 1);
 | 
	
		
			
				|  |  | +	case 1:			/* filename = /url_redir */
 | 
	
		
			
				|  |  | +	    sx = endbase;
 | 
	
		
			
				|  |  | +	    endfile = mempcpy(sx, redir_filename, sizeof redir_filename) - 1;
 | 
	
		
			
				|  |  |  	    break;
 | 
	
		
			
				|  |  | -	default:
 | 
	
		
			
				|  |  | -	    filename = filebase;
 | 
	
		
			
				|  |  | +	case 2:			/* filename = /url/_redir */
 | 
	
		
			
				|  |  | +	    sx = endbase - (endbase[-1] == '/');
 | 
	
		
			
				|  |  | +	    *sx++ = '/';
 | 
	
		
			
				|  |  | +	    endfile = mempcpy(sx, redir_filename, sizeof redir_filename) - 1;
 | 
	
		
			
				|  |  |  	    break;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | +	*endfile = '\0';
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	if (!filename)
 | 
	
		
			
				|  |  |  	    continue;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	filename[-1] = '/';
 | 
	
		
			
				|  |  | -	MSG("trying to open: %s\n", filename);
 | 
	
		
			
				|  |  | +	MSG("trying to open: %s... ", filename);
 | 
	
		
			
				|  |  |  	if (unzLocateFile(unz, filename, 1) == UNZ_OK) {
 | 
	
		
			
				|  |  | +	    CMSG("found\n");
 | 
	
		
			
				|  |  |  	    found = true;
 | 
	
		
			
				|  |  |  	    break;
 | 
	
		
			
				|  |  | +	} else {
 | 
	
		
			
				|  |  | +	    CMSG("not found\n");
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    size_t filelen = endfile - filename;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |      if (!found) {
 | 
	
		
			
				|  |  |  	err = httpd_err_enoent(req);
 | 
	
		
			
				|  |  |  	goto out;
 | 
	
		
			
				|  |  | -    } else if (m) {
 | 
	
		
			
				|  |  | -	err = httpd_send_plain(req, 302 - (m == 1), filename-1, filelen+1, 0, 0);
 | 
	
		
			
				|  |  | -	goto out;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    /* Note: p points to the end of the filename string */
 | 
	
		
			
				|  |  | +    size_t filelen = endfile - filename;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      const struct mime_type *mime_type = mime_types;
 | 
	
		
			
				|  |  |      /* The default entry with length 0 will always match */
 | 
	
		
			
				|  |  |      while (mime_type->ext_len) {
 | 
	
		
			
				|  |  |  	len = mime_type->ext_len;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	if (len < filelen && !memcmp(endfile - len, mime_type->ext, len))
 | 
	
		
			
				|  |  | +	if (len <= filelen && !memcmp(endfile - len, mime_type->ext, len))
 | 
	
		
			
				|  |  |  	    break;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	mime_type++;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    MSG("found %s ext %s type %s\n",
 | 
	
		
			
				|  |  | +	filename,
 | 
	
		
			
				|  |  | +	mime_type->ext ? mime_type->ext : "(none)",
 | 
	
		
			
				|  |  | +	mime_type->mime ? mime_type->mime : "(none)");
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  |      unz_file_info fileinfo;
 | 
	
		
			
				|  |  |      memset(&fileinfo, 0, sizeof fileinfo);
 | 
	
		
			
				|  |  |      unzGetCurrentFileInfo(unz, &fileinfo, NULL, 0, NULL, 0, NULL, 0);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    MSG("len %u compressed %u\n",
 | 
	
		
			
				|  |  | +	fileinfo.uncompressed_size,
 | 
	
		
			
				|  |  | +	fileinfo.compressed_size);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /*
 | 
	
		
			
				|  |  | +     * Is it a redirect?
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    if (mime_type->flags & MT_REDIR) {
 | 
	
		
			
				|  |  | +	if (fileinfo.uncompressed_size > buffer_size ||
 | 
	
		
			
				|  |  | +	    unzOpenCurrentFile(unz) != UNZ_OK) {
 | 
	
		
			
				|  |  | +	    err = HTTP_ERR(req, 500, "Cannot open file in archive");
 | 
	
		
			
				|  |  | +	    goto out;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +	file_open = true;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	len = fileinfo.uncompressed_size;
 | 
	
		
			
				|  |  | +	if (unzReadCurrentFile(unz, buffer, len) != len) {
 | 
	
		
			
				|  |  | +	    err = ESP_ERR_HTTPD_RESULT_TRUNC;
 | 
	
		
			
				|  |  | +	    goto out;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	MSG("redirect: %.*s\n", (int)len, buffer);
 | 
	
		
			
				|  |  | +	
 | 
	
		
			
				|  |  | +	err = httpd_send_plain(req, 302, buffer, len, 0, 0);
 | 
	
		
			
				|  |  | +	goto out;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  |      /*
 | 
	
		
			
				|  |  |       * Hopefully the combination of date and CRC
 | 
	
		
			
				|  |  |       * is strong enough to quality for a "strong" ETag
 | 
	
	
		
			
				|  | @@ -627,7 +662,7 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      if (unzOpenCurrentFile(unz) != UNZ_OK) {
 | 
	
		
			
				|  |  | -	err = HTTP_ERR(req, 500, "Cannot open file in archive");
 | 
	
		
			
				|  |  | +	err = HTTP_ERR(req, 400, "Cannot open file in archive");
 | 
	
		
			
				|  |  |  	goto out;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      file_open = true;
 |