diff --git a/src/apps/httpd/fs.c b/src/apps/httpd/fs.c index 3c983936..35b5e310 100644 --- a/src/apps/httpd/fs.c +++ b/src/apps/httpd/fs.c @@ -36,6 +36,7 @@ #include "fsdata.h" #include + #if HTTPD_USE_CUSTOM_FSDATA #include "fsdata_custom.c" #else /* HTTPD_USE_CUSTOM_FSDATA */ @@ -50,6 +51,9 @@ void fs_close_custom(struct fs_file *file); #if LWIP_HTTPD_FS_ASYNC_READ u8_t fs_canread_custom(struct fs_file *file); u8_t fs_wait_read_custom(struct fs_file *file, fs_wait_cb callback_fn, void *callback_arg); +int fs_read_async_custom(struct fs_file *file, char *buffer, int count, fs_wait_cb callback_fn, void *callback_arg); +#else /* LWIP_HTTPD_FS_ASYNC_READ */ +int fs_read_custom(struct fs_file *file, char *buffer, int count); #endif /* LWIP_HTTPD_FS_ASYNC_READ */ #endif /* LWIP_HTTPD_CUSTOM_FILES */ @@ -77,7 +81,7 @@ fs_open(struct fs_file *file, const char *name) file->len = f->len; file->index = f->len; file->pextension = NULL; - file->http_header_included = f->http_header_included; + file->flags = f->flags; #if HTTPD_PRECALCULATED_CHECKSUM file->chksum_count = f->chksum_count; file->chksum = f->chksum; @@ -117,22 +121,22 @@ fs_read(struct fs_file *file, char *buffer, int count) #endif /* LWIP_HTTPD_FS_ASYNC_READ */ { int read; - if(file->index == file->len) { return FS_READ_EOF; } #if LWIP_HTTPD_FS_ASYNC_READ -#if LWIP_HTTPD_CUSTOM_FILES - if (!fs_canread_custom(file)) { - if (fs_wait_read_custom(file, callback_fn, callback_arg)) { - return FS_READ_DELAYED; - } - } -#else /* LWIP_HTTPD_CUSTOM_FILES */ LWIP_UNUSED_ARG(callback_fn); LWIP_UNUSED_ARG(callback_arg); -#endif /* LWIP_HTTPD_CUSTOM_FILES */ #endif /* LWIP_HTTPD_FS_ASYNC_READ */ +#if LWIP_HTTPD_CUSTOM_FILES + if (file->is_custom_file) { +#if LWIP_HTTPD_FS_ASYNC_READ + return fs_read_async_custom(file, buffer, count, callback_fn, callback_arg); +#else /* LWIP_HTTPD_FS_ASYNC_READ */ + return fs_read_custom(file, buffer, count); +#endif /* LWIP_HTTPD_FS_ASYNC_READ */ + } +#endif /* LWIP_HTTPD_CUSTOM_FILES */ read = file->len - file->index; if(read > count) { diff --git a/src/apps/httpd/fsdata.h b/src/apps/httpd/fsdata.h index b315b3fa..ac4548c7 100644 --- a/src/apps/httpd/fsdata.h +++ b/src/apps/httpd/fsdata.h @@ -40,7 +40,7 @@ struct fsdata_file { const unsigned char *name; const unsigned char *data; int len; - u8_t http_header_included; + u8_t flags; #if HTTPD_PRECALCULATED_CHECKSUM u16_t chksum_count; const struct fsdata_chksum *chksum; diff --git a/src/apps/httpd/httpd.c b/src/apps/httpd/httpd.c index 832dd18f..c2825145 100644 --- a/src/apps/httpd/httpd.c +++ b/src/apps/httpd/httpd.c @@ -88,6 +88,7 @@ #include #include +#include #if LWIP_TCP @@ -95,13 +96,10 @@ #define MIN_REQ_LEN 7 #define CRLF "\r\n" -#define HTTP11_CONNECTIONKEEPALIVE "Connection: keep-alive" - -#if LWIP_HTTPD_SSI -#define LWIP_HTTPD_IS_SSI(hs) ((hs)->ssi) -#else /* LWIP_HTTPD_SSI */ -#define LWIP_HTTPD_IS_SSI(hs) 0 -#endif /* LWIP_HTTPD_SSI */ +#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE +#define HTTP11_CONNECTIONKEEPALIVE "Connection: keep-alive" +#define HTTP11_CONNECTIONKEEPALIVE2 "Connection: Keep-Alive" +#endif /** These defines check whether tcp_write has to copy data or not */ @@ -114,7 +112,7 @@ #else /* LWIP_HTTPD_SSI */ /** Default: don't copy if the data is sent from file-system directly */ #define HTTP_IS_DATA_VOLATILE(hs) (((hs->file != NULL) && (hs->handle != NULL) && (hs->file == \ - (const char*)hs->handle->data + hs->handle->len - hs->left)) \ + (char*)hs->handle->data + hs->handle->len - hs->left)) \ ? 0 : TCP_WRITE_FLAG_COPY) #endif /* LWIP_HTTPD_SSI */ #endif @@ -152,15 +150,37 @@ static char httpd_req_buf[LWIP_HTTPD_MAX_REQ_LENGTH+1]; #endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */ #if LWIP_HTTPD_SUPPORT_POST -/** Filename for response file to send when POST is finished */ -static char http_post_response_filename[LWIP_HTTPD_POST_MAX_RESPONSE_URI_LEN+1]; -#endif /* LWIP_HTTPD_SUPPORT_POST */ +#if LWIP_HTTPD_POST_MAX_RESPONSE_URI_LEN > LWIP_HTTPD_MAX_REQUEST_URI_LEN +#define LWIP_HTTPD_URI_BUF_LEN LWIP_HTTPD_POST_MAX_RESPONSE_URI_LEN +#endif +#endif +#ifndef LWIP_HTTPD_URI_BUF_LEN +#define LWIP_HTTPD_URI_BUF_LEN LWIP_HTTPD_MAX_REQUEST_URI_LEN +#endif +#if LWIP_HTTPD_URI_BUF_LEN +/* Filename for response file to send when POST is finished or + * search for default files when a directory is requested. */ +static char http_uri_buf[LWIP_HTTPD_URI_BUF_LEN+1]; +#endif #if LWIP_HTTPD_DYNAMIC_HEADERS /* The number of individual strings that comprise the headers sent before each * requested file. */ -#define NUM_FILE_HDR_STRINGS 3 +#define NUM_FILE_HDR_STRINGS 5 +#define HDR_STRINGS_IDX_HTTP_STATUS 0 /* e.g. "HTTP/1.0 200 OK\r\n" */ +#define HDR_STRINGS_IDX_SERVER_NAME 1 /* e.g. "Server: "HTTPD_SERVER_AGENT"\r\n" */ +#define HDR_STRINGS_IDX_CONTENT_LEN_KEPALIVE 2 /* e.g. "Content-Length: xy\r\n" and/or "Connection: keep-alive\r\n" */ +#define HDR_STRINGS_IDX_CONTENT_LEN_NR 3 /* the byte count, when content-length is used */ +#define HDR_STRINGS_IDX_CONTENT_TYPE 4 /* the content type (or default answer content type including default document) */ + +/* The dynamically generated Content-Length buffer needs space for CRLF + NULL */ +#define LWIP_HTTPD_MAX_CONTENT_LEN_OFFSET 3 +#ifndef LWIP_HTTPD_MAX_CONTENT_LEN_SIZE +/* The dynamically generated Content-Length buffer shall be able to work with + ~953 MB (9 digits) */ +#define LWIP_HTTPD_MAX_CONTENT_LEN_SIZE (9 + LWIP_HTTPD_MAX_CONTENT_LEN_OFFSET) +#endif #endif /* LWIP_HTTPD_DYNAMIC_HEADERS */ #if LWIP_HTTPD_SSI @@ -200,7 +220,7 @@ struct http_state { #endif /* LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED */ struct fs_file file_handle; struct fs_file *handle; - const char *file; /* Pointer to first unsent byte in buf. */ + const char *file; /* Pointer to first unsent byte in buf. */ struct tcp_pcb *pcb; #if LWIP_HTTPD_SUPPORT_REQUESTLIST @@ -225,6 +245,7 @@ struct http_state { #endif /* LWIP_HTTPD_CGI */ #if LWIP_HTTPD_DYNAMIC_HEADERS const char *hdrs[NUM_FILE_HDR_STRINGS]; /* HTTP headers to be sent. */ + char hdr_content_len[LWIP_HTTPD_MAX_CONTENT_LEN_SIZE]; u16_t hdr_pos; /* The position of the first unsent header byte in the current string */ u16_t hdr_index; /* The index of the hdr string currently being sent. */ @@ -243,28 +264,27 @@ struct http_state { }; #if HTTPD_USE_MEM_POOL -LWIP_MEMPOOL_DECLARE(HTTPD_STATE, 20, sizeof(struct http_state), "HTTPD_STATE") +LWIP_MEMPOOL_DECLARE(HTTPD_STATE, MEMP_NUM_PARALLEL_HTTPD_CONNS, sizeof(struct http_state), "HTTPD_STATE") +#if LWIP_HTTPD_SSI +LWIP_MEMPOOL_DECLARE(HTTPD_SSI_STATE, MEMP_NUM_PARALLEL_HTTPD_SSI_CONNS, sizeof(struct http_ssi_state), "HTTPD_SSI_STATE") +#define HTTP_FREE_SSI_STATE(x) LWIP_MEMPOOL_FREE(HTTPD_SSI_STATE, (x)) +#define HTTP_ALLOC_SSI_STATE() (struct http_ssi_state *)LWIP_MEMPOOL_ALLOC(HTTPD_SSI_STATE) +#endif /* LWIP_HTTPD_SSI */ #define HTTP_ALLOC_HTTP_STATE() (struct http_state *)LWIP_MEMPOOL_ALLOC(HTTPD_STATE) #define HTTP_FREE_HTTP_STATE(x) LWIP_MEMPOOL_FREE(HTTPD_STATE, (x)) - -#if LWIP_HTTPD_SSI -LWIP_MEMPOOL_DECLARE(HTTPD_SSI_STATE, 20, sizeof(struct http_ssi_state), "HTTPD_SSI_STATE") -#define HTTP_ALLOC_SSI_STATE() (struct http_ssi_state *)LWIP_MEMPOOL_ALLOC(HTTPD_SSI_STATE) -#define HTTP_FREE_SSI_STATE(x) LWIP_MEMPOOL_FREE(HTTPD_SSI_STATE, (x)) -#endif /* LWIP_HTTPD_SSI */ #else /* HTTPD_USE_MEM_POOL */ #define HTTP_ALLOC_HTTP_STATE() (struct http_state *)mem_malloc(sizeof(struct http_state)) #define HTTP_FREE_HTTP_STATE(x) mem_free(x) #if LWIP_HTTPD_SSI #define HTTP_ALLOC_SSI_STATE() (struct http_ssi_state *)mem_malloc(sizeof(struct http_ssi_state)) -#define HTTP_FREE_SSI_STATE(x) mem_free(x) +#define HTTP_FREE_SSI_STATE(x) mem_free(x) #endif /* LWIP_HTTPD_SSI */ #endif /* HTTPD_USE_MEM_POOL */ static err_t http_close_conn(struct tcp_pcb *pcb, struct http_state *hs); static err_t http_close_or_abort_conn(struct tcp_pcb *pcb, struct http_state *hs, u8_t abort_conn); static err_t http_find_file(struct http_state *hs, const char *uri, int is_09); -static err_t http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const char *uri, u8_t tag_check); +static err_t http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const char *uri, u8_t tag_check, char* params); static err_t http_poll(void *arg, struct tcp_pcb *pcb); static u8_t http_check_eof(struct tcp_pcb *pcb, struct http_state *hs); #if LWIP_HTTPD_FS_ASYNC_READ @@ -273,9 +293,11 @@ static void http_continue(void *connection); #if LWIP_HTTPD_SSI /* SSI insert handler function pointer. */ -tSSIHandler g_pfnSSIHandler = NULL; -int g_iNumTags = 0; -const char **g_ppcTags = NULL; +tSSIHandler g_pfnSSIHandler; +#if !LWIP_HTTPD_SSI_RAW +int g_iNumTags; +const char **g_ppcTags; +#endif /* !LWIP_HTTPD_SSI_RAW */ #define LEN_TAG_LEAD_IN 5 const char * const g_pcTagLeadIn = ""; /* CGI handler information */ const tCGI *g_pCGIs; int g_iNumCGIs; +int http_cgi_paramcount; +#define http_cgi_params hs->params +#define http_cgi_param_vals hs->param_vals +#elif LWIP_HTTPD_CGI_SSI +char *http_cgi_params[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Params extracted from the request URI */ +char *http_cgi_param_vals[LWIP_HTTPD_MAX_CGI_PARAMETERS]; /* Values for each extracted param */ #endif /* LWIP_HTTPD_CGI */ #if LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED @@ -304,17 +332,74 @@ strnstr(const char* buffer, const char* token, size_t n) const char* p; int tokenlen = (int)strlen(token); if (tokenlen == 0) { - return (char *)(size_t)buffer; /* cast to size_t is a hack to cast away constness */ + return (char *)buffer; } for (p = buffer; *p && (p + tokenlen <= buffer + n); p++) { if ((*p == *token) && (strncmp(p, token, tokenlen) == 0)) { - return (char *)(size_t)p; /* cast to size_t is a hack to cast away constness */ + return (char *)p; } } return NULL; } #endif /* LWIP_HTTPD_STRNSTR_PRIVATE */ +#if LWIP_HTTPD_STRICMP_PRIVATE +static int +stricmp(const char* str1, const char* str2) +{ + char c1, c2; + + do { + c1 = *str1++; + c2 = *str2++; + if (c1 != c2) { + char c1_upc = c1 | 0x20; + if ((c1_upc >= 'a') && (c1_upc <= 'z')) { + /* characters are not equal an one is in the alphabet range: + downcase both chars and check again */ + char c2_upc = c2 | 0x20; + if (c1_upc != c2_upc) { + /* still not equal */ + /* don't care for < or > */ + return 1; + } + } else { + /* characters are not equal but none is in the alphabet range */ + return 1; + } + } + } while (c1 != 0); + return 0; +} +#endif /* LWIP_HTTPD_STRICMP_PRIVATE */ + +#if LWIP_HTTPD_ITOA_PRIVATE +static void +httpd_itoa(int value, char* result) +{ + const int base = 10; + char* ptr = result, *ptr1 = result, tmp_char; + int tmp_value; + + do { + tmp_value = value; + value /= base; + *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + (tmp_value - value * base)]; + } while(value); + + /* Apply negative sign */ + if (tmp_value < 0) { + *ptr++ = '-'; + } + *ptr-- = '\0'; + while(ptr1 < ptr) { + tmp_char = *ptr; + *ptr--= *ptr1; + *ptr1++ = tmp_char; + } +} +#endif + #if LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED static void http_kill_oldest_connection(u8_t ssi_required) @@ -334,6 +419,7 @@ http_kill_oldest_connection(u8_t ssi_required) { hs_free_next = hs; } + LWIP_ASSERT("broken list", hs != hs->next); hs = hs->next; } if (hs_free_next != NULL) { @@ -533,6 +619,13 @@ http_write(struct tcp_pcb *pcb, const void* ptr, u16_t *length, u8_t apiflags) *length = 0; } +#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE + /* ensure nagle is normally enabled (only disabled for persistent connections + when all data has been enqueued but the connection stays open for the next + request */ + tcp_nagle_enable(pcb); +#endif + return err; } @@ -557,8 +650,8 @@ http_close_or_abort_conn(struct tcp_pcb *pcb, struct http_state *hs, u8_t abort_ #endif /* LWIP_HTTPD_POST_MANUAL_WND */ ) { /* make sure the post code knows that the connection is closed */ - http_post_response_filename[0] = 0; - httpd_post_finished(hs, http_post_response_filename, LWIP_HTTPD_POST_MAX_RESPONSE_URI_LEN); + http_uri_buf[0] = 0; + httpd_post_finished(hs, http_uri_buf, LWIP_HTTPD_URI_BUF_LEN); } } #endif /* LWIP_HTTPD_SUPPORT_POST*/ @@ -607,7 +700,7 @@ http_eof(struct tcp_pcb *pcb, struct http_state *hs) { /* HTTP/1.1 persistent connection? (Not supported for SSI) */ #if LWIP_HTTPD_SUPPORT_11_KEEPALIVE - if (hs->keepalive && !LWIP_HTTPD_IS_SSI(hs)) { + if (hs->keepalive) { #if LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED struct http_state* next = hs->next; #endif @@ -619,6 +712,8 @@ http_eof(struct tcp_pcb *pcb, struct http_state *hs) #endif hs->pcb = pcb; hs->keepalive = 1; + /* ensure nagle doesn't interfere with sending all data as fast as possible: */ + tcp_nagle_disable(pcb); } else #endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */ { @@ -626,7 +721,7 @@ http_eof(struct tcp_pcb *pcb, struct http_state *hs) } } -#if LWIP_HTTPD_CGI +#if LWIP_HTTPD_CGI || LWIP_HTTPD_CGI_SSI /** * Extract URI parameters from the parameter-part of an URI in the form * "test.cgi?x=y" @todo: better explanation! @@ -643,6 +738,8 @@ extract_uri_parameters(struct http_state *hs, char *params) char *equals; int loop; + LWIP_UNUSED_ARG(hs); + /* If we have no parameters at all, return immediately. */ if(!params || (params[0] == '\0')) { return(0); @@ -656,7 +753,7 @@ extract_uri_parameters(struct http_state *hs, char *params) for(loop = 0; (loop < LWIP_HTTPD_MAX_CGI_PARAMETERS) && pair; loop++) { /* Save the name of the parameter */ - hs->params[loop] = pair; + http_cgi_params[loop] = pair; /* Remember the start of this name=value pair */ equals = pair; @@ -684,15 +781,15 @@ extract_uri_parameters(struct http_state *hs, char *params) equals = strchr(equals, '='); if(equals) { *equals = '\0'; - hs->param_vals[loop] = equals + 1; + http_cgi_param_vals[loop] = equals + 1; } else { - hs->param_vals[loop] = NULL; + http_cgi_param_vals[loop] = NULL; } } return loop; } -#endif /* LWIP_HTTPD_CGI */ +#endif /* LWIP_HTTPD_CGI || LWIP_HTTPD_CGI_SSI */ #if LWIP_HTTPD_SSI /** @@ -708,32 +805,57 @@ extract_uri_parameters(struct http_state *hs, char *params) static void get_tag_insert(struct http_state *hs) { - int loop; +#if LWIP_HTTPD_SSI_RAW + const char* tag; +#else /* LWIP_HTTPD_SSI_RAW */ + int tag; +#endif /* LWIP_HTTPD_SSI_RAW */ size_t len; struct http_ssi_state *ssi; +#if LWIP_HTTPD_SSI_MULTIPART + u16_t current_tag_part; +#endif /* LWIP_HTTPD_SSI_MULTIPART */ + LWIP_ASSERT("hs != NULL", hs != NULL); ssi = hs->ssi; LWIP_ASSERT("ssi != NULL", ssi != NULL); #if LWIP_HTTPD_SSI_MULTIPART - u16_t current_tag_part = ssi->tag_part; + current_tag_part = ssi->tag_part; ssi->tag_part = HTTPD_LAST_TAG_PART; #endif /* LWIP_HTTPD_SSI_MULTIPART */ +#if LWIP_HTTPD_SSI_RAW + tag = ssi->tag_name; +#endif - if(g_pfnSSIHandler && g_ppcTags && g_iNumTags) { + if(g_pfnSSIHandler +#if !LWIP_HTTPD_SSI_RAW + && g_ppcTags && g_iNumTags +#endif /* !LWIP_HTTPD_SSI_RAW */ + ) { /* Find this tag in the list we have been provided. */ - for(loop = 0; loop < g_iNumTags; loop++) { - if(strcmp(ssi->tag_name, g_ppcTags[loop]) == 0) { - ssi->tag_insert_len = g_pfnSSIHandler(loop, ssi->tag_insert, +#if LWIP_HTTPD_SSI_RAW + { +#else /* LWIP_HTTPD_SSI_RAW */ + for(tag = 0; tag < g_iNumTags; tag++) { + if(strcmp(ssi->tag_name, g_ppcTags[tag]) == 0) +#endif /* LWIP_HTTPD_SSI_RAW */ + { + ssi->tag_insert_len = g_pfnSSIHandler(tag, ssi->tag_insert, LWIP_HTTPD_MAX_TAG_INSERT_LEN #if LWIP_HTTPD_SSI_MULTIPART , current_tag_part, &ssi->tag_part #endif /* LWIP_HTTPD_SSI_MULTIPART */ #if LWIP_HTTPD_FILE_STATE - , hs->handle->state + , (hs->handle ? hs->handle->state : NULL) #endif /* LWIP_HTTPD_FILE_STATE */ ); - return; +#if LWIP_HTTPD_SSI_RAW + if (ssi->tag_insert_len != HTTPD_SSI_TAG_UNKNOWN) +#endif /* LWIP_HTTPD_SSI_RAW */ + { + return; + } } } } @@ -764,94 +886,143 @@ get_tag_insert(struct http_state *hs) * them into the supplied buffer. */ static void -get_http_headers(struct http_state *pState, char *pszURI) +get_http_headers(struct http_state *hs, const char *uri) { - unsigned int iLoop; - char *pszWork; - char *pszExt; - char *pszVars; - - /* Ensure that we initialize the loop counter. */ - iLoop = 0; + size_t content_type; + char *tmp; + char *ext; + char *vars; + u8_t add_content_len; /* In all cases, the second header we send is the server identification so set it here. */ - pState->hdrs[1] = g_psHTTPHeaderStrings[HTTP_HDR_SERVER]; + hs->hdrs[HDR_STRINGS_IDX_SERVER_NAME] = g_psHTTPHeaderStrings[HTTP_HDR_SERVER]; + hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEPALIVE] = NULL; + hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_NR] = NULL; /* Is this a normal file or the special case we use to send back the default "404: Page not found" response? */ - if (pszURI == NULL) { - pState->hdrs[0] = g_psHTTPHeaderStrings[HTTP_HDR_NOT_FOUND]; - pState->hdrs[2] = g_psHTTPHeaderStrings[DEFAULT_404_HTML]; + if (uri == NULL) { + hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_NOT_FOUND]; +#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE + if (hs->keepalive) { + hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = g_psHTTPHeaderStrings[DEFAULT_404_HTML_PERSISTENT]; + } else +#endif + { + hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = g_psHTTPHeaderStrings[DEFAULT_404_HTML]; + } /* Set up to send the first header string. */ - pState->hdr_index = 0; - pState->hdr_pos = 0; + hs->hdr_index = 0; + hs->hdr_pos = 0; return; + } + /* We are dealing with a particular filename. Look for one other + special case. We assume that any filename with "404" in it must be + indicative of a 404 server error whereas all other files require + the 200 OK header. */ + if (strstr(uri, "404")) { + hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_NOT_FOUND]; + } else if (strstr(uri, "400")) { + hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_BAD_REQUEST]; + } else if (strstr(uri, "501")) { + hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_NOT_IMPL]; } else { - /* We are dealing with a particular filename. Look for one other - special case. We assume that any filename with "404" in it must be - indicative of a 404 server error whereas all other files require - the 200 OK header. */ - if (strstr(pszURI, "404")) { - pState->hdrs[0] = g_psHTTPHeaderStrings[HTTP_HDR_NOT_FOUND]; - } else if (strstr(pszURI, "400")) { - pState->hdrs[0] = g_psHTTPHeaderStrings[HTTP_HDR_BAD_REQUEST]; - } else if (strstr(pszURI, "501")) { - pState->hdrs[0] = g_psHTTPHeaderStrings[HTTP_HDR_NOT_IMPL]; - } else { - pState->hdrs[0] = g_psHTTPHeaderStrings[HTTP_HDR_OK]; - } + hs->hdrs[HDR_STRINGS_IDX_HTTP_STATUS] = g_psHTTPHeaderStrings[HTTP_HDR_OK]; + } - /* Determine if the URI has any variables and, if so, temporarily remove - them. */ - pszVars = strchr(pszURI, '?'); - if(pszVars) { - *pszVars = '\0'; - } - - /* Get a pointer to the file extension. We find this by looking for the - last occurrence of "." in the filename passed. */ - pszExt = NULL; - pszWork = strchr(pszURI, '.'); - while(pszWork) { - pszExt = pszWork + 1; - pszWork = strchr(pszExt, '.'); - } + /* Determine if the URI has any variables and, if so, temporarily remove + them. */ + vars = strchr(uri, '?'); + if(vars) { + *vars = '\0'; + } + /* Get a pointer to the file extension. We find this by looking for the + last occurrence of "." in the filename passed. */ + ext = NULL; + tmp = strchr(uri, '.'); + while (tmp) { + ext = tmp + 1; + tmp = strchr(ext, '.'); + } + if (ext != NULL) { /* Now determine the content type and add the relevant header for that. */ - for(iLoop = 0; (iLoop < NUM_HTTP_HEADERS) && pszExt; iLoop++) { + for (content_type = 0; content_type < NUM_HTTP_HEADERS; content_type++) { /* Have we found a matching extension? */ - if(!strcmp(g_psHTTPHeaders[iLoop].extension, pszExt)) { - pState->hdrs[2] = g_psHTTPHeaders[iLoop].content_type; + if(!stricmp(g_psHTTPHeaders[content_type].extension, ext)) { break; } } - - /* Reinstate the parameter marker if there was one in the original URI. */ - if(pszVars) { - *pszVars = '?'; - } + } else { + content_type = NUM_HTTP_HEADERS; } + /* Reinstate the parameter marker if there was one in the original URI. */ + if (vars) { + *vars = '?'; + } + +#if LWIP_HTTPD_OMIT_HEADER_FOR_EXTENSIONLESS_URI /* Does the URL passed have any file extension? If not, we assume it is a special-case URL used for control state notification and we do not send any HTTP headers with the response. */ - if(!pszExt) { + if (!ext) { /* Force the header index to a value indicating that all headers have already been sent. */ - pState->hdr_index = NUM_FILE_HDR_STRINGS; - } else { - /* Did we find a matching extension? */ - if(iLoop == NUM_HTTP_HEADERS) { - /* No - use the default, plain text file type. */ - pState->hdrs[2] = HTTP_HDR_DEFAULT_TYPE; - } - - /* Set up to send the first header string. */ - pState->hdr_index = 0; - pState->hdr_pos = 0; + hs->hdr_index = NUM_FILE_HDR_STRINGS; + return; } +#endif /* LWIP_HTTPD_OMIT_HEADER_FOR_EXTENSIONLESS_URI */ + add_content_len = 1; + /* Did we find a matching extension? */ + if(content_type < NUM_HTTP_HEADERS) { + /* yes, store it */ + hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = g_psHTTPHeaders[content_type].content_type; + } else if (!ext) { + /* no, no extension found -> use binary transfer to prevent the browser adding '.txt' on save */ + hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = HTTP_HDR_APP; + } else { + /* No - use the default, plain text file type. */ + hs->hdrs[HDR_STRINGS_IDX_CONTENT_TYPE] = HTTP_HDR_DEFAULT_TYPE; + } + /* Add content-length header? */ +#if LWIP_HTTPD_SSI + if (hs->ssi != NULL) { + add_content_len = 0; /* @todo: get maximum file length from SSI */ + } else +#endif /* LWIP_HTTPD_SSI */ + if ((hs->handle == NULL) || ((hs->handle->flags & FS_FILE_FLAGS_HEADER_PERSISTENT) == 0)) { + add_content_len = 0; + } + if (add_content_len) { + size_t len; + LWIP_HTTPD_ITOA(hs->hdr_content_len, (size_t)LWIP_HTTPD_MAX_CONTENT_LEN_SIZE, + hs->handle->len); + len = strlen(hs->hdr_content_len); + if (len <= LWIP_HTTPD_MAX_CONTENT_LEN_SIZE - LWIP_HTTPD_MAX_CONTENT_LEN_OFFSET) { + SMEMCPY(&hs->hdr_content_len[len], CRLF "\0", 3); + hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_NR] = hs->hdr_content_len; + } else { + add_content_len = 0; + } + } +#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE + if (add_content_len) { + hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEPALIVE] = g_psHTTPHeaderStrings[HTTP_HDR_KEEPALIVE_LEN]; + } else { + hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEPALIVE] = g_psHTTPHeaderStrings[HTTP_HDR_CONN_CLOSE]; + } +#else /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */ + if (add_content_len) { + hs->hdrs[HDR_STRINGS_IDX_CONTENT_LEN_KEPALIVE] = g_psHTTPHeaderStrings[HTTP_HDR_CONTENT_LENGTH]; + } +#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */ + + /* Set up to send the first header string. */ + hs->hdr_index = 0; + hs->hdr_pos = 0; } /** Sub-function of http_send(): send dynamic headers @@ -888,6 +1059,10 @@ http_send_headers(struct tcp_pcb *pcb, struct http_state *hs) ptr = (const void *)(hs->hdrs[hs->hdr_index] + hs->hdr_pos); old_sendlen = sendlen; apiflags = HTTP_IS_HDR_VOLATILE(hs, ptr); + if (hs->hdr_index == HDR_STRINGS_IDX_CONTENT_LEN_NR) { + /* content-length is always volatile */ + apiflags |= TCP_WRITE_FLAG_COPY; + } if (hs->hdr_index < NUM_FILE_HDR_STRINGS - 1) { apiflags |= TCP_WRITE_FLAG_MORE; } @@ -908,6 +1083,11 @@ http_send_headers(struct tcp_pcb *pcb, struct http_state *hs) if(hs->hdr_pos == hdrlen) { /* Yes - move on to the next one */ hs->hdr_index++; + /* skip headers that are NULL (not all headers are required) */ + while ((hs->hdr_index < NUM_FILE_HDR_STRINGS) && + (hs->hdrs[hs->hdr_index] == NULL)) { + hs->hdr_index++; + } hs->hdr_pos = 0; } } @@ -918,7 +1098,7 @@ http_send_headers(struct tcp_pcb *pcb, struct http_state *hs) * (which would happen when sending files from async read). */ if(http_check_eof(pcb, hs)) { data_to_send = HTTP_DATA_TO_SEND_CONTINUE; - } + } } /* If we get here and there are still header bytes to send, we send * the header information we just wrote immediately. If there are no @@ -966,7 +1146,7 @@ http_check_eof(struct tcp_pcb *pcb, struct http_state *hs) /* Do we already have a send buffer allocated? */ if(hs->buf) { /* Yes - get the length of the buffer */ - count = hs->buf_len; + count = LWIP_MIN(hs->buf_len, bytes_left); } else { /* We don't have a send buffer so allocate one now */ count = tcp_sndbuf(pcb); @@ -1010,7 +1190,7 @@ http_check_eof(struct tcp_pcb *pcb, struct http_state *hs) return 0; } /* We reached the end of the file so this request is done. - * @todo: don't close here for HTTP/1.1? */ + * @todo: close here for HTTP/1.1 when reading file fails */ LWIP_DEBUGF(HTTPD_DEBUG, ("End of file.\n")); http_eof(pcb, hs); return 0; @@ -1358,11 +1538,8 @@ http_send_data_ssi(struct tcp_pcb *pcb, struct http_state *hs) #endif /* !LWIP_HTTPD_SSI_INCLUDE_TAG*/ } } - break; + break; } - default: - LWIP_ASSERT("Unknown state", 0); - break; } } @@ -1418,9 +1595,10 @@ http_send(struct tcp_pcb *pcb, struct http_state *hs) #if LWIP_HTTPD_DYNAMIC_HEADERS /* Do we have any more header data to send for this file? */ - if(hs->hdr_index < NUM_FILE_HDR_STRINGS) { + if (hs->hdr_index < NUM_FILE_HDR_STRINGS) { data_to_send = http_send_headers(pcb, hs); - if (data_to_send != HTTP_DATA_TO_SEND_CONTINUE) { + if ((data_to_send != HTTP_DATA_TO_SEND_CONTINUE) && + (hs->hdr_index < NUM_FILE_HDR_STRINGS)) { return data_to_send; } } @@ -1490,7 +1668,7 @@ http_find_error_file(struct http_state *hs, u16_t error_nr) } } } - return http_init_file(hs, &hs->file_handle, 0, NULL, 0); + return http_init_file(hs, &hs->file_handle, 0, NULL, 0, NULL); } #else /* LWIP_HTTPD_SUPPORT_EXTSTATUS */ #define http_find_error_file(hs, error_nr) ERR_ARG @@ -1545,9 +1723,9 @@ http_handle_post_finished(struct http_state *hs) #endif /* LWIP_HTTPD_POST_MANUAL_WND */ /* application error or POST finished */ /* NULL-terminate the buffer */ - http_post_response_filename[0] = 0; - httpd_post_finished(hs, http_post_response_filename, LWIP_HTTPD_POST_MAX_RESPONSE_URI_LEN); - return http_find_file(hs, http_post_response_filename, 0); + http_uri_buf[0] = 0; + httpd_post_finished(hs, http_uri_buf, LWIP_HTTPD_URI_BUF_LEN); + return http_find_file(hs, http_uri_buf, 0); } /** Pass received POST body data to the application and correctly handle @@ -1564,11 +1742,13 @@ http_post_rxpbuf(struct http_state *hs, struct pbuf *p) { err_t err; - /* adjust remaining Content-Length */ - if (hs->post_content_len_left < p->tot_len) { - hs->post_content_len_left = 0; - } else { - hs->post_content_len_left -= p->tot_len; + if (p != NULL) { + /* adjust remaining Content-Length */ + if (hs->post_content_len_left < p->tot_len) { + hs->post_content_len_left = 0; + } else { + hs->post_content_len_left -= p->tot_len; + } } err = httpd_post_receive_data(hs, p); if (err != ERR_OK) { @@ -1620,17 +1800,25 @@ http_post_request(struct pbuf *inp, struct http_state *hs, char *scontent_len_end = strnstr(scontent_len + HTTP_HDR_CONTENT_LEN_LEN, CRLF, HTTP_HDR_CONTENT_LEN_DIGIT_MAX_LEN); if (scontent_len_end != NULL) { int content_len; - char *conten_len_num = scontent_len + HTTP_HDR_CONTENT_LEN_LEN; - content_len = atoi(conten_len_num); - if (content_len > 0) { + char *content_len_num = scontent_len + HTTP_HDR_CONTENT_LEN_LEN; + content_len = atoi(content_len_num); + if (content_len == 0) { + /* if atoi returns 0 on error, fix this */ + if ((content_len_num[0] != '0') || (content_len_num[1] != '\r')) { + content_len = -1; + } + } + if (content_len >= 0) { /* adjust length of HTTP header passed to application */ const char *hdr_start_after_uri = uri_end + 1; u16_t hdr_len = LWIP_MIN(data_len, crlfcrlf + 4 - data); u16_t hdr_data_len = LWIP_MIN(data_len, crlfcrlf + 4 - hdr_start_after_uri); u8_t post_auto_wnd = 1; - http_post_response_filename[0] = 0; + http_uri_buf[0] = 0; + /* trim http header */ + *crlfcrlf = 0; err = httpd_post_begin(hs, uri, hdr_start_after_uri, hdr_data_len, content_len, - http_post_response_filename, LWIP_HTTPD_POST_MAX_RESPONSE_URI_LEN, &post_auto_wnd); + http_uri_buf, LWIP_HTTPD_URI_BUF_LEN, &post_auto_wnd); if (err == ERR_OK) { /* try to pass in data of the first pbuf(s) */ struct pbuf *q = inp; @@ -1657,16 +1845,19 @@ http_post_request(struct pbuf *inp, struct http_state *hs, #endif /* LWIP_HTTPD_POST_MANUAL_WND */ pbuf_ref(q); return http_post_rxpbuf(hs, q); + } else if (hs->post_content_len_left == 0) { + q = pbuf_alloc(PBUF_RAW, 0, PBUF_REF); + return http_post_rxpbuf(hs, q); } else { return ERR_OK; } } else { /* return file passed from application */ - return http_find_file(hs, http_post_response_filename, 0); + return http_find_file(hs, http_uri_buf, 0); } } else { LWIP_DEBUGF(HTTPD_DEBUG, ("POST received invalid Content-Length: %s\n", - conten_len_num)); + content_len_num)); return ERR_ARG; } } @@ -1846,7 +2037,7 @@ http_parse_request(struct pbuf *inp, struct http_state *hs, struct tcp_pcb *pcb) return http_find_error_file(hs, 501); } /* if we come here, method is OK, parse URI */ - left_len = (u16_t)(data_len - ((sp1 +1) - data)); + left_len = data_len - ((sp1 +1) - data); sp2 = strnstr(sp1 + 1, " ", left_len); #if LWIP_HTTPD_SUPPORT_V09 if (sp2 == NULL) { @@ -1861,13 +2052,16 @@ http_parse_request(struct pbuf *inp, struct http_state *hs, struct tcp_pcb *pcb) #endif /* LWIP_HTTPD_SUPPORT_POST */ } #endif /* LWIP_HTTPD_SUPPORT_V09 */ - uri_len = (u16_t)(sp2 - (sp1 + 1)); + uri_len = sp2 - (sp1 + 1); if ((sp2 != 0) && (sp2 > sp1)) { /* wait for CRLFCRLF (indicating end of HTTP headers) before parsing anything */ if (strnstr(data, CRLF CRLF, data_len) != NULL) { char *uri = sp1 + 1; #if LWIP_HTTPD_SUPPORT_11_KEEPALIVE - if (!is_09 && strnstr(data, HTTP11_CONNECTIONKEEPALIVE, data_len)) { + /* This is HTTP/1.0 compatible: for strict 1.1, a connection + would always be persistent unless "close" was specified. */ + if (!is_09 && (strnstr(data, HTTP11_CONNECTIONKEEPALIVE, data_len) || + strnstr(data, HTTP11_CONNECTIONKEEPALIVE2, data_len))) { hs->keepalive = 1; } else { hs->keepalive = 0; @@ -1940,11 +2134,10 @@ http_find_file(struct http_state *hs, const char *uri, int is_09) { size_t loop; struct fs_file *file = NULL; - char *params; + char *params = NULL; err_t err; #if LWIP_HTTPD_CGI int i; - int count; #endif /* LWIP_HTTPD_CGI */ #if !LWIP_HTTPD_SSI const @@ -1952,15 +2145,41 @@ http_find_file(struct http_state *hs, const char *uri, int is_09) /* By default, assume we will not be processing server-side-includes tags */ u8_t tag_check = 0; - /* Have we been asked for the default root file? */ - if((uri[0] == '/') && (uri[1] == 0)) { + /* Have we been asked for the default file (in root or a directory) ? */ +#if LWIP_HTTPD_MAX_REQUEST_URI_LEN + size_t uri_len = strlen(uri); + if ((uri_len > 0) && (uri[uri_len-1] == '/') && + ((uri != http_uri_buf) || (uri_len == 1))) { + size_t copy_len = LWIP_MIN(sizeof(http_uri_buf) - 1, uri_len - 1); + if (copy_len > 0) { + MEMCPY(http_uri_buf, uri, copy_len); + http_uri_buf[copy_len] = 0; + } +#else /* LWIP_HTTPD_MAX_REQUEST_URI_LEN */ + if ((uri[0] == '/') && (uri[1] == 0)) { +#endif /* LWIP_HTTPD_MAX_REQUEST_URI_LEN */ /* Try each of the configured default filenames until we find one that exists. */ for (loop = 0; loop < NUM_DEFAULT_FILENAMES; loop++) { - LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Looking for %s...\n", g_psDefaultFilenames[loop].name)); - err = fs_open(&hs->file_handle, g_psDefaultFilenames[loop].name); - uri = g_psDefaultFilenames[loop].name; + const char* file_name; +#if LWIP_HTTPD_MAX_REQUEST_URI_LEN + if (copy_len > 0) { + size_t len_left = sizeof(http_uri_buf) - copy_len - 1; + if (len_left > 0) { + size_t name_len = strlen(g_psDefaultFilenames[loop].name); + size_t name_copy_len = LWIP_MIN(len_left, name_len); + MEMCPY(&http_uri_buf[copy_len], g_psDefaultFilenames[loop].name, name_copy_len); + } + file_name = http_uri_buf; + } else +#endif /* LWIP_HTTPD_MAX_REQUEST_URI_LEN */ + { + file_name = g_psDefaultFilenames[loop].name; + } + LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Looking for %s...\n", file_name)); + err = fs_open(&hs->file_handle, file_name); if(err == ERR_OK) { + uri = file_name; file = &hs->file_handle; LWIP_DEBUGF(HTTPD_DEBUG | LWIP_DBG_TRACE, ("Opened.\n")); #if LWIP_HTTPD_SSI @@ -1969,14 +2188,8 @@ http_find_file(struct http_state *hs, const char *uri, int is_09) break; } } - if (file == NULL) { - /* None of the default filenames exist so send back a 404 page */ - file = http_get_404_file(hs, &uri); -#if LWIP_HTTPD_SSI - tag_check = 0; -#endif /* LWIP_HTTPD_SSI */ - } - } else { + } + if (file == NULL) { /* No - we've been asked for a specific file. */ /* First, isolate the base URI (without any parameters) */ params = (char *)strchr(uri, '?'); @@ -1987,6 +2200,7 @@ http_find_file(struct http_state *hs, const char *uri, int is_09) } #if LWIP_HTTPD_CGI + http_cgi_paramcount = -1; /* Does the base URI we have isolated correspond to a CGI handler? */ if (g_iNumCGIs && g_pCGIs) { for (i = 0; i < g_iNumCGIs; i++) { @@ -1995,8 +2209,8 @@ http_find_file(struct http_state *hs, const char *uri, int is_09) * We found a CGI that handles this URI so extract the * parameters and call the handler. */ - count = extract_uri_parameters(hs, params); - uri = g_pCGIs[i].pfnCGIHandler(i, count, hs->params, + http_cgi_paramcount = extract_uri_parameters(hs, params); + uri = g_pCGIs[i].pfnCGIHandler(i, http_cgi_paramcount, hs->params, hs->param_vals); break; } @@ -2016,17 +2230,37 @@ http_find_file(struct http_state *hs, const char *uri, int is_09) if (file != NULL) { /* See if we have been asked for an shtml file and, if so, enable tag checking. */ + const char* ext = NULL, *sub; + char* param = (char*)strstr(uri, "?"); + if (param != NULL) { + /* separate uri from parameters for now, set back later */ + *param = 0; + } + sub = uri; + ext = uri; + for (sub = strstr(sub, "."); sub != NULL; sub = strstr(sub, ".")) + { + ext = sub; + sub++; + } tag_check = 0; for (loop = 0; loop < NUM_SHTML_EXTENSIONS; loop++) { - if (strstr(uri, g_pcSSIExtensions[loop])) { + if (!stricmp(ext, g_pcSSIExtensions[loop])) { tag_check = 1; break; } } + if (param != NULL) { + *param = '?'; + } } #endif /* LWIP_HTTPD_SSI */ } - return http_init_file(hs, file, is_09, uri, tag_check); + if (file == NULL) { + /* None of the default filenames exist so send back a 404 page */ + file = http_get_404_file(hs, &uri); + } + return http_init_file(hs, file, is_09, uri, tag_check, params); } /** Initialize a http connection with a file to send (if found). @@ -2037,11 +2271,13 @@ http_find_file(struct http_state *hs, const char *uri, int is_09) * @param is_09 1 if the request is HTTP/0.9 (no HTTP headers in response) * @param uri the HTTP header URI * @param tag_check enable SSI tag checking + * @param uri_has_params != NULL if URI has parameters (separated by '?') * @return ERR_OK if file was found and hs has been initialized correctly * another err_t otherwise */ static err_t -http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const char *uri, u8_t tag_check) +http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const char *uri, + u8_t tag_check, char* params) { if (file != NULL) { /* file opened, initialise struct http_state */ @@ -2063,19 +2299,28 @@ http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const cha hs->handle = file; hs->file = file->data; LWIP_ASSERT("File length must be positive!", (file->len >= 0)); - hs->left = file->len; +#if LWIP_HTTPD_CUSTOM_FILES + if (file->is_custom_file && (file->data == NULL)) { + /* custom file, need to read data first (via fs_read_custom) */ + hs->left = 0; + } else +#endif /* LWIP_HTTPD_CUSTOM_FILES */ + { + hs->left = file->len; + } hs->retries = 0; #if LWIP_HTTPD_TIMING hs->time_started = sys_now(); #endif /* LWIP_HTTPD_TIMING */ #if !LWIP_HTTPD_DYNAMIC_HEADERS - LWIP_ASSERT("HTTP headers not included in file system", hs->handle->http_header_included); + LWIP_ASSERT("HTTP headers not included in file system", + (hs->handle->flags & FS_FILE_FLAGS_HEADER_INCLUDED) != 0); #endif /* !LWIP_HTTPD_DYNAMIC_HEADERS */ #if LWIP_HTTPD_SUPPORT_V09 - if (hs->handle->http_header_included && is_09) { + if (is_09 && ((hs->handle->flags & FS_FILE_FLAGS_HEADER_INCLUDED) != 0)) { /* HTTP/0.9 responses are sent without HTTP header, search for the end of the header. */ - const char *file_start = strnstr(hs->file, CRLF CRLF, hs->left); + char *file_start = strnstr(hs->file, CRLF CRLF, hs->left); if (file_start != NULL) { size_t diff = file_start + 4 - hs->file; hs->file += diff; @@ -2083,6 +2328,27 @@ http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const cha } } #endif /* LWIP_HTTPD_SUPPORT_V09*/ +#if LWIP_HTTPD_CGI_SSI + if (params != NULL) { + /* URI contains parameters, call generic CGI handler */ + int count; +#if LWIP_HTTPD_CGI + if (http_cgi_paramcount >= 0) { + count = http_cgi_paramcount; + } else +#endif + { + count = extract_uri_parameters(hs, params); + } + httpd_cgi_handler(uri, count, http_cgi_params, http_cgi_param_vals +#if defined(LWIP_HTTPD_FILE_STATE) && LWIP_HTTPD_FILE_STATE + , hs->handle->state +#endif /* LWIP_HTTPD_FILE_STATE */ + ); + } +#else /* LWIP_HTTPD_CGI_SSI */ + LWIP_UNUSED_ARG(params); +#endif /* LWIP_HTTPD_CGI_SSI */ } else { hs->handle = NULL; hs->file = NULL; @@ -2090,14 +2356,28 @@ http_init_file(struct http_state *hs, struct fs_file *file, int is_09, const cha hs->retries = 0; } #if LWIP_HTTPD_DYNAMIC_HEADERS - /* Determine the HTTP headers to send based on the file extension of + /* Determine the HTTP headers to send based on the file extension of * the requested URI. */ - if ((hs->handle == NULL) || !hs->handle->http_header_included) { - get_http_headers(hs, (char*)uri); + if ((hs->handle == NULL) || ((hs->handle->flags & FS_FILE_FLAGS_HEADER_INCLUDED) == 0)) { + get_http_headers(hs, uri); } #else /* LWIP_HTTPD_DYNAMIC_HEADERS */ LWIP_UNUSED_ARG(uri); #endif /* LWIP_HTTPD_DYNAMIC_HEADERS */ +#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE + if (hs->keepalive) { +#if LWIP_HTTPD_SSI + if (hs->ssi != NULL) { + hs->keepalive = 0; + } else +#endif /* LWIP_HTTPD_SSI */ + { + if ((hs->handle != NULL) && ((hs->handle->flags & FS_FILE_FLAGS_HEADER_PERSISTENT) == 0)) { + hs->keepalive = 0; + } + } + } +#endif /* LWIP_HTTPD_SUPPORT_11_KEEPALIVE */ return ERR_OK; } @@ -2286,10 +2566,10 @@ static err_t http_accept(void *arg, struct tcp_pcb *pcb, err_t err) { struct http_state *hs; - LWIP_UNUSED_ARG(arg); LWIP_UNUSED_ARG(err); + LWIP_UNUSED_ARG(arg); LWIP_DEBUGF(HTTPD_DEBUG, ("http_accept %p / %p\n", (void*)pcb, arg)); - + if ((err != ERR_OK) || (pcb == NULL)) { return ERR_VAL; } @@ -2328,15 +2608,16 @@ httpd_init(void) struct tcp_pcb *pcb; err_t err; - LWIP_DEBUGF(HTTPD_DEBUG, ("httpd_init\n")); - +#if MEMP_MEM_MALLOC || MEM_USE_POOLS || MEMP_USE_CUSTOM_POOLS #if HTTPD_USE_MEM_POOL LWIP_MEMPOOL_INIT(HTTPD_STATE); #if LWIP_HTTPD_SSI LWIP_MEMPOOL_INIT(HTTPD_SSI_STATE); -#endif /* LWIP_HTTPD_SSI */ -#endif /* HTTPD_USE_MEM_POOL */ - +#endif +#endif +#endif + LWIP_DEBUGF(HTTPD_DEBUG, ("httpd_init\n")); + pcb = tcp_new_ip_type(IPADDR_TYPE_ANY); LWIP_ASSERT("httpd_init: tcp_new failed", pcb != NULL); tcp_setprio(pcb, HTTPD_TCP_PRIO); @@ -2345,7 +2626,6 @@ httpd_init(void) LWIP_ASSERT("httpd_init: tcp_bind failed", err == ERR_OK); pcb = tcp_listen(pcb); LWIP_ASSERT("httpd_init: tcp_listen failed", pcb != NULL); - /* initialize accept callback */ tcp_accept(pcb, http_accept); } @@ -2363,12 +2643,18 @@ http_set_ssi_handler(tSSIHandler ssi_handler, const char **tags, int num_tags) LWIP_DEBUGF(HTTPD_DEBUG, ("http_set_ssi_handler\n")); LWIP_ASSERT("no ssi_handler given", ssi_handler != NULL); + g_pfnSSIHandler = ssi_handler; + +#if LWIP_HTTPD_SSI_RAW + LWIP_UNUSED_ARG(tags); + LWIP_UNUSED_ARG(num_tags); +#else /* LWIP_HTTPD_SSI_RAW */ LWIP_ASSERT("no tags given", tags != NULL); LWIP_ASSERT("invalid number of tags", num_tags > 0); - g_pfnSSIHandler = ssi_handler; g_ppcTags = tags; g_iNumTags = num_tags; +#endif /* !LWIP_HTTPD_SSI_RAW */ } #endif /* LWIP_HTTPD_SSI */ diff --git a/src/apps/httpd/httpd_structs.h b/src/apps/httpd/httpd_structs.h index 0c373032..fbd135a8 100644 --- a/src/apps/httpd/httpd_structs.h +++ b/src/apps/httpd/httpd_structs.h @@ -27,8 +27,12 @@ static const char * const g_psHTTPHeaderStrings[] = "Content-Length: ", "Connection: Close\r\n", "Connection: keep-alive\r\n", + "Connection: keep-alive\r\nContent-Length: ", "Server: "HTTPD_SERVER_AGENT"\r\n", "\r\n

404: The requested file cannot be found.

\r\n" +#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE + ,"Connection: keep-alive\r\nContent-Length: 77\r\n\r\n

404: The requested file cannot be found.

\r\n" +#endif }; /* Indexes into the g_psHTTPHeaderStrings array */ @@ -40,11 +44,15 @@ static const char * const g_psHTTPHeaderStrings[] = #define HTTP_HDR_NOT_FOUND_11 5 /* 404 File not found */ #define HTTP_HDR_BAD_REQUEST_11 6 /* 400 Bad request */ #define HTTP_HDR_NOT_IMPL_11 7 /* 501 Not Implemented */ -#define HTTP_HDR_CONTENT_LENGTH 8 /* Content-Length: (HTTP 1.1)*/ +#define HTTP_HDR_CONTENT_LENGTH 8 /* Content-Length: (HTTP 1.0)*/ #define HTTP_HDR_CONN_CLOSE 9 /* Connection: Close (HTTP 1.1) */ #define HTTP_HDR_CONN_KEEPALIVE 10 /* Connection: keep-alive (HTTP 1.1) */ -#define HTTP_HDR_SERVER 11 /* Server: HTTPD_SERVER_AGENT */ -#define DEFAULT_404_HTML 12 /* default 404 body */ +#define HTTP_HDR_KEEPALIVE_LEN 11 /* Connection: keep-alive + Content-Length: (HTTP 1.1)*/ +#define HTTP_HDR_SERVER 12 /* Server: HTTPD_SERVER_AGENT */ +#define DEFAULT_404_HTML 13 /* default 404 body */ +#if LWIP_HTTPD_SUPPORT_11_KEEPALIVE +#define DEFAULT_404_HTML_PERSISTENT 14 /* default 404 body, but including Connection: keep-alive */ +#endif #define HTTP_HDR_HTML "Content-type: text/html\r\n\r\n" @@ -61,6 +69,7 @@ static const char * const g_psHTTPHeaderStrings[] = #define HTTP_HDR_SWF "Content-type: application/x-shockwave-flash\r\n\r\n" #define HTTP_HDR_XML "Content-type: text/xml\r\n\r\n" #define HTTP_HDR_PDF "Content-type: application/pdf\r\n\r\n" +#define HTTP_HDR_JSON "Content-type: application/json\r\n\r\n" #define HTTP_HDR_DEFAULT_TYPE "Content-type: text/plain\r\n\r\n" @@ -87,7 +96,8 @@ static const tHTTPHeader g_psHTTPHeaders[] = { "swf", HTTP_HDR_SWF}, { "xml", HTTP_HDR_XML}, { "xsl", HTTP_HDR_XML}, - { "pdf", HTTP_HDR_PDF} + { "pdf", HTTP_HDR_PDF}, + { "json", HTTP_HDR_JSON} }; #define NUM_HTTP_HEADERS (sizeof(g_psHTTPHeaders) / sizeof(tHTTPHeader)) diff --git a/src/apps/httpd/makefsdata/makefsdata.c b/src/apps/httpd/makefsdata/makefsdata.c index 2b0e2238..d29a0475 100644 --- a/src/apps/httpd/makefsdata/makefsdata.c +++ b/src/apps/httpd/makefsdata/makefsdata.c @@ -21,6 +21,49 @@ #endif #include #include +#include +#include + +/** Makefsdata can generate *all* files deflate-compressed (where file size shrinks). + * Since nearly all browsers support this, this is a good way to reduce ROM size. + * To compress the files, "miniz.c" must be downloaded seperately. + */ +#ifndef MAKEFS_SUPPORT_DEFLATE +#define MAKEFS_SUPPORT_DEFLATE 0 +#endif + +#define COPY_BUFSIZE (1024*1024) /* 1 MByte */ + +#if MAKEFS_SUPPORT_DEFLATE +#include "../miniz.c" + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint; + +#define my_max(a,b) (((a) > (b)) ? (a) : (b)) +#define my_min(a,b) (((a) < (b)) ? (a) : (b)) + +/* COMP_OUT_BUF_SIZE is the size of the output buffer used during compression. + COMP_OUT_BUF_SIZE must be >= 1 and <= OUT_BUF_SIZE */ +#define COMP_OUT_BUF_SIZE COPY_BUFSIZE + +/* OUT_BUF_SIZE is the size of the output buffer used during decompression. + OUT_BUF_SIZE must be a power of 2 >= TINFL_LZ_DICT_SIZE (because the low-level decompressor not only writes, but reads from the output buffer as it decompresses) */ +#define OUT_BUF_SIZE COPY_BUFSIZE +static uint8 s_outbuf[OUT_BUF_SIZE]; +static uint8 s_checkbuf[OUT_BUF_SIZE]; + +/* tdefl_compressor contains all the state needed by the low-level compressor so it's a pretty big struct (~300k). + This example makes it a global vs. putting it on the stack, of course in real-world usage you'll probably malloc() or new it. */ +tdefl_compressor g_deflator; +tinfl_decompressor g_inflator; + +int deflate_level = 10; /* default compression level, can be changed via command line */ +#define USAGE_ARG_DEFLATE " [-defl]" +#else /* MAKEFS_SUPPORT_DEFLATE */ +#define USAGE_ARG_DEFLATE "" +#endif /* MAKEFS_SUPPORT_DEFLATE */ /* Compatibility defines Win32 vs. DOS */ #ifdef WIN32 @@ -66,6 +109,7 @@ #define LWIP_HTTPD_DYNAMIC_HEADERS 1 #define LWIP_HTTPD_SSI 1 #include "../httpd_structs.h" +#include "lwip/apps/fs.h" #include "../core/inet_chksum.c" #include "../core/def.c" @@ -85,8 +129,6 @@ static int payload_alingment_dummy_counter = 0; #define MAX_PATH_LEN 256 -#define COPY_BUFSIZE 10240 - struct file_entry { struct file_entry* next; @@ -95,14 +137,13 @@ struct file_entry int process_sub(FILE *data_file, FILE *struct_file); int process_file(FILE *data_file, FILE *struct_file, const char *filename); -int file_write_http_header(FILE *data_file, const char *filename, int file_size, - u16_t *http_hdr_len, u16_t *http_hdr_chksum); +int file_write_http_header(FILE *data_file, const char *filename, int file_size, u16_t *http_hdr_len, + u16_t *http_hdr_chksum, u8_t provide_content_len, int is_compressed); int file_put_ascii(FILE *file, const char *ascii_string, int len, int *i); int s_put_ascii(char *buf, const char *ascii_string, int len, int *i); void concat_files(const char *file1, const char *file2, const char *targetfile); int check_path(char* path, size_t size); -static unsigned char file_buffer_raw[COPY_BUFSIZE]; /* 5 bytes per char + 3 bytes per line */ static char file_buffer_c[COPY_BUFSIZE * 5 + ((COPY_BUFSIZE / HEX_BYTES_PER_LINE) * 3)]; @@ -115,13 +156,19 @@ unsigned char includeHttpHeader = 1; unsigned char useHttp11 = 0; unsigned char supportSsi = 1; unsigned char precalcChksum = 0; +unsigned char includeLastModified = 0; +#if MAKEFS_SUPPORT_DEFLATE +unsigned char deflateNonSsiFiles = 0; +size_t deflatedBytesReduced = 0; +size_t overallDataBytes = 0; +#endif struct file_entry* first_file = NULL; struct file_entry* last_file = NULL; static void print_usage(void) { - printf(" Usage: htmlgen [targetdir] [-s] [-i] [-f:]" NEWLINE NEWLINE); + printf(" Usage: htmlgen [targetdir] [-s] [-i] [-f:] [-m]" USAGE_ARG_DEFLATE NEWLINE NEWLINE); printf(" targetdir: relative or absolute path to files to convert" NEWLINE); printf(" switch -s: toggle processing of subdirectories (default is on)" NEWLINE); printf(" switch -e: exclude HTTP header from file (header is created at runtime, default is off)" NEWLINE); @@ -129,6 +176,11 @@ static void print_usage(void) printf(" switch -nossi: no support for SSI (cannot calculate Content-Length for SSI)" NEWLINE); printf(" switch -c: precalculate checksums for all pages (default is off)" NEWLINE); printf(" switch -f: target filename (default is \"fsdata.c\")" NEWLINE); + printf(" switch -m: include \"Last-Modified\" header based on file time" NEWLINE); +#if MAKEFS_SUPPORT_DEFLATE + printf(" switch -defl: deflate-compress all non-SSI files" NEWLINE); + printf(" ATTENTION: browser has to support \"Content-Encoding: deflate\"!" NEWLINE); +#endif printf(" if targetdir not specified, htmlgen will attempt to" NEWLINE); printf(" process files in subdirectory 'fs'" NEWLINE); } @@ -171,6 +223,27 @@ int main(int argc, char *argv[]) strncpy(targetfile, &argv[i][3], sizeof(targetfile) - 1); targetfile[sizeof(targetfile) - 1] = 0; printf("Writing to file \"%s\"\n", targetfile); + } else if (strstr(argv[i], "-m")) { + includeLastModified = 1; + } else if (strstr(argv[i], "-defl")) { +#if MAKEFS_SUPPORT_DEFLATE + char* colon = strstr(argv[i], ":"); + if (colon) { + if (colon[1] != 0) { + int defl_level = atoi(&colon[1]); + if ((defl_level >= 0) && (defl_level <= 10)) { + deflate_level = defl_level; + } else { + printf("ERROR: deflate level must be [0..10]" NEWLINE); + exit(0); + } + } + } + deflateNonSsiFiles = 1; + printf("Deflating all non-SSI files with level %d (but only if size is reduced)" NEWLINE, deflate_level); +#else + printf("WARNING: Deflate support is disabled\n"); +#endif } else if ((strstr(argv[i], "-?")) || (strstr(argv[i], "-h"))) { print_usage(); exit(0); @@ -230,6 +303,20 @@ int main(int argc, char *argv[]) fprintf(data_file, "#include \"fsdata.h\"" NEWLINE NEWLINE NEWLINE); fprintf(data_file, "#define file_NULL (struct fsdata_file *) NULL" NEWLINE NEWLINE NEWLINE); + /* define FS_FILE_FLAGS_HEADER_INCLUDED to 1 if not defined (compatibility with older httpd/fs) */ + fprintf(data_file, "#ifndef FS_FILE_FLAGS_HEADER_INCLUDED" NEWLINE "#define FS_FILE_FLAGS_HEADER_INCLUDED 1" NEWLINE "#endif" NEWLINE); + /* define FS_FILE_FLAGS_HEADER_PERSISTENT to 0 if not defined (compatibility with older httpd/fs: wasn't supported back then) */ + fprintf(data_file, "#ifndef FS_FILE_FLAGS_HEADER_PERSISTENT" NEWLINE "#define FS_FILE_FLAGS_HEADER_PERSISTENT 0" NEWLINE "#endif" NEWLINE); + + /* define alignment defines */ +#if ALIGN_PAYLOAD + fprintf(data_file, "/* FSDATA_FILE_ALIGNMENT: 0=off, 1=by variable, 2=by include */" NEWLINE "#ifndef FSDATA_FILE_ALIGNMENT" NEWLINE "#define FSDATA_FILE_ALIGNMENT 0" NEWLINE "#endif" NEWLINE); +#endif + fprintf(data_file, "#ifndef FSDATA_ALIGN_PRE" NEWLINE "#define FSDATA_ALIGN_PRE" NEWLINE "#endif" NEWLINE); + fprintf(data_file, "#ifndef FSDATA_ALIGN_POST" NEWLINE "#define FSDATA_ALIGN_POST" NEWLINE "#endif" NEWLINE); +#if ALIGN_PAYLOAD + fprintf(data_file, "#if FSDATA_FILE_ALIGNMENT==2" NEWLINE "#include \"fsdata_alignment.h\"" NEWLINE "#endif" NEWLINE); +#endif sprintf(lastFileVar, "NULL"); @@ -257,7 +344,14 @@ int main(int argc, char *argv[]) printf("Warning: failed to delete fshdr.tmp\n"); } - printf(NEWLINE "Processed %d files - done." NEWLINE NEWLINE, filesProcessed); + printf(NEWLINE "Processed %d files - done." NEWLINE, filesProcessed); +#if MAKEFS_SUPPORT_DEFLATE + if (deflateNonSsiFiles) { + printf("(Deflated total byte reduction: %d bytes -> %d bytes (%.02f%%)" NEWLINE, + (int)overallDataBytes, (int)deflatedBytesReduced, (float)((deflatedBytesReduced*100.0)/overallDataBytes)); + } +#endif + printf(NEWLINE); while (first_file != NULL) { struct file_entry* fe = first_file; @@ -295,16 +389,18 @@ static void copy_file(const char *filename_in, FILE *fout) { FILE *fin; size_t len; + void* buf; fin = fopen(filename_in, "rb"); if (fin == NULL) { printf("Failed to open file \"%s\"\n", filename_in); exit(-1); } - - while((len = fread(file_buffer_raw, 1, COPY_BUFSIZE, fin)) > 0) + buf = malloc(COPY_BUFSIZE); + while((len = fread(buf, 1, COPY_BUFSIZE, fin)) > 0) { - fwrite(file_buffer_raw, 1, len, fout); + fwrite(buf, 1, len, fout); } + free(buf); fclose(fin); } @@ -347,7 +443,7 @@ int process_sub(FILE *data_file, FILE *struct_file) strncat(curSubdir, "/", freelen); strncat(curSubdir, curName, freelen - 1); curSubdir[sizeof(curSubdir) - 1] = 0; - printf(NEWLINE "processing subdirectory %s/..." NEWLINE, curSubdir); + printf("processing subdirectory %s/..." NEWLINE, curSubdir); filesProcessed += process_sub(data_file, struct_file); CHDIR(".."); curSubdir[sublen] = 0; @@ -376,58 +472,134 @@ int process_sub(FILE *data_file, FILE *struct_file) return filesProcessed; } -int get_file_size(const char* filename) +u8_t* get_file_data(const char* filename, int* file_size, int can_be_compressed, int* is_compressed) { FILE *inFile; - int file_size = -1; + size_t fsize = 0; + u8_t* buf; + size_t r; + int rs; inFile = fopen(filename, "rb"); if (inFile == NULL) { printf("Failed to open file \"%s\"\n", filename); exit(-1); } fseek(inFile, 0, SEEK_END); - file_size = ftell(inFile); - fclose(inFile); - return file_size; -} - -void process_file_data(const char *filename, FILE *data_file) -{ - FILE *source_file; - size_t len, written, i, src_off=0; - - source_file = fopen(filename, "rb"); - if (source_file == NULL) { - printf("Failed to open file \"%s\"\n", filename); - exit(-1); + rs = ftell(inFile); + if(rs < 0) + { + printf("ftell failed with %d\n", errno); + exit(-1); } - - do { - size_t off = 0; - len = fread(file_buffer_raw, 1, COPY_BUFSIZE, source_file); - if (len > 0) { - for (i = 0; i < len; i++) { - sprintf(&file_buffer_c[off], "0x%02.2x,", file_buffer_raw[i]); - off += 5; - if ((++src_off % HEX_BYTES_PER_LINE) == 0) { - memcpy(&file_buffer_c[off], NEWLINE, NEWLINE_LEN); - off += NEWLINE_LEN; + fsize = (size_t)rs; + fseek(inFile, 0, SEEK_SET); + buf = (u8_t*)malloc(fsize); + LWIP_ASSERT("buf != NULL", buf != NULL); + r = fread(buf, 1, fsize, inFile); + *file_size = fsize; + *is_compressed = 0; +#if MAKEFS_SUPPORT_DEFLATE + overallDataBytes += fsize; + if (deflateNonSsiFiles) { + if (can_be_compressed) { + if (fsize < OUT_BUF_SIZE) { + u8_t* ret_buf; + tdefl_status status; + size_t in_bytes = fsize; + size_t out_bytes = OUT_BUF_SIZE; + const void *next_in = buf; + void *next_out = s_outbuf; + /* create tdefl() compatible flags (we have to compose the low-level flags ourselves, or use tdefl_create_comp_flags_from_zip_params() but that means MINIZ_NO_ZLIB_APIS can't be defined). */ + mz_uint comp_flags = s_tdefl_num_probes[MZ_MIN(10, deflate_level)] | ((deflate_level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (!deflate_level) { + comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; } + status = tdefl_init(&g_deflator, NULL, NULL, comp_flags); + if (status != TDEFL_STATUS_OKAY) { + printf("tdefl_init() failed!\n"); + exit(-1); + } + memset(s_outbuf, 0, sizeof(s_outbuf)); + status = tdefl_compress(&g_deflator, next_in, &in_bytes, next_out, &out_bytes, TDEFL_FINISH); + if (status != TDEFL_STATUS_DONE) { + printf("deflate failed: %d\n", status); + exit(-1); + } + LWIP_ASSERT("out_bytes <= COPY_BUFSIZE", out_bytes <= OUT_BUF_SIZE); + if (out_bytes < fsize) { + ret_buf = (u8_t*)malloc(out_bytes); + LWIP_ASSERT("ret_buf != NULL", ret_buf != NULL); + memcpy(ret_buf, s_outbuf, out_bytes); + { + /* sanity-check compression be inflating and comparing to the original */ + tinfl_status dec_status; + tinfl_decompressor inflator; + size_t dec_in_bytes = out_bytes; + size_t dec_out_bytes = OUT_BUF_SIZE; + next_out = s_checkbuf; + + tinfl_init(&inflator); + memset(s_checkbuf, 0, sizeof(s_checkbuf)); + dec_status = tinfl_decompress(&inflator, (const mz_uint8 *)ret_buf, &dec_in_bytes, s_checkbuf, (mz_uint8 *)next_out, &dec_out_bytes, 0); + LWIP_ASSERT("tinfl_decompress failed", dec_status == TINFL_STATUS_DONE); + LWIP_ASSERT("tinfl_decompress size mismatch", fsize == dec_out_bytes); + LWIP_ASSERT("decompressed memcmp failed", !memcmp(s_checkbuf, buf, fsize)); + } + /* free original buffer, use compressed data + size */ + free(buf); + buf = ret_buf; + *file_size = out_bytes; + printf(" - deflate: %d bytes -> %d bytes (%.02f%%)" NEWLINE, (int)fsize, (int)out_bytes, (float)((out_bytes*100.0)/fsize)); + deflatedBytesReduced += (size_t)(fsize - out_bytes); + *is_compressed = 1; + } else { + printf(" - uncompressed: (would be %d bytes larger using deflate)" NEWLINE, (int)(out_bytes - fsize)); + } + } else { + printf(" - uncompressed: (file is larger than deflate bufer)" NEWLINE); } - written = fwrite(file_buffer_c, 1, off, data_file); + } else { + printf(" - SSI file, cannot be compressed" NEWLINE); } - } while(len > 0); - fclose(source_file); + } +#else + LWIP_UNUSED_ARG(compress); +#endif + fclose(inFile); + return buf; } -int write_checksums(FILE *struct_file, const char *filename, const char *varname, - u16_t hdr_len, u16_t hdr_chksum) +void process_file_data(FILE* data_file, u8_t* file_data, size_t file_size) +{ + size_t written, i, src_off=0; + + size_t off = 0; + for (i = 0; i < file_size; i++) { + LWIP_ASSERT("file_buffer_c overflow", off < sizeof(file_buffer_c) - 5); + sprintf(&file_buffer_c[off], "0x%02.2x,", file_data[i]); + off += 5; + if ((++src_off % HEX_BYTES_PER_LINE) == 0) { + LWIP_ASSERT("file_buffer_c overflow", off < sizeof(file_buffer_c) - NEWLINE_LEN); + memcpy(&file_buffer_c[off], NEWLINE, NEWLINE_LEN); + off += NEWLINE_LEN; + } + if (off + 20 >= sizeof(file_buffer_c)) { + written = fwrite(file_buffer_c, 1, off, data_file); + LWIP_ASSERT("written == off", written == off); + off = 0; + } + } + written = fwrite(file_buffer_c, 1, off, data_file); + LWIP_ASSERT("written == off", written == off); +} + +int write_checksums(FILE *struct_file, const char *varname, + u16_t hdr_len, u16_t hdr_chksum, const u8_t* file_data, size_t file_size) { int chunk_size = TCP_MSS; - int offset; + int offset, src_offset; size_t len; int i = 0; - FILE *f; #if LWIP_TCP_TIMESTAMPS /* when timestamps are used, usable space is 12 bytes less per segment */ chunk_size -= 12; @@ -436,29 +608,24 @@ int write_checksums(FILE *struct_file, const char *filename, const char *varname fprintf(struct_file, "#if HTTPD_PRECALCULATED_CHECKSUM" NEWLINE); fprintf(struct_file, "const struct fsdata_chksum chksums_%s[] = {" NEWLINE, varname); - memset(file_buffer_raw, 0xab, sizeof(file_buffer_raw)); - f = fopen(filename, "rb"); - if (f == NULL) { - printf("Failed to open file \"%s\"\n", filename); - exit(-1); - } if (hdr_len > 0) { /* add checksum for HTTP header */ fprintf(struct_file, "{%d, 0x%04x, %d}," NEWLINE, 0, hdr_chksum, hdr_len); i++; } + src_offset = 0; for (offset = hdr_len; ; offset += len) { unsigned short chksum; - len = fread(file_buffer_raw, 1, chunk_size, f); + void* data = (void*)&file_data[src_offset]; + len = LWIP_MIN(chunk_size, (int)file_size - src_offset); if (len == 0) { break; } - chksum = ~inet_chksum(file_buffer_raw, (u16_t)len); + chksum = ~inet_chksum(data, (u16_t)len); /* add checksum for data */ fprintf(struct_file, "{%d, 0x%04x, %d}," NEWLINE, offset, chksum, len); i++; } - fclose(f); fprintf(struct_file, "};" NEWLINE); fprintf(struct_file, "#endif /* HTTPD_PRECALCULATED_CHECKSUM */" NEWLINE); return i; @@ -480,7 +647,7 @@ static void fix_filename_for_c(char* qualifiedName, size_t max_len) { struct file_entry* f; size_t len = strlen(qualifiedName); - char *new_name = malloc(len + 2); + char *new_name = (char*)malloc(len + 2); int filename_ok; int cnt = 0; size_t i; @@ -516,7 +683,7 @@ static void fix_filename_for_c(char* qualifiedName, size_t max_len) static void register_filename(const char* qualifiedName) { - struct file_entry* fe = malloc(sizeof(struct file_entry)); + struct file_entry* fe = (struct file_entry*)malloc(sizeof(struct file_entry)); fe->filename_c = strdup(qualifiedName); fe->next = NULL; if (first_file == NULL) { @@ -527,6 +694,17 @@ static void register_filename(const char* qualifiedName) } } +int is_ssi_file(const char* filename) +{ + size_t loop; + for (loop = 0; loop < NUM_SHTML_EXTENSIONS; loop++) { + if (strstr(filename, g_pcSSIExtensions[loop])) { + return 1; + } + } + return 0; +} + int process_file(FILE *data_file, FILE *struct_file, const char *filename) { char varname[MAX_PATH_LEN]; @@ -536,6 +714,11 @@ int process_file(FILE *data_file, FILE *struct_file, const char *filename) u16_t http_hdr_chksum = 0; u16_t http_hdr_len = 0; int chksum_count = 0; + u8_t flags = 0; + const char* flags_str; + u8_t has_content_len; + u8_t* file_data; + int is_compressed = 0; /* create qualified name (TODO: prepend slash or not?) */ sprintf(qualifiedName,"%s/%s", curSubdir, filename); @@ -545,10 +728,12 @@ int process_file(FILE *data_file, FILE *struct_file, const char *filename) fix_filename_for_c(varname, MAX_PATH_LEN); register_filename(varname); #if ALIGN_PAYLOAD - /* to force even alignment of array */ + /* to force even alignment of array, type 1 */ + fprintf(data_file, "#if FSDATA_FILE_ALIGNMENT==1" NEWLINE); fprintf(data_file, "static const " PAYLOAD_ALIGN_TYPE " dummy_align_%s = %d;" NEWLINE, varname, payload_alingment_dummy_counter++); + fprintf(data_file, "#endif" NEWLINE); #endif /* ALIGN_PAYLOAD */ - fprintf(data_file, "static const unsigned char data_%s[] = {" NEWLINE, varname); + fprintf(data_file, "static const unsigned char FSDATA_ALIGN_PRE data_%s[] FSDATA_ALIGN_POST = {" NEWLINE, varname); /* encode source file name (used by file system, not returned to browser) */ fprintf(data_file, "/* %s (%d chars) */" NEWLINE, qualifiedName, strlen(qualifiedName)+1); file_put_ascii(data_file, qualifiedName, strlen(qualifiedName)+1, &i); @@ -561,12 +746,17 @@ int process_file(FILE *data_file, FILE *struct_file, const char *filename) #endif /* ALIGN_PAYLOAD */ fprintf(data_file, NEWLINE); - file_size = get_file_size(filename); + has_content_len = !is_ssi_file(filename); + file_data = get_file_data(filename, &file_size, includeHttpHeader && has_content_len, &is_compressed); if (includeHttpHeader) { - file_write_http_header(data_file, filename, file_size, &http_hdr_len, &http_hdr_chksum); + file_write_http_header(data_file, filename, file_size, &http_hdr_len, &http_hdr_chksum, has_content_len, is_compressed); + flags = FS_FILE_FLAGS_HEADER_INCLUDED; + if (has_content_len) { + flags |= FS_FILE_FLAGS_HEADER_PERSISTENT; + } } if (precalcChksum) { - chksum_count = write_checksums(struct_file, filename, varname, http_hdr_len, http_hdr_chksum); + chksum_count = write_checksums(struct_file, varname, http_hdr_len, http_hdr_chksum, file_data, file_size); } /* build declaration of struct fsdata_file in temp file */ @@ -575,7 +765,22 @@ int process_file(FILE *data_file, FILE *struct_file, const char *filename) fprintf(struct_file, "data_%s," NEWLINE, varname); fprintf(struct_file, "data_%s + %d," NEWLINE, varname, i); fprintf(struct_file, "sizeof(data_%s) - %d," NEWLINE, varname, i); - fprintf(struct_file, "%d," NEWLINE, includeHttpHeader); + switch(flags) + { + case(FS_FILE_FLAGS_HEADER_INCLUDED): + flags_str = "FS_FILE_FLAGS_HEADER_INCLUDED"; + break; + case(FS_FILE_FLAGS_HEADER_PERSISTENT): + flags_str = "FS_FILE_FLAGS_HEADER_PERSISTENT"; + break; + case(FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT): + flags_str = "FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT"; + break; + default: + flags_str = "0"; + break; + } + fprintf(struct_file, "%s," NEWLINE, flags_str); if (precalcChksum) { fprintf(struct_file, "#if HTTPD_PRECALCULATED_CHECKSUM" NEWLINE); fprintf(struct_file, "%d, chksums_%s," NEWLINE, chksum_count, varname); @@ -587,14 +792,14 @@ int process_file(FILE *data_file, FILE *struct_file, const char *filename) /* write actual file contents */ i = 0; fprintf(data_file, NEWLINE "/* raw file data (%d bytes) */" NEWLINE, file_size); - process_file_data(filename, data_file); + process_file_data(data_file, file_data, file_size); fprintf(data_file, "};" NEWLINE NEWLINE); - + free(file_data); return 0; } -int file_write_http_header(FILE *data_file, const char *filename, int file_size, - u16_t *http_hdr_len, u16_t *http_hdr_chksum) +int file_write_http_header(FILE *data_file, const char *filename, int file_size, u16_t *http_hdr_len, + u16_t *http_hdr_chksum, u8_t provide_content_len, int is_compressed) { int i = 0; int response_type = HTTP_HDR_OK; @@ -606,17 +811,7 @@ int file_write_http_header(FILE *data_file, const char *filename, int file_size, u16_t acc; const char *file_ext; int j; - u8_t keepalive = useHttp11; - - if (keepalive) { - size_t loop; - for (loop = 0; loop < NUM_SHTML_EXTENSIONS; loop++) { - if (strstr(filename, g_pcSSIExtensions[loop])) { - /* no keepalive connection for SSI files */ - keepalive = 0; - } - } - } + u8_t provide_last_modified = includeLastModified; memset(hdr_buf, 0, sizeof(hdr_buf)); @@ -685,38 +880,78 @@ int file_write_http_header(FILE *data_file, const char *filename, int file_size, } } - if (useHttp11) { + /* Content-Length is used for persistent connections in HTTP/1.1 but also for + download progress in older versions + @todo: just use a big-enough buffer and let the HTTPD send spaces? */ + if (provide_content_len) { char intbuf[MAX_PATH_LEN]; int content_len = file_size; memset(intbuf, 0, sizeof(intbuf)); - - if (!keepalive) { - content_len *= 2; + cur_string = g_psHTTPHeaderStrings[HTTP_HDR_CONTENT_LENGTH]; + cur_len = strlen(cur_string); + fprintf(data_file, NEWLINE "/* \"%s%d\r\n\" (%d+ bytes) */" NEWLINE, cur_string, content_len, cur_len+2); + written += file_put_ascii(data_file, cur_string, cur_len, &i); + if (precalcChksum) { + memcpy(&hdr_buf[hdr_len], cur_string, cur_len); + hdr_len += cur_len; } + + _itoa(content_len, intbuf, 10); + strcat(intbuf, "\r\n"); + cur_len = strlen(intbuf); + written += file_put_ascii(data_file, intbuf, cur_len, &i); + i = 0; + if (precalcChksum) { + memcpy(&hdr_buf[hdr_len], intbuf, cur_len); + hdr_len += cur_len; + } + } + if (provide_last_modified) { + char modbuf[256]; + struct stat stat_data; + struct tm* t; + memset(modbuf, 0, sizeof(modbuf)); + memset(&stat_data, 0, sizeof(stat_data)); + cur_string = modbuf; + strcpy(modbuf, "Last-Modified: "); + if(stat(filename, &stat_data) != 0) { - cur_string = g_psHTTPHeaderStrings[HTTP_HDR_CONTENT_LENGTH]; - cur_len = strlen(cur_string); - fprintf(data_file, NEWLINE "/* \"%s%d\r\n\" (%d+ bytes) */" NEWLINE, cur_string, content_len, cur_len+2); - written += file_put_ascii(data_file, cur_string, cur_len, &i); - if (precalcChksum) { - memcpy(&hdr_buf[hdr_len], cur_string, cur_len); - hdr_len += cur_len; - } - - _itoa(content_len, intbuf, 10); - strcat(intbuf, "\r\n"); - cur_len = strlen(intbuf); - written += file_put_ascii(data_file, intbuf, cur_len, &i); - i = 0; - if (precalcChksum) { - memcpy(&hdr_buf[hdr_len], intbuf, cur_len); - hdr_len += cur_len; - } + printf("stat(%s) failed with error %d\n", filename, errno); + exit(-1); + } + t = gmtime(&stat_data.st_mtime); + if(t == NULL) + { + printf("gmtime() failed with error %d\n", errno); + exit(-1); + } + strftime(&modbuf[15], sizeof(modbuf)-15, "%a, %d %b %Y %H:%M:%S GMT", t); + cur_len = strlen(cur_string); + fprintf(data_file, NEWLINE "/* \"%s\"\r\n\" (%d+ bytes) */" NEWLINE, cur_string, cur_len+2); + written += file_put_ascii(data_file, cur_string, cur_len, &i); + if (precalcChksum) { + memcpy(&hdr_buf[hdr_len], cur_string, cur_len); + hdr_len += cur_len; } - if (keepalive) { + modbuf[0] = 0; + strcat(modbuf, "\r\n"); + cur_len = strlen(modbuf); + written += file_put_ascii(data_file, modbuf, cur_len, &i); + i = 0; + if (precalcChksum) { + memcpy(&hdr_buf[hdr_len], modbuf, cur_len); + hdr_len += cur_len; + } + } + + /* HTTP/1.1 implements persistent connections */ + if (useHttp11) { + if (provide_content_len) { cur_string = g_psHTTPHeaderStrings[HTTP_HDR_CONN_KEEPALIVE]; } else { + /* no Content-Length available, so a persistent connection is no possible + because the client does not know the data length */ cur_string = g_psHTTPHeaderStrings[HTTP_HDR_CONN_CLOSE]; } cur_len = strlen(cur_string); @@ -729,11 +964,29 @@ int file_write_http_header(FILE *data_file, const char *filename, int file_size, } } +#if MAKEFS_SUPPORT_DEFLATE + if (is_compressed) { + /* tell the client about the deflate encoding */ + LWIP_ASSERT("error", deflateNonSsiFiles); + cur_string = "Content-Encoding: deflate\r\n"; + cur_len = strlen(cur_string); + fprintf(data_file, NEWLINE "/* \"%s\" (%d bytes) */" NEWLINE, cur_string, cur_len); + written += file_put_ascii(data_file, cur_string, cur_len, &i); + i = 0; + } +#else + LWIP_UNUSED_ARG(is_compressed); +#endif + + /* write content-type, ATTENTION: this includes the double-CRLF! */ cur_string = file_type; cur_len = strlen(cur_string); fprintf(data_file, NEWLINE "/* \"%s\" (%d bytes) */" NEWLINE, cur_string, cur_len); written += file_put_ascii(data_file, cur_string, cur_len, &i); i = 0; + + /* ATTENTION: headers are done now (double-CRLF has been written!) */ + if (precalcChksum) { memcpy(&hdr_buf[hdr_len], cur_string, cur_len); hdr_len += cur_len; diff --git a/src/include/lwip/apps/fs.h b/src/include/lwip/apps/fs.h index e938e71b..bb176fa0 100644 --- a/src/include/lwip/apps/fs.h +++ b/src/include/lwip/apps/fs.h @@ -50,6 +50,9 @@ struct fsdata_chksum { }; #endif /* HTTPD_PRECALCULATED_CHECKSUM */ +#define FS_FILE_FLAGS_HEADER_INCLUDED 0x01 +#define FS_FILE_FLAGS_HEADER_PERSISTENT 0x02 + struct fs_file { const char *data; int len; @@ -59,7 +62,7 @@ struct fs_file { const struct fsdata_chksum *chksum; u16_t chksum_count; #endif /* HTTPD_PRECALCULATED_CHECKSUM */ - u8_t http_header_included; + u8_t flags; #if LWIP_HTTPD_CUSTOM_FILES u8_t is_custom_file; #endif /* LWIP_HTTPD_CUSTOM_FILES */ diff --git a/src/include/lwip/apps/httpd.h b/src/include/lwip/apps/httpd.h index d7dae7e6..a739c117 100644 --- a/src/include/lwip/apps/httpd.h +++ b/src/include/lwip/apps/httpd.h @@ -92,6 +92,27 @@ void http_set_cgi_handlers(const tCGI *pCGIs, int iNumHandlers); #endif /* LWIP_HTTPD_CGI */ +#if LWIP_HTTPD_CGI || LWIP_HTTPD_CGI_SSI + +/* The maximum number of parameters that the CGI handler can be sent. */ +#ifndef LWIP_HTTPD_MAX_CGI_PARAMETERS +#define LWIP_HTTPD_MAX_CGI_PARAMETERS 16 +#endif + +#if LWIP_HTTPD_CGI_SSI +/** Define this generic CGI handler in your application. + * It is called once for every URI with parameters. + * The parameters can be stored to + */ +extern void httpd_cgi_handler(const char* uri, int iNumParams, char **pcParam, char **pcValue +#if defined(LWIP_HTTPD_FILE_STATE) && LWIP_HTTPD_FILE_STATE + , void *connection_state +#endif /* LWIP_HTTPD_FILE_STATE */ + ); +#endif /* LWIP_HTTPD_CGI_SSI */ + +#endif /* LWIP_HTTPD_CGI || LWIP_HTTPD_CGI_SSI */ + #if LWIP_HTTPD_SSI /* @@ -123,7 +144,13 @@ void http_set_cgi_handlers(const tCGI *pCGIs, int iNumHandlers); * output JavaScript code must do so in an encapsulated way, sending the whole * HTML section as a single include. */ -typedef u16_t (*tSSIHandler)(int iIndex, char *pcInsert, int iInsertLen +typedef u16_t (*tSSIHandler)( +#if LWIP_HTTPD_SSI_RAW + const char* ssi_tag_name, +#else /* LWIP_HTTPD_SSI_RAW */ + int iIndex, +#endif /* LWIP_HTTPD_SSI_RAW */ + char *pcInsert, int iInsertLen #if LWIP_HTTPD_SSI_MULTIPART , u16_t current_tag_part, u16_t *next_tag_part #endif /* LWIP_HTTPD_SSI_MULTIPART */ @@ -132,9 +159,18 @@ typedef u16_t (*tSSIHandler)(int iIndex, char *pcInsert, int iInsertLen #endif /* LWIP_HTTPD_FILE_STATE */ ); +/** Set the SSI handler function + * (if LWIP_HTTPD_SSI_RAW==1, only the first argument is used) + */ void http_set_ssi_handler(tSSIHandler pfnSSIHandler, const char **ppcTags, int iNumTags); +/** For LWIP_HTTPD_SSI_RAW==1, return this to indicat the tag is unknown. + * In this case, the webserver writes a warning into the page. + * You can also just return 0 to write nothing for unknown tags. + */ +#define HTTPD_SSI_TAG_UNKNOWN 0xFFFF + #endif /* LWIP_HTTPD_SSI */ #if LWIP_HTTPD_SUPPORT_POST @@ -192,8 +228,9 @@ void httpd_post_data_recved(void *connection, u16_t recved_len); void httpd_init(void); + #ifdef __cplusplus } #endif -#endif /* LWIP_HDR_APPS_HTTPD_H */ +#endif /* LWIP_HTTPD_H */ diff --git a/src/include/lwip/apps/httpd_opts.h b/src/include/lwip/apps/httpd_opts.h index 6a538dde..58a8eacf 100644 --- a/src/include/lwip/apps/httpd_opts.h +++ b/src/include/lwip/apps/httpd_opts.h @@ -38,11 +38,16 @@ #include "lwip/opt.h" -/** Set this to 1 to support CGI */ +/** Set this to 1 to support CGI (old style) */ #ifndef LWIP_HTTPD_CGI #define LWIP_HTTPD_CGI 0 #endif +/** Set this to 1 to support CGI (new style) */ +#ifndef LWIP_HTTPD_CGI_SSI +#define LWIP_HTTPD_CGI_SSI 0 +#endif + /** Set this to 1 to support SSI (Server-Side-Includes) */ #ifndef LWIP_HTTPD_SSI #define LWIP_HTTPD_SSI 0 @@ -143,6 +148,28 @@ #define LWIP_HTTPD_STRNSTR_PRIVATE 1 #endif +/** Set this to 1 on platforms where stricmp is not available */ +#ifndef LWIP_HTTPD_STRICMP_PRIVATE +#define LWIP_HTTPD_STRICMP_PRIVATE 0 +#endif + +/** Set this to 1 on platforms where stricmp is not available */ +#ifndef LWIP_HTTPD_ITOA_PRIVATE +#define LWIP_HTTPD_ITOA_PRIVATE 1 +#endif + +/** Define this to a smaller function if you have itoa() at hand... */ +#ifndef LWIP_HTTPD_ITOA +#ifndef LWIP_HTTPD_ITOA_PRIVATE +#define LWIP_HTTPD_ITOA_PRIVATE 1 +#endif +#if LWIP_HTTPD_ITOA_PRIVATE +#define LWIP_HTTPD_ITOA(buffer, bufsize, number) httpd_itoa(number, buffer) +#else +#define LWIP_HTTPD_ITOA(buffer, bufsize, number) snprintf(buffer, bufsize, "%d", number) +#endif +#endif + /** Set this to one to show error pages when parsing a request fails instead of simply closing the connection. */ #ifndef LWIP_HTTPD_SUPPORT_EXTSTATUS @@ -188,6 +215,15 @@ #endif #endif /* LWIP_HTTPD_SUPPORT_REQUESTLIST */ +/** This is the size of a static buffer used when URIs end with '/'. + * In this buffer, the directory requested is concatenated with all the + * configured default file names. + * Set to 0 to disable checking default filenames on non-root directories. + */ +#ifndef LWIP_HTTPD_MAX_REQUEST_URI_LEN +#define LWIP_HTTPD_MAX_REQUEST_URI_LEN 63 +#endif + /** Maximum length of the filename to send as response to a POST request, * filled in by the application when a POST is finished. */ @@ -216,6 +252,12 @@ #define LWIP_HTTPD_KILL_OLD_ON_CONNECTIONS_EXCEEDED 0 #endif +/** Set this to 1 to send URIs without extension without headers + * (who uses this at all??) */ +#ifndef LWIP_HTTPD_OMIT_HEADER_FOR_EXTENSIONLESS_URI +#define LWIP_HTTPD_OMIT_HEADER_FOR_EXTENSIONLESS_URI 0 +#endif + /** Default: Tags are sent from struct http_state and are therefore volatile */ #ifndef HTTP_IS_TAG_VOLATILE #define HTTP_IS_TAG_VOLATILE(ptr) TCP_WRITE_FLAG_COPY