diff mbox series

[08/12] net/httpd: add httpd common code

Message ID 20240627113939.100620-8-mikhail.kshevetskiy@iopsys.eu
State Changes Requested
Delegated to: Peter Robinson
Headers show
Series [01/12] net/tcp: fix TCP options processing | expand

Commit Message

Mikhail Kshevetskiy June 27, 2024, 11:39 a.m. UTC
This patch adds HTTP/1.1 compatible web-server that can be used
by other. Server supports GET, POST, and HEAD requests. On client
request it will call user specified GET/POST callback. Then results
will be transmitted to client.

The following restrictions exist on the POST request
at the moment:
  * only multipart/form-data with a single file object
  * object will be stored to a memory area specified in
    image_load_addr variable

Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
---
 include/net.h       |   2 +-
 include/net/httpd.h |  64 ++++
 net/Kconfig         |  14 +
 net/Makefile        |   1 +
 net/httpd.c         | 695 ++++++++++++++++++++++++++++++++++++++++++++
 net/net.c           |   6 +
 6 files changed, 781 insertions(+), 1 deletion(-)
 create mode 100644 include/net/httpd.h
 create mode 100644 net/httpd.c

Comments

Peter Robinson July 1, 2024, 3:54 p.m. UTC | #1
Hi Mikhail,

> This patch adds HTTP/1.1 compatible web-server that can be used
> by other. Server supports GET, POST, and HEAD requests. On client
> request it will call user specified GET/POST callback. Then results
> will be transmitted to client.

Why are we adding a HTTP server? I don't see a cover letter explaining
overall what you're attempting to achieve with this patch set so
please add that. Also I suggest you look at the LWIP patch set [1] as
that may make what you wish to achieve more straight forward.

Peter

[1] https://lists.denx.de/pipermail/u-boot/2024-June/556526.html

> The following restrictions exist on the POST request
> at the moment:
>   * only multipart/form-data with a single file object
>   * object will be stored to a memory area specified in
>     image_load_addr variable
>
> Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
> ---
>  include/net.h       |   2 +-
>  include/net/httpd.h |  64 ++++
>  net/Kconfig         |  14 +
>  net/Makefile        |   1 +
>  net/httpd.c         | 695 ++++++++++++++++++++++++++++++++++++++++++++
>  net/net.c           |   6 +
>  6 files changed, 781 insertions(+), 1 deletion(-)
>  create mode 100644 include/net/httpd.h
>  create mode 100644 net/httpd.c
>
> diff --git a/include/net.h b/include/net.h
> index 235396a171b..6debbf8ed2a 100644
> --- a/include/net.h
> +++ b/include/net.h
> @@ -516,7 +516,7 @@ extern int          net_restart_wrap;       /* Tried all network devices */
>  enum proto_t {
>         BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP,
>         NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT_UDP, FASTBOOT_TCP,
> -       WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_STORE, RS
> +       WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_STORE, HTTPD, RS
>  };
>
>  extern char    net_boot_file_name[1024];/* Boot File name */
> diff --git a/include/net/httpd.h b/include/net/httpd.h
> new file mode 100644
> index 00000000000..ff0dc93ecf5
> --- /dev/null
> +++ b/include/net/httpd.h
> @@ -0,0 +1,64 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * httpd support header file
> + * Copyright (C) 2024 IOPSYS Software Solutions AB
> + * Author: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
> + *
> + */
> +#ifndef __NET_HTTPD_COMMON_H__
> +#define __NET_HTTPD_COMMON_H__
> +
> +struct http_reply {
> +       int             code;
> +       const char      *code_msg;
> +       const char      *data_type;
> +       void            *data;
> +       u32             len;
> +};
> +
> +struct httpd_post_data {
> +       const char      *name;
> +       const char      *filename;
> +       void            *addr;
> +       u32             size;
> +};
> +
> +enum httpd_req_check {
> +       HTTPD_REQ_OK,
> +       HTTPD_BAD_URL,
> +       HTTPD_BAD_REQ,
> +       HTTPD_CLNT_RST
> +};
> +
> +struct httpd_config {
> +       enum net_loop_state     (*on_stop)(void);
> +       void                    (*on_req_end)(void *req_id);
> +
> +       enum httpd_req_check    (*pre_get)(void *req_id, const char *url);
> +       enum httpd_req_check    (*pre_post)(void *req_id, const char *url,
> +                                           struct httpd_post_data *post);
> +
> +       struct http_reply *     (*get)(void *req_id, const char *url);
> +       struct http_reply *     (*post)(void *req_id, const char *url,
> +                                       struct httpd_post_data *post);
> +
> +       struct http_reply       *error_400;
> +       struct http_reply       *error_404;
> +};
> +
> +/**
> + * httpd_setup() - configure the webserver
> + */
> +void httpd_setup(struct httpd_config *config);
> +
> +/**
> + * httpd_stop() - start stopping of the webserver
> + */
> +void httpd_stop(void);
> +
> +/**
> + * httpd_start() - start the webserver
> + */
> +void httpd_start(void);
> +
> +#endif /* __NET_HTTPD_COMMON_H__ */
> diff --git a/net/Kconfig b/net/Kconfig
> index 5dff6336293..424c5f0dae8 100644
> --- a/net/Kconfig
> +++ b/net/Kconfig
> @@ -243,6 +243,20 @@ config PROT_TCP_SACK
>           This option should be turn on if you want to achieve the fastest
>           file transfer possible.
>
> +config HTTPD_COMMON
> +       bool "HTTP server common code"
> +       depends on PROT_TCP
> +       help
> +         HTTP/1.1 compatible web-server common code. It supports standard
> +         GET/POST requests. User MUST provide a configuration to the
> +         web-server. On client request web-server will call user specified
> +         GET/POST callback. Then results will be transmitted to the client.
> +         The following restricions on the POST request are present at the
> +         moment:
> +           * only mulipart/form-data with a single binary object
> +           * object will be stored to a memory area specified in
> +             image_load_addr variable
> +
>  config IPV6
>         bool "IPv6 support"
>         help
> diff --git a/net/Makefile b/net/Makefile
> index dac7b4859fb..c1f491fad02 100644
> --- a/net/Makefile
> +++ b/net/Makefile
> @@ -34,6 +34,7 @@ obj-$(CONFIG_PROT_UDP) += udp.o
>  obj-$(CONFIG_PROT_TCP) += tcp.o
>  obj-$(CONFIG_CMD_WGET) += wget.o
>  obj-$(CONFIG_CMD_NETCAT) += netcat.o
> +obj-$(CONFIG_HTTPD_COMMON) += httpd.o
>
>  # Disable this warning as it is triggered by:
>  # sprintf(buf, index ? "foo%d" : "foo", index)
> diff --git a/net/httpd.c b/net/httpd.c
> new file mode 100644
> index 00000000000..31c10843a44
> --- /dev/null
> +++ b/net/httpd.c
> @@ -0,0 +1,695 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * httpd support driver
> + * Copyright (C) 2024 IOPSYS Software Solutions AB
> + * Author: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
> + */
> +
> +#include <command.h>
> +#include <version.h>
> +#include <display_options.h>
> +#include <env.h>
> +#include <image.h>
> +#include <mapmem.h>
> +#include <malloc.h>
> +#include <net.h>
> +#include <net/tcp.h>
> +#include <net/httpd.h>
> +
> +#define HTTP_PORT              80
> +
> +#define MAX_URL_LEN            128
> +#define MAX_BOUNDARY_LEN       80
> +#define MAX_MPART_NAME_LEN     80
> +#define MAX_FILENAME_LEN       256
> +#define BUFFER_LEN             2048
> +
> +enum http_req_state {
> +       ST_REQ_LINE = 0,
> +       ST_REQ_HDR,
> +       ST_REQ_MPBOUNDARY,
> +       ST_REQ_MPART,
> +       ST_REQ_MPFILE,
> +       ST_REQ_MPEND,
> +       ST_REQ_DONE,
> +};
> +
> +enum http_reply_state {
> +       ST_REPLY_ERR = 0,
> +       ST_REPLY_HDR,
> +       ST_REPLY_BODY
> +};
> +
> +enum http_method {
> +       HTTP_UNKNOWN = -1,
> +       HTTP_GET,
> +       HTTP_POST,
> +       HTTP_HEAD,
> +       HTTP_OPTIONS,
> +};
> +
> +struct httpd_priv {
> +       enum http_req_state     req_state;
> +       enum http_reply_state   reply_state;
> +
> +       struct tcp_stream       *tcp;
> +
> +       enum http_method        method;
> +       char                    url[MAX_URL_LEN];
> +       u32                     version_major;
> +       u32                     version_minor;
> +       u32                     hdr_len;
> +
> +       u32                     post_fstart;
> +       u32                     post_flen;
> +       char                    post_fname[MAX_FILENAME_LEN];
> +       char                    post_name[MAX_MPART_NAME_LEN];
> +       char                    post_boundary[MAX_BOUNDARY_LEN];
> +
> +       int                     reply_code;
> +       u32                     reply_fstart;
> +       u32                     reply_flen;
> +       void                    *reply_fdata;
> +
> +       u32                     rx_processed;
> +       char                    buf[BUFFER_LEN];
> +};
> +
> +static struct http_reply options_reply = {
> +       .code      = 200,
> +       .code_msg  = "OK",
> +       .data_type = "text/plain",
> +       .data      = NULL,
> +       .len       = 0
> +};
> +
> +static int stop_server;
> +static int tsize_num_hash;
> +
> +static struct httpd_config *cfg;
> +
> +static void show_block_marker(u32 offs, u32 size)
> +{
> +       int cnt;
> +
> +       if (offs > size)
> +               offs = size;
> +
> +       cnt = offs * 50 / size;
> +       while (tsize_num_hash < cnt) {
> +               putc('#');
> +               tsize_num_hash++;
> +       }
> +
> +       if (cnt == 50)
> +               putc('\n');
> +}
> +
> +static void tcp_stream_on_closed(struct tcp_stream *tcp)
> +{
> +       struct httpd_priv *priv = tcp->priv;
> +
> +       if ((priv->req_state != ST_REQ_DONE) &&
> +           (priv->req_state >= ST_REQ_MPFILE)) {
> +               printf("\nHTTPD: transfer was terminated\n");
> +       }
> +
> +       if (cfg->on_req_end != NULL)
> +               cfg->on_req_end(tcp->priv);
> +
> +       free(tcp->priv);
> +
> +       if (stop_server)
> +               net_set_state(cfg->on_stop != NULL ?
> +                             cfg->on_stop() :
> +                             NETLOOP_SUCCESS);
> +}
> +
> +static void http_make_reply(struct httpd_priv *priv, struct http_reply *reply,
> +                           int head_only)
> +{
> +       int offs = 0;
> +
> +       if (priv->version_major >= 1) {
> +               offs = snprintf(priv->buf, sizeof(priv->buf),
> +                               "HTTP/%d.%d %d %s\r\n"
> +                               "Server: %s\r\n"
> +                               "Connection: close\r\n"
> +                               "Cache-Control: no-store\r\n"
> +                               "Content-Type: %s\r\n"
> +                               "Content-Length: %d\r\n",
> +                               priv->version_major, priv->version_minor,
> +                               reply->code, reply->code_msg, U_BOOT_VERSION,
> +                               reply->data_type,
> +                               head_only ? 0 : reply->len);
> +               if (priv->method == HTTP_OPTIONS)
> +                       offs += snprintf(priv->buf + offs, sizeof(priv->buf) - offs,
> +                                       "Access-Control-Allow-Methods: GET, HEAD, OPTIONS, POST\r\n");
> +               offs += snprintf(priv->buf + offs, sizeof(priv->buf) - offs,
> +                               "\r\n");
> +       }
> +
> +       priv->reply_code = reply->code;
> +       priv->reply_fstart = offs;
> +       if (!head_only) {
> +               priv->reply_flen = reply->len;
> +               priv->reply_fdata = reply->data;
> +       }
> +}
> +
> +static enum httpd_req_check http_parse_line(struct httpd_priv *priv, char *line)
> +{
> +       char                    *url, *version, *data, *end;
> +       u32                     len, tmp;
> +       enum httpd_req_check    ret;
> +       struct httpd_post_data  post;
> +
> +       switch (priv->req_state) {
> +       case ST_REQ_LINE:
> +               if (strncasecmp(line, "GET ", 4) == 0) {
> +                       priv->method = HTTP_GET;
> +                       url = line + 4;
> +               } else if (strncasecmp(line, "POST ", 5) == 0) {
> +                       priv->method = HTTP_POST;
> +                       url = line + 5;
> +               } else if (strncasecmp(line, "HEAD ", 5) == 0) {
> +                       priv->method = HTTP_HEAD;
> +                       url = line + 5;
> +               } else if (strncasecmp(line, "OPTIONS ", 8) == 0) {
> +                       priv->method = HTTP_OPTIONS;
> +                       url = line + 8;
> +               } else {
> +                       /* unknown request */
> +                       return HTTPD_CLNT_RST;
> +               }
> +
> +               version = strstr(url, " ");
> +               if (version == NULL) {
> +                       /* check for HTTP 0.9 */
> +                       if ((*url != '/') || (priv->method != HTTP_GET))
> +                               return HTTPD_CLNT_RST;
> +
> +                       if (strlen(url) >= MAX_URL_LEN)
> +                               return HTTPD_CLNT_RST;
> +
> +                       if (cfg->pre_get != NULL) {
> +                               ret = cfg->pre_get(priv, url);
> +                               if (ret != HTTPD_REQ_OK)
> +                                       return ret;
> +                       }
> +
> +                       priv->req_state = ST_REQ_DONE;
> +                       priv->hdr_len = strlen(line) + 2;
> +                       priv->version_major = 0;
> +                       priv->version_minor = 9;
> +                       strcpy(priv->url, url);
> +                       return HTTPD_REQ_OK;
> +               }
> +
> +               if (strncasecmp(version + 1, "HTTP/", 5) != 0) {
> +                       /* version is required for HTTP >= 1.0 */
> +                       return HTTPD_CLNT_RST;
> +               }
> +
> +               *version++ = '\0';
> +               version += strlen("HTTP/");
> +
> +               priv->version_major = dectoul(version, &end);
> +               switch (*end) {
> +               case '\0':
> +                       priv->version_minor = 0;
> +                       break;
> +               case '.':
> +                       priv->version_minor = dectoul(end + 1, &end);
> +                       if (*end == '\0')
> +                               break;
> +                       fallthrough;
> +               default:
> +                       /* bad version format */
> +                       return HTTPD_CLNT_RST;
> +               }
> +
> +               if (priv->version_major < 1) {
> +                       /* bad version */
> +                       return HTTPD_CLNT_RST;
> +               }
> +
> +               if ((priv->version_major > 1) || (priv->version_minor > 1)) {
> +                       /* We support HTTP/1.1 or early standards only */
> +                       priv->version_major = 1;
> +                       priv->version_minor = 1;
> +               }
> +
> +               if (*url != '/')
> +                       return HTTPD_CLNT_RST;
> +
> +               if (strlen(url) >= MAX_URL_LEN)
> +                       return HTTPD_CLNT_RST;
> +
> +               priv->req_state = ST_REQ_HDR;
> +               strcpy(priv->url, url);
> +               return HTTPD_REQ_OK;
> +
> +       case ST_REQ_HDR:
> +               if (*line == '\0') {
> +                       priv->hdr_len = priv->rx_processed + 2;
> +                       switch (priv->method) {
> +                       case HTTP_GET:
> +                       case HTTP_HEAD:
> +                               if (cfg->pre_get != NULL) {
> +                                       ret = cfg->pre_get(priv, priv->url);
> +                                       if (ret != HTTPD_REQ_OK)
> +                                               return ret;
> +                               }
> +                               fallthrough;
> +
> +                       case HTTP_OPTIONS:
> +                               priv->req_state = ST_REQ_DONE;
> +                               return HTTPD_REQ_OK;
> +
> +                       default:
> +                               break;
> +                       }
> +
> +                       if (*priv->post_boundary != '\0') {
> +                               priv->req_state = ST_REQ_MPBOUNDARY;
> +                               return HTTPD_REQ_OK;
> +                       }
> +                       /* NOT multipart/form-data POST request */
> +                       return HTTPD_BAD_REQ;
> +               }
> +
> +               if (priv->method != HTTP_POST)
> +                       return HTTPD_REQ_OK;
> +
> +               len = strlen("Content-Length: ");
> +               if (strncasecmp(line, "Content-Length: ", len) == 0) {
> +                       data = line + len;
> +                       priv->post_flen = simple_strtol(data, &end, 10);
> +                       if (*end != '\0') {
> +                               /* bad Content-Length string */
> +                               return HTTPD_BAD_REQ;
> +                       }
> +                       return HTTPD_REQ_OK;
> +               }
> +
> +               len = strlen("Content-Type: ");
> +               if (strncasecmp(line, "Content-Type: ", len) == 0) {
> +                       data = strstr(line + len, " boundary=");
> +                       if (data == NULL) {
> +                               /* expect multipart/form-data format */
> +                               return HTTPD_BAD_REQ;
> +                       }
> +
> +                       data += strlen(" boundary=");
> +                       if (strlen(data) >= sizeof(priv->post_boundary)) {
> +                               /* no space to keep boundary */
> +                               return HTTPD_BAD_REQ;
> +                       }
> +
> +                       strcpy(priv->post_boundary, data);
> +                       return HTTPD_REQ_OK;
> +               }
> +
> +               return HTTPD_REQ_OK;
> +
> +       case ST_REQ_MPBOUNDARY:
> +               if (*line == '\0')
> +                       return HTTPD_REQ_OK;
> +               if ((line[0] != '-') || (line[1] != '-') ||
> +                   (strcmp(line + 2, priv->post_boundary) != 0)) {
> +                       /* expect boundary line */
> +                       return HTTPD_BAD_REQ;
> +               }
> +               priv->req_state = ST_REQ_MPART;
> +               return HTTPD_REQ_OK;
> +
> +       case ST_REQ_MPART:
> +               if (*line == '\0') {
> +                       if (*priv->post_name == '\0')
> +                               return HTTPD_BAD_REQ;
> +
> +                       priv->post_fstart = priv->rx_processed + 2;
> +                       priv->post_flen -= priv->post_fstart - priv->hdr_len;
> +                       /* expect: "\r\n--${boundary}--\r\n", so strlen() + 8 */
> +                       priv->post_flen -= strlen(priv->post_boundary) + 8;
> +
> +                       if (cfg->pre_post != NULL) {
> +                               post.addr     = NULL;
> +                               post.name     = priv->post_name;
> +                               post.filename = priv->post_fname;
> +                               post.size     = priv->post_flen;
> +
> +                               ret = cfg->pre_post(priv, priv->url, &post);
> +                               if (ret != HTTPD_REQ_OK)
> +                                       return ret;
> +                       }
> +
> +                       tsize_num_hash = 0;
> +                       printf("File: %s, %u bytes\n", priv->post_fname, priv->post_flen);
> +                       printf("Loading: ");
> +
> +                       priv->req_state = ST_REQ_MPFILE;
> +                       return HTTPD_REQ_OK;
> +               }
> +
> +               len = strlen("Content-Disposition: ");
> +               if (strncasecmp(line, "Content-Disposition: ", len) == 0) {
> +                       data = strstr(line + len, " name=\"");
> +                       if (data == NULL) {
> +                               /* name attribute not found */
> +                               return HTTPD_BAD_REQ;
> +                       }
> +
> +                       data += strlen(" name=\"");
> +                       end = strstr(data, "\"");
> +                       if (end == NULL) {
> +                               /* bad name attribute format */
> +                               return HTTPD_BAD_REQ;
> +                       }
> +
> +                       tmp = end - data;
> +                       if (tmp >= sizeof(priv->post_name)) {
> +                               /* multipart name is too long */
> +                               return HTTPD_BAD_REQ;
> +                       }
> +                       strncpy(priv->post_name, data, tmp);
> +                       priv->post_name[tmp] = '\0';
> +
> +                       data = strstr(line + len, " filename=\"");
> +                       if (data == NULL) {
> +                               /* filename attribute not found */
> +                               return HTTPD_BAD_REQ;
> +                       }
> +
> +                       data += strlen(" filename=\"");
> +                       end = strstr(data, "\"");
> +                       if (end == NULL) {
> +                               /* bad filename attribute format */
> +                               return HTTPD_BAD_REQ;
> +                       }
> +
> +                       tmp = end - data;
> +                       if (tmp >= sizeof(priv->post_fname))
> +                               tmp = sizeof(priv->post_fname) - 1;
> +                       strncpy(priv->post_fname, data, tmp);
> +                       priv->post_fname[tmp] = '\0';
> +                       return HTTPD_REQ_OK;
> +               }
> +
> +               return HTTPD_REQ_OK;
> +
> +       case ST_REQ_MPEND:
> +               if (*line == '\0')
> +                       return HTTPD_REQ_OK;
> +
> +               len = strlen(priv->post_boundary);
> +               if ((line[0] != '-') || (line[1] != '-') ||
> +                   (strncmp(line + 2, priv->post_boundary, len) != 0) ||
> +                   (line[len + 2] != '-') || (line[len + 3] != '-') ||
> +                   (line[len + 4] != '\0')) {
> +                       /* expect final boundary line */
> +                       return HTTPD_BAD_REQ;
> +               }
> +               priv->req_state = ST_REQ_DONE;
> +               return HTTPD_REQ_OK;
> +
> +       default:
> +               return HTTPD_BAD_REQ;
> +       }
> +}
> +
> +static enum httpd_req_check http_parse_buf(struct httpd_priv *priv,
> +                                          char *buf, u32 size)
> +{
> +       char                    *eol_pos;
> +       u32                     len;
> +       enum httpd_req_check    ret;
> +
> +       buf[size] = '\0';
> +       while (size > 0) {
> +               eol_pos = strstr(buf, "\r\n");
> +               if (eol_pos == NULL)
> +                       break;
> +
> +               *eol_pos = '\0';
> +               len = eol_pos + 2 - buf;
> +
> +               ret = http_parse_line(priv, buf);
> +               if (ret != HTTPD_REQ_OK) {
> +                       /* request processing error */
> +                       return ret;
> +               }
> +
> +               priv->rx_processed += len;
> +               buf += len;
> +               size -= len;
> +
> +               if ((priv->req_state == ST_REQ_MPFILE) ||
> +                   (priv->req_state == ST_REQ_DONE))
> +                       return HTTPD_REQ_OK;
> +       }
> +       /* continue when more data becomes available */
> +       return HTTPD_REQ_OK;
> +}
> +
> +static void tcp_stream_on_rcv_nxt_update(struct tcp_stream *tcp, u32 rx_bytes)
> +{
> +       struct httpd_priv       *priv;
> +       void                    *ptr;
> +       u32                     shift, size;
> +       enum httpd_req_check    ret;
> +       struct http_reply       *reply;
> +       struct httpd_post_data  post;
> +
> +       priv = tcp->priv;
> +
> +       switch (priv->req_state) {
> +       case ST_REQ_DONE:
> +               return;
> +
> +       case ST_REQ_MPFILE:
> +               show_block_marker(rx_bytes - priv->post_fstart,
> +                                 priv->post_flen);
> +               if (rx_bytes < priv->post_fstart + priv->post_flen) {
> +                       priv->rx_processed = rx_bytes;
> +                       return;
> +               }
> +               priv->req_state = ST_REQ_MPEND;
> +               priv->rx_processed = priv->post_fstart + priv->post_flen;
> +               fallthrough;
> +
> +       case ST_REQ_MPEND:
> +               shift = priv->rx_processed - priv->post_fstart;
> +               ptr = map_sysmem(image_load_addr + shift,
> +                                rx_bytes - priv->rx_processed);
> +               ret = http_parse_buf(priv, ptr,
> +                                    rx_bytes - priv->rx_processed);
> +               unmap_sysmem(ptr);
> +
> +               if (ret != HTTPD_REQ_OK)
> +                       goto error;
> +               if (priv->req_state != ST_REQ_DONE)
> +                       return;
> +               break;
> +
> +       default:
> +               ret = http_parse_buf(priv, priv->buf + priv->rx_processed,
> +                                    rx_bytes - priv->rx_processed);
> +               if (ret != HTTPD_REQ_OK)
> +                       goto error;
> +
> +               if (priv->req_state == ST_REQ_MPFILE) {
> +                       /*
> +                        * We just switched from parsing of HTTP request
> +                        * headers to binary data reading. Our tcp->rx
> +                        * handler may put some binary data to priv->buf.
> +                        * It's time to copy these data to a proper place.
> +                        * It's required to copy whole buffer data starting
> +                        * from priv->rx_processed position. Otherwise we
> +                        * may miss data placed after the first hole.
> +                        */
> +                       size = sizeof(priv->buf) - priv->rx_processed;
> +                       if (size > 0) {
> +                               ptr = map_sysmem(image_load_addr, size);
> +                               memcpy(ptr, priv->buf + priv->rx_processed, size);
> +                               unmap_sysmem(ptr);
> +                       }
> +
> +                       show_block_marker(rx_bytes - priv->post_fstart,
> +                                         priv->post_flen);
> +               }
> +
> +               if (priv->req_state != ST_REQ_DONE)
> +                       return;
> +               break;
> +       }
> +
> +       switch (priv->method) {
> +       case HTTP_OPTIONS:
> +               reply = &options_reply;
> +               break;
> +
> +       case HTTP_GET:
> +       case HTTP_HEAD:
> +               if (cfg->get == NULL) {
> +                       ret = HTTPD_BAD_REQ;
> +                       goto error;
> +               }
> +               reply = cfg->get(priv, priv->url);
> +               break;
> +
> +       case HTTP_POST:
> +               if (cfg->post == NULL) {
> +                       ret = HTTPD_BAD_REQ;
> +                       goto error;
> +               }
> +               post.name     = priv->post_name;
> +               post.filename = priv->post_fname;
> +               post.size     = priv->post_flen;
> +               post.addr     = map_sysmem(image_load_addr, post.size);
> +               reply = cfg->post(priv, priv->url, &post);
> +               unmap_sysmem(post.addr);
> +               break;
> +
> +       default:
> +               ret = HTTPD_BAD_REQ;
> +               goto error;
> +       }
> +
> +       http_make_reply(priv, reply, priv->method == HTTP_HEAD);
> +       return;
> +
> +error:
> +       priv->req_state = ST_REQ_DONE;
> +       switch (ret) {
> +       case HTTPD_BAD_URL:
> +               http_make_reply(priv, cfg->error_404, 0);
> +               break;
> +       case HTTPD_BAD_REQ:
> +               http_make_reply(priv, cfg->error_400, 0);
> +               break;
> +       default:
> +               tcp_stream_reset(tcp);
> +               break;
> +       }
> +}
> +
> +static u32 tcp_stream_rx(struct tcp_stream *tcp, u32 rx_offs, void *buf, u32 len)
> +{
> +       void                    *ptr;
> +       struct httpd_priv       *priv;
> +       u32                     shift;
> +
> +       priv = tcp->priv;
> +       switch (priv->req_state) {
> +       case ST_REQ_DONE:
> +               return len;
> +       case ST_REQ_MPFILE:
> +       case ST_REQ_MPEND:
> +               shift = rx_offs - priv->post_fstart;
> +               ptr = map_sysmem(image_load_addr + shift, len);
> +               memcpy(ptr, buf, len);
> +               unmap_sysmem(ptr);
> +               return len;
> +       default:
> +               /*
> +                * accept data that fits to buffer,
> +                * reserve space for end of line symbol
> +                */
> +               if (rx_offs + len > sizeof(priv->buf) - 1)
> +                       len = sizeof(priv->buf) - rx_offs - 1;
> +               memcpy(priv->buf + rx_offs, buf, len);
> +               return len;
> +       }
> +}
> +
> +static void tcp_stream_on_snd_una_update(struct tcp_stream *tcp, u32 tx_bytes)
> +{
> +       struct httpd_priv *priv;
> +
> +       priv = tcp->priv;
> +       if ((priv->req_state == ST_REQ_DONE) &&
> +           (tx_bytes == priv->reply_fstart + priv->reply_flen))
> +               tcp_stream_close(tcp);
> +}
> +
> +static u32 tcp_stream_tx(struct tcp_stream *tcp, u32 tx_offs, void *buf, u32 maxlen)
> +{
> +       struct httpd_priv       *priv;
> +       u32                     len, bytes = 0;
> +       char                    *ptr;
> +
> +       priv = tcp->priv;
> +       if (priv->req_state != ST_REQ_DONE)
> +               return 0;
> +
> +       if (tx_offs < priv->reply_fstart) {
> +               len = maxlen;
> +               if (len > priv->reply_fstart - tx_offs)
> +                       len = priv->reply_fstart - tx_offs;
> +               memcpy(buf, priv->buf + tx_offs, len);
> +               buf += len;
> +               tx_offs += len;
> +               bytes += len;
> +               maxlen -= len;
> +       }
> +
> +       if (tx_offs >= priv->reply_fstart) {
> +               if (tx_offs + maxlen > priv->reply_fstart + priv->reply_flen)
> +                       maxlen = priv->reply_fstart + priv->reply_flen - tx_offs;
> +               if (maxlen > 0) {
> +                       ptr = priv->reply_fdata + tx_offs - priv->reply_fstart;
> +                       memcpy(buf, ptr, maxlen);
> +                       bytes += maxlen;
> +               }
> +       }
> +
> +       return bytes;
> +}
> +
> +static int tcp_stream_on_create(struct tcp_stream *tcp)
> +{
> +       struct httpd_priv       *priv;
> +
> +       if ((cfg == NULL) || stop_server ||
> +           (tcp->lport != HTTP_PORT))
> +               return 0;
> +
> +       priv = malloc(sizeof(struct httpd_priv));
> +       if (priv == NULL)
> +               return 0;
> +
> +       memset(priv, 0, sizeof(struct httpd_priv));
> +       priv->tcp = tcp;
> +
> +       tcp->priv = priv;
> +       tcp->on_closed = tcp_stream_on_closed;
> +       tcp->on_rcv_nxt_update = tcp_stream_on_rcv_nxt_update;
> +       tcp->rx = tcp_stream_rx;
> +       tcp->on_snd_una_update = tcp_stream_on_snd_una_update;
> +       tcp->tx = tcp_stream_tx;
> +       return 1;
> +}
> +
> +void httpd_setup(struct httpd_config *config)
> +{
> +       cfg = config;
> +}
> +
> +void httpd_stop(void)
> +{
> +       stop_server = 1;
> +}
> +
> +void httpd_start(void)
> +{
> +       if (cfg == NULL) {
> +               net_set_state(NETLOOP_FAIL);
> +               return;
> +       }
> +       stop_server = 0;
> +       memset(net_server_ethaddr, 0, 6);
> +       tcp_stream_set_on_create_handler(tcp_stream_on_create);
> +       printf("HTTPD listening on port %d...\n", HTTP_PORT);
> +}
> diff --git a/net/net.c b/net/net.c
> index 809fe5c4792..ca761c57372 100644
> --- a/net/net.c
> +++ b/net/net.c
> @@ -111,6 +111,7 @@
>  #include <net/tcp.h>
>  #include <net/wget.h>
>  #include <net/netcat.h>
> +#include <net/httpd.h>
>  #include "arp.h"
>  #include "bootp.h"
>  #include "cdp.h"
> @@ -575,6 +576,11 @@ restart:
>                         netcat_store_start();
>                         break;
>  #endif
> +#if defined(CONFIG_HTTPD_COMMON)
> +               case HTTPD:
> +                       httpd_start();
> +                       break;
> +#endif
>  #if defined(CONFIG_CMD_CDP)
>                 case CDP:
>                         cdp_start();
> --
> 2.43.0
>
Mikhail Kshevetskiy July 3, 2024, 2:06 a.m. UTC | #2
On 7/1/24 19:54, Peter Robinson wrote:
> Hi Mikhail,
>
>> This patch adds HTTP/1.1 compatible web-server that can be used
>> by other. Server supports GET, POST, and HEAD requests. On client
>> request it will call user specified GET/POST callback. Then results
>> will be transmitted to client.
> Why are we adding a HTTP server? I don't see a cover letter explaining
> overall what you're attempting to achieve with this patch set so
> please add that. Also I suggest you look at the LWIP patch set [1] as
> that may make what you wish to achieve more straight forward.
>
> Peter
>
> [1] https://lists.denx.de/pipermail/u-boot/2024-June/556526.html

This patch series consist of
* TCP fixes. Current U-Boot implementation of TCP is bad. It is
especially bad for uploading. This patch series fixes TCP support. I
know about attempts to add LWIP to u-Boot, but it's not in U-Boot yet.

* Rewrite of existing TCP clients (wget, fastboot_tcp)  on the base of
new code

* netcat client/server. It was written to test data downloading and
uploading using TCP.

* HTTPD support. It consist of 2 parts: common code and sample
web-server. Sample web-server can be used as a reference httpd
implementation. We use this HTTPD support for our firmware upgrade
web-server. It is similar to the sample web-server.

PS: Will resend patches with a cover letter tomorrow.

>> The following restrictions exist on the POST request
>> at the moment:
>>   * only multipart/form-data with a single file object
>>   * object will be stored to a memory area specified in
>>     image_load_addr variable
>>
>> Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
>> ---
>>  include/net.h       |   2 +-
>>  include/net/httpd.h |  64 ++++
>>  net/Kconfig         |  14 +
>>  net/Makefile        |   1 +
>>  net/httpd.c         | 695 ++++++++++++++++++++++++++++++++++++++++++++
>>  net/net.c           |   6 +
>>  6 files changed, 781 insertions(+), 1 deletion(-)
>>  create mode 100644 include/net/httpd.h
>>  create mode 100644 net/httpd.c
>>
>> diff --git a/include/net.h b/include/net.h
>> index 235396a171b..6debbf8ed2a 100644
>> --- a/include/net.h
>> +++ b/include/net.h
>> @@ -516,7 +516,7 @@ extern int          net_restart_wrap;       /* Tried all network devices */
>>  enum proto_t {
>>         BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP,
>>         NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT_UDP, FASTBOOT_TCP,
>> -       WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_STORE, RS
>> +       WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_STORE, HTTPD, RS
>>  };
>>
>>  extern char    net_boot_file_name[1024];/* Boot File name */
>> diff --git a/include/net/httpd.h b/include/net/httpd.h
>> new file mode 100644
>> index 00000000000..ff0dc93ecf5
>> --- /dev/null
>> +++ b/include/net/httpd.h
>> @@ -0,0 +1,64 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +/*
>> + * httpd support header file
>> + * Copyright (C) 2024 IOPSYS Software Solutions AB
>> + * Author: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
>> + *
>> + */
>> +#ifndef __NET_HTTPD_COMMON_H__
>> +#define __NET_HTTPD_COMMON_H__
>> +
>> +struct http_reply {
>> +       int             code;
>> +       const char      *code_msg;
>> +       const char      *data_type;
>> +       void            *data;
>> +       u32             len;
>> +};
>> +
>> +struct httpd_post_data {
>> +       const char      *name;
>> +       const char      *filename;
>> +       void            *addr;
>> +       u32             size;
>> +};
>> +
>> +enum httpd_req_check {
>> +       HTTPD_REQ_OK,
>> +       HTTPD_BAD_URL,
>> +       HTTPD_BAD_REQ,
>> +       HTTPD_CLNT_RST
>> +};
>> +
>> +struct httpd_config {
>> +       enum net_loop_state     (*on_stop)(void);
>> +       void                    (*on_req_end)(void *req_id);
>> +
>> +       enum httpd_req_check    (*pre_get)(void *req_id, const char *url);
>> +       enum httpd_req_check    (*pre_post)(void *req_id, const char *url,
>> +                                           struct httpd_post_data *post);
>> +
>> +       struct http_reply *     (*get)(void *req_id, const char *url);
>> +       struct http_reply *     (*post)(void *req_id, const char *url,
>> +                                       struct httpd_post_data *post);
>> +
>> +       struct http_reply       *error_400;
>> +       struct http_reply       *error_404;
>> +};
>> +
>> +/**
>> + * httpd_setup() - configure the webserver
>> + */
>> +void httpd_setup(struct httpd_config *config);
>> +
>> +/**
>> + * httpd_stop() - start stopping of the webserver
>> + */
>> +void httpd_stop(void);
>> +
>> +/**
>> + * httpd_start() - start the webserver
>> + */
>> +void httpd_start(void);
>> +
>> +#endif /* __NET_HTTPD_COMMON_H__ */
>> diff --git a/net/Kconfig b/net/Kconfig
>> index 5dff6336293..424c5f0dae8 100644
>> --- a/net/Kconfig
>> +++ b/net/Kconfig
>> @@ -243,6 +243,20 @@ config PROT_TCP_SACK
>>           This option should be turn on if you want to achieve the fastest
>>           file transfer possible.
>>
>> +config HTTPD_COMMON
>> +       bool "HTTP server common code"
>> +       depends on PROT_TCP
>> +       help
>> +         HTTP/1.1 compatible web-server common code. It supports standard
>> +         GET/POST requests. User MUST provide a configuration to the
>> +         web-server. On client request web-server will call user specified
>> +         GET/POST callback. Then results will be transmitted to the client.
>> +         The following restricions on the POST request are present at the
>> +         moment:
>> +           * only mulipart/form-data with a single binary object
>> +           * object will be stored to a memory area specified in
>> +             image_load_addr variable
>> +
>>  config IPV6
>>         bool "IPv6 support"
>>         help
>> diff --git a/net/Makefile b/net/Makefile
>> index dac7b4859fb..c1f491fad02 100644
>> --- a/net/Makefile
>> +++ b/net/Makefile
>> @@ -34,6 +34,7 @@ obj-$(CONFIG_PROT_UDP) += udp.o
>>  obj-$(CONFIG_PROT_TCP) += tcp.o
>>  obj-$(CONFIG_CMD_WGET) += wget.o
>>  obj-$(CONFIG_CMD_NETCAT) += netcat.o
>> +obj-$(CONFIG_HTTPD_COMMON) += httpd.o
>>
>>  # Disable this warning as it is triggered by:
>>  # sprintf(buf, index ? "foo%d" : "foo", index)
>> diff --git a/net/httpd.c b/net/httpd.c
>> new file mode 100644
>> index 00000000000..31c10843a44
>> --- /dev/null
>> +++ b/net/httpd.c
>> @@ -0,0 +1,695 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * httpd support driver
>> + * Copyright (C) 2024 IOPSYS Software Solutions AB
>> + * Author: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
>> + */
>> +
>> +#include <command.h>
>> +#include <version.h>
>> +#include <display_options.h>
>> +#include <env.h>
>> +#include <image.h>
>> +#include <mapmem.h>
>> +#include <malloc.h>
>> +#include <net.h>
>> +#include <net/tcp.h>
>> +#include <net/httpd.h>
>> +
>> +#define HTTP_PORT              80
>> +
>> +#define MAX_URL_LEN            128
>> +#define MAX_BOUNDARY_LEN       80
>> +#define MAX_MPART_NAME_LEN     80
>> +#define MAX_FILENAME_LEN       256
>> +#define BUFFER_LEN             2048
>> +
>> +enum http_req_state {
>> +       ST_REQ_LINE = 0,
>> +       ST_REQ_HDR,
>> +       ST_REQ_MPBOUNDARY,
>> +       ST_REQ_MPART,
>> +       ST_REQ_MPFILE,
>> +       ST_REQ_MPEND,
>> +       ST_REQ_DONE,
>> +};
>> +
>> +enum http_reply_state {
>> +       ST_REPLY_ERR = 0,
>> +       ST_REPLY_HDR,
>> +       ST_REPLY_BODY
>> +};
>> +
>> +enum http_method {
>> +       HTTP_UNKNOWN = -1,
>> +       HTTP_GET,
>> +       HTTP_POST,
>> +       HTTP_HEAD,
>> +       HTTP_OPTIONS,
>> +};
>> +
>> +struct httpd_priv {
>> +       enum http_req_state     req_state;
>> +       enum http_reply_state   reply_state;
>> +
>> +       struct tcp_stream       *tcp;
>> +
>> +       enum http_method        method;
>> +       char                    url[MAX_URL_LEN];
>> +       u32                     version_major;
>> +       u32                     version_minor;
>> +       u32                     hdr_len;
>> +
>> +       u32                     post_fstart;
>> +       u32                     post_flen;
>> +       char                    post_fname[MAX_FILENAME_LEN];
>> +       char                    post_name[MAX_MPART_NAME_LEN];
>> +       char                    post_boundary[MAX_BOUNDARY_LEN];
>> +
>> +       int                     reply_code;
>> +       u32                     reply_fstart;
>> +       u32                     reply_flen;
>> +       void                    *reply_fdata;
>> +
>> +       u32                     rx_processed;
>> +       char                    buf[BUFFER_LEN];
>> +};
>> +
>> +static struct http_reply options_reply = {
>> +       .code      = 200,
>> +       .code_msg  = "OK",
>> +       .data_type = "text/plain",
>> +       .data      = NULL,
>> +       .len       = 0
>> +};
>> +
>> +static int stop_server;
>> +static int tsize_num_hash;
>> +
>> +static struct httpd_config *cfg;
>> +
>> +static void show_block_marker(u32 offs, u32 size)
>> +{
>> +       int cnt;
>> +
>> +       if (offs > size)
>> +               offs = size;
>> +
>> +       cnt = offs * 50 / size;
>> +       while (tsize_num_hash < cnt) {
>> +               putc('#');
>> +               tsize_num_hash++;
>> +       }
>> +
>> +       if (cnt == 50)
>> +               putc('\n');
>> +}
>> +
>> +static void tcp_stream_on_closed(struct tcp_stream *tcp)
>> +{
>> +       struct httpd_priv *priv = tcp->priv;
>> +
>> +       if ((priv->req_state != ST_REQ_DONE) &&
>> +           (priv->req_state >= ST_REQ_MPFILE)) {
>> +               printf("\nHTTPD: transfer was terminated\n");
>> +       }
>> +
>> +       if (cfg->on_req_end != NULL)
>> +               cfg->on_req_end(tcp->priv);
>> +
>> +       free(tcp->priv);
>> +
>> +       if (stop_server)
>> +               net_set_state(cfg->on_stop != NULL ?
>> +                             cfg->on_stop() :
>> +                             NETLOOP_SUCCESS);
>> +}
>> +
>> +static void http_make_reply(struct httpd_priv *priv, struct http_reply *reply,
>> +                           int head_only)
>> +{
>> +       int offs = 0;
>> +
>> +       if (priv->version_major >= 1) {
>> +               offs = snprintf(priv->buf, sizeof(priv->buf),
>> +                               "HTTP/%d.%d %d %s\r\n"
>> +                               "Server: %s\r\n"
>> +                               "Connection: close\r\n"
>> +                               "Cache-Control: no-store\r\n"
>> +                               "Content-Type: %s\r\n"
>> +                               "Content-Length: %d\r\n",
>> +                               priv->version_major, priv->version_minor,
>> +                               reply->code, reply->code_msg, U_BOOT_VERSION,
>> +                               reply->data_type,
>> +                               head_only ? 0 : reply->len);
>> +               if (priv->method == HTTP_OPTIONS)
>> +                       offs += snprintf(priv->buf + offs, sizeof(priv->buf) - offs,
>> +                                       "Access-Control-Allow-Methods: GET, HEAD, OPTIONS, POST\r\n");
>> +               offs += snprintf(priv->buf + offs, sizeof(priv->buf) - offs,
>> +                               "\r\n");
>> +       }
>> +
>> +       priv->reply_code = reply->code;
>> +       priv->reply_fstart = offs;
>> +       if (!head_only) {
>> +               priv->reply_flen = reply->len;
>> +               priv->reply_fdata = reply->data;
>> +       }
>> +}
>> +
>> +static enum httpd_req_check http_parse_line(struct httpd_priv *priv, char *line)
>> +{
>> +       char                    *url, *version, *data, *end;
>> +       u32                     len, tmp;
>> +       enum httpd_req_check    ret;
>> +       struct httpd_post_data  post;
>> +
>> +       switch (priv->req_state) {
>> +       case ST_REQ_LINE:
>> +               if (strncasecmp(line, "GET ", 4) == 0) {
>> +                       priv->method = HTTP_GET;
>> +                       url = line + 4;
>> +               } else if (strncasecmp(line, "POST ", 5) == 0) {
>> +                       priv->method = HTTP_POST;
>> +                       url = line + 5;
>> +               } else if (strncasecmp(line, "HEAD ", 5) == 0) {
>> +                       priv->method = HTTP_HEAD;
>> +                       url = line + 5;
>> +               } else if (strncasecmp(line, "OPTIONS ", 8) == 0) {
>> +                       priv->method = HTTP_OPTIONS;
>> +                       url = line + 8;
>> +               } else {
>> +                       /* unknown request */
>> +                       return HTTPD_CLNT_RST;
>> +               }
>> +
>> +               version = strstr(url, " ");
>> +               if (version == NULL) {
>> +                       /* check for HTTP 0.9 */
>> +                       if ((*url != '/') || (priv->method != HTTP_GET))
>> +                               return HTTPD_CLNT_RST;
>> +
>> +                       if (strlen(url) >= MAX_URL_LEN)
>> +                               return HTTPD_CLNT_RST;
>> +
>> +                       if (cfg->pre_get != NULL) {
>> +                               ret = cfg->pre_get(priv, url);
>> +                               if (ret != HTTPD_REQ_OK)
>> +                                       return ret;
>> +                       }
>> +
>> +                       priv->req_state = ST_REQ_DONE;
>> +                       priv->hdr_len = strlen(line) + 2;
>> +                       priv->version_major = 0;
>> +                       priv->version_minor = 9;
>> +                       strcpy(priv->url, url);
>> +                       return HTTPD_REQ_OK;
>> +               }
>> +
>> +               if (strncasecmp(version + 1, "HTTP/", 5) != 0) {
>> +                       /* version is required for HTTP >= 1.0 */
>> +                       return HTTPD_CLNT_RST;
>> +               }
>> +
>> +               *version++ = '\0';
>> +               version += strlen("HTTP/");
>> +
>> +               priv->version_major = dectoul(version, &end);
>> +               switch (*end) {
>> +               case '\0':
>> +                       priv->version_minor = 0;
>> +                       break;
>> +               case '.':
>> +                       priv->version_minor = dectoul(end + 1, &end);
>> +                       if (*end == '\0')
>> +                               break;
>> +                       fallthrough;
>> +               default:
>> +                       /* bad version format */
>> +                       return HTTPD_CLNT_RST;
>> +               }
>> +
>> +               if (priv->version_major < 1) {
>> +                       /* bad version */
>> +                       return HTTPD_CLNT_RST;
>> +               }
>> +
>> +               if ((priv->version_major > 1) || (priv->version_minor > 1)) {
>> +                       /* We support HTTP/1.1 or early standards only */
>> +                       priv->version_major = 1;
>> +                       priv->version_minor = 1;
>> +               }
>> +
>> +               if (*url != '/')
>> +                       return HTTPD_CLNT_RST;
>> +
>> +               if (strlen(url) >= MAX_URL_LEN)
>> +                       return HTTPD_CLNT_RST;
>> +
>> +               priv->req_state = ST_REQ_HDR;
>> +               strcpy(priv->url, url);
>> +               return HTTPD_REQ_OK;
>> +
>> +       case ST_REQ_HDR:
>> +               if (*line == '\0') {
>> +                       priv->hdr_len = priv->rx_processed + 2;
>> +                       switch (priv->method) {
>> +                       case HTTP_GET:
>> +                       case HTTP_HEAD:
>> +                               if (cfg->pre_get != NULL) {
>> +                                       ret = cfg->pre_get(priv, priv->url);
>> +                                       if (ret != HTTPD_REQ_OK)
>> +                                               return ret;
>> +                               }
>> +                               fallthrough;
>> +
>> +                       case HTTP_OPTIONS:
>> +                               priv->req_state = ST_REQ_DONE;
>> +                               return HTTPD_REQ_OK;
>> +
>> +                       default:
>> +                               break;
>> +                       }
>> +
>> +                       if (*priv->post_boundary != '\0') {
>> +                               priv->req_state = ST_REQ_MPBOUNDARY;
>> +                               return HTTPD_REQ_OK;
>> +                       }
>> +                       /* NOT multipart/form-data POST request */
>> +                       return HTTPD_BAD_REQ;
>> +               }
>> +
>> +               if (priv->method != HTTP_POST)
>> +                       return HTTPD_REQ_OK;
>> +
>> +               len = strlen("Content-Length: ");
>> +               if (strncasecmp(line, "Content-Length: ", len) == 0) {
>> +                       data = line + len;
>> +                       priv->post_flen = simple_strtol(data, &end, 10);
>> +                       if (*end != '\0') {
>> +                               /* bad Content-Length string */
>> +                               return HTTPD_BAD_REQ;
>> +                       }
>> +                       return HTTPD_REQ_OK;
>> +               }
>> +
>> +               len = strlen("Content-Type: ");
>> +               if (strncasecmp(line, "Content-Type: ", len) == 0) {
>> +                       data = strstr(line + len, " boundary=");
>> +                       if (data == NULL) {
>> +                               /* expect multipart/form-data format */
>> +                               return HTTPD_BAD_REQ;
>> +                       }
>> +
>> +                       data += strlen(" boundary=");
>> +                       if (strlen(data) >= sizeof(priv->post_boundary)) {
>> +                               /* no space to keep boundary */
>> +                               return HTTPD_BAD_REQ;
>> +                       }
>> +
>> +                       strcpy(priv->post_boundary, data);
>> +                       return HTTPD_REQ_OK;
>> +               }
>> +
>> +               return HTTPD_REQ_OK;
>> +
>> +       case ST_REQ_MPBOUNDARY:
>> +               if (*line == '\0')
>> +                       return HTTPD_REQ_OK;
>> +               if ((line[0] != '-') || (line[1] != '-') ||
>> +                   (strcmp(line + 2, priv->post_boundary) != 0)) {
>> +                       /* expect boundary line */
>> +                       return HTTPD_BAD_REQ;
>> +               }
>> +               priv->req_state = ST_REQ_MPART;
>> +               return HTTPD_REQ_OK;
>> +
>> +       case ST_REQ_MPART:
>> +               if (*line == '\0') {
>> +                       if (*priv->post_name == '\0')
>> +                               return HTTPD_BAD_REQ;
>> +
>> +                       priv->post_fstart = priv->rx_processed + 2;
>> +                       priv->post_flen -= priv->post_fstart - priv->hdr_len;
>> +                       /* expect: "\r\n--${boundary}--\r\n", so strlen() + 8 */
>> +                       priv->post_flen -= strlen(priv->post_boundary) + 8;
>> +
>> +                       if (cfg->pre_post != NULL) {
>> +                               post.addr     = NULL;
>> +                               post.name     = priv->post_name;
>> +                               post.filename = priv->post_fname;
>> +                               post.size     = priv->post_flen;
>> +
>> +                               ret = cfg->pre_post(priv, priv->url, &post);
>> +                               if (ret != HTTPD_REQ_OK)
>> +                                       return ret;
>> +                       }
>> +
>> +                       tsize_num_hash = 0;
>> +                       printf("File: %s, %u bytes\n", priv->post_fname, priv->post_flen);
>> +                       printf("Loading: ");
>> +
>> +                       priv->req_state = ST_REQ_MPFILE;
>> +                       return HTTPD_REQ_OK;
>> +               }
>> +
>> +               len = strlen("Content-Disposition: ");
>> +               if (strncasecmp(line, "Content-Disposition: ", len) == 0) {
>> +                       data = strstr(line + len, " name=\"");
>> +                       if (data == NULL) {
>> +                               /* name attribute not found */
>> +                               return HTTPD_BAD_REQ;
>> +                       }
>> +
>> +                       data += strlen(" name=\"");
>> +                       end = strstr(data, "\"");
>> +                       if (end == NULL) {
>> +                               /* bad name attribute format */
>> +                               return HTTPD_BAD_REQ;
>> +                       }
>> +
>> +                       tmp = end - data;
>> +                       if (tmp >= sizeof(priv->post_name)) {
>> +                               /* multipart name is too long */
>> +                               return HTTPD_BAD_REQ;
>> +                       }
>> +                       strncpy(priv->post_name, data, tmp);
>> +                       priv->post_name[tmp] = '\0';
>> +
>> +                       data = strstr(line + len, " filename=\"");
>> +                       if (data == NULL) {
>> +                               /* filename attribute not found */
>> +                               return HTTPD_BAD_REQ;
>> +                       }
>> +
>> +                       data += strlen(" filename=\"");
>> +                       end = strstr(data, "\"");
>> +                       if (end == NULL) {
>> +                               /* bad filename attribute format */
>> +                               return HTTPD_BAD_REQ;
>> +                       }
>> +
>> +                       tmp = end - data;
>> +                       if (tmp >= sizeof(priv->post_fname))
>> +                               tmp = sizeof(priv->post_fname) - 1;
>> +                       strncpy(priv->post_fname, data, tmp);
>> +                       priv->post_fname[tmp] = '\0';
>> +                       return HTTPD_REQ_OK;
>> +               }
>> +
>> +               return HTTPD_REQ_OK;
>> +
>> +       case ST_REQ_MPEND:
>> +               if (*line == '\0')
>> +                       return HTTPD_REQ_OK;
>> +
>> +               len = strlen(priv->post_boundary);
>> +               if ((line[0] != '-') || (line[1] != '-') ||
>> +                   (strncmp(line + 2, priv->post_boundary, len) != 0) ||
>> +                   (line[len + 2] != '-') || (line[len + 3] != '-') ||
>> +                   (line[len + 4] != '\0')) {
>> +                       /* expect final boundary line */
>> +                       return HTTPD_BAD_REQ;
>> +               }
>> +               priv->req_state = ST_REQ_DONE;
>> +               return HTTPD_REQ_OK;
>> +
>> +       default:
>> +               return HTTPD_BAD_REQ;
>> +       }
>> +}
>> +
>> +static enum httpd_req_check http_parse_buf(struct httpd_priv *priv,
>> +                                          char *buf, u32 size)
>> +{
>> +       char                    *eol_pos;
>> +       u32                     len;
>> +       enum httpd_req_check    ret;
>> +
>> +       buf[size] = '\0';
>> +       while (size > 0) {
>> +               eol_pos = strstr(buf, "\r\n");
>> +               if (eol_pos == NULL)
>> +                       break;
>> +
>> +               *eol_pos = '\0';
>> +               len = eol_pos + 2 - buf;
>> +
>> +               ret = http_parse_line(priv, buf);
>> +               if (ret != HTTPD_REQ_OK) {
>> +                       /* request processing error */
>> +                       return ret;
>> +               }
>> +
>> +               priv->rx_processed += len;
>> +               buf += len;
>> +               size -= len;
>> +
>> +               if ((priv->req_state == ST_REQ_MPFILE) ||
>> +                   (priv->req_state == ST_REQ_DONE))
>> +                       return HTTPD_REQ_OK;
>> +       }
>> +       /* continue when more data becomes available */
>> +       return HTTPD_REQ_OK;
>> +}
>> +
>> +static void tcp_stream_on_rcv_nxt_update(struct tcp_stream *tcp, u32 rx_bytes)
>> +{
>> +       struct httpd_priv       *priv;
>> +       void                    *ptr;
>> +       u32                     shift, size;
>> +       enum httpd_req_check    ret;
>> +       struct http_reply       *reply;
>> +       struct httpd_post_data  post;
>> +
>> +       priv = tcp->priv;
>> +
>> +       switch (priv->req_state) {
>> +       case ST_REQ_DONE:
>> +               return;
>> +
>> +       case ST_REQ_MPFILE:
>> +               show_block_marker(rx_bytes - priv->post_fstart,
>> +                                 priv->post_flen);
>> +               if (rx_bytes < priv->post_fstart + priv->post_flen) {
>> +                       priv->rx_processed = rx_bytes;
>> +                       return;
>> +               }
>> +               priv->req_state = ST_REQ_MPEND;
>> +               priv->rx_processed = priv->post_fstart + priv->post_flen;
>> +               fallthrough;
>> +
>> +       case ST_REQ_MPEND:
>> +               shift = priv->rx_processed - priv->post_fstart;
>> +               ptr = map_sysmem(image_load_addr + shift,
>> +                                rx_bytes - priv->rx_processed);
>> +               ret = http_parse_buf(priv, ptr,
>> +                                    rx_bytes - priv->rx_processed);
>> +               unmap_sysmem(ptr);
>> +
>> +               if (ret != HTTPD_REQ_OK)
>> +                       goto error;
>> +               if (priv->req_state != ST_REQ_DONE)
>> +                       return;
>> +               break;
>> +
>> +       default:
>> +               ret = http_parse_buf(priv, priv->buf + priv->rx_processed,
>> +                                    rx_bytes - priv->rx_processed);
>> +               if (ret != HTTPD_REQ_OK)
>> +                       goto error;
>> +
>> +               if (priv->req_state == ST_REQ_MPFILE) {
>> +                       /*
>> +                        * We just switched from parsing of HTTP request
>> +                        * headers to binary data reading. Our tcp->rx
>> +                        * handler may put some binary data to priv->buf.
>> +                        * It's time to copy these data to a proper place.
>> +                        * It's required to copy whole buffer data starting
>> +                        * from priv->rx_processed position. Otherwise we
>> +                        * may miss data placed after the first hole.
>> +                        */
>> +                       size = sizeof(priv->buf) - priv->rx_processed;
>> +                       if (size > 0) {
>> +                               ptr = map_sysmem(image_load_addr, size);
>> +                               memcpy(ptr, priv->buf + priv->rx_processed, size);
>> +                               unmap_sysmem(ptr);
>> +                       }
>> +
>> +                       show_block_marker(rx_bytes - priv->post_fstart,
>> +                                         priv->post_flen);
>> +               }
>> +
>> +               if (priv->req_state != ST_REQ_DONE)
>> +                       return;
>> +               break;
>> +       }
>> +
>> +       switch (priv->method) {
>> +       case HTTP_OPTIONS:
>> +               reply = &options_reply;
>> +               break;
>> +
>> +       case HTTP_GET:
>> +       case HTTP_HEAD:
>> +               if (cfg->get == NULL) {
>> +                       ret = HTTPD_BAD_REQ;
>> +                       goto error;
>> +               }
>> +               reply = cfg->get(priv, priv->url);
>> +               break;
>> +
>> +       case HTTP_POST:
>> +               if (cfg->post == NULL) {
>> +                       ret = HTTPD_BAD_REQ;
>> +                       goto error;
>> +               }
>> +               post.name     = priv->post_name;
>> +               post.filename = priv->post_fname;
>> +               post.size     = priv->post_flen;
>> +               post.addr     = map_sysmem(image_load_addr, post.size);
>> +               reply = cfg->post(priv, priv->url, &post);
>> +               unmap_sysmem(post.addr);
>> +               break;
>> +
>> +       default:
>> +               ret = HTTPD_BAD_REQ;
>> +               goto error;
>> +       }
>> +
>> +       http_make_reply(priv, reply, priv->method == HTTP_HEAD);
>> +       return;
>> +
>> +error:
>> +       priv->req_state = ST_REQ_DONE;
>> +       switch (ret) {
>> +       case HTTPD_BAD_URL:
>> +               http_make_reply(priv, cfg->error_404, 0);
>> +               break;
>> +       case HTTPD_BAD_REQ:
>> +               http_make_reply(priv, cfg->error_400, 0);
>> +               break;
>> +       default:
>> +               tcp_stream_reset(tcp);
>> +               break;
>> +       }
>> +}
>> +
>> +static u32 tcp_stream_rx(struct tcp_stream *tcp, u32 rx_offs, void *buf, u32 len)
>> +{
>> +       void                    *ptr;
>> +       struct httpd_priv       *priv;
>> +       u32                     shift;
>> +
>> +       priv = tcp->priv;
>> +       switch (priv->req_state) {
>> +       case ST_REQ_DONE:
>> +               return len;
>> +       case ST_REQ_MPFILE:
>> +       case ST_REQ_MPEND:
>> +               shift = rx_offs - priv->post_fstart;
>> +               ptr = map_sysmem(image_load_addr + shift, len);
>> +               memcpy(ptr, buf, len);
>> +               unmap_sysmem(ptr);
>> +               return len;
>> +       default:
>> +               /*
>> +                * accept data that fits to buffer,
>> +                * reserve space for end of line symbol
>> +                */
>> +               if (rx_offs + len > sizeof(priv->buf) - 1)
>> +                       len = sizeof(priv->buf) - rx_offs - 1;
>> +               memcpy(priv->buf + rx_offs, buf, len);
>> +               return len;
>> +       }
>> +}
>> +
>> +static void tcp_stream_on_snd_una_update(struct tcp_stream *tcp, u32 tx_bytes)
>> +{
>> +       struct httpd_priv *priv;
>> +
>> +       priv = tcp->priv;
>> +       if ((priv->req_state == ST_REQ_DONE) &&
>> +           (tx_bytes == priv->reply_fstart + priv->reply_flen))
>> +               tcp_stream_close(tcp);
>> +}
>> +
>> +static u32 tcp_stream_tx(struct tcp_stream *tcp, u32 tx_offs, void *buf, u32 maxlen)
>> +{
>> +       struct httpd_priv       *priv;
>> +       u32                     len, bytes = 0;
>> +       char                    *ptr;
>> +
>> +       priv = tcp->priv;
>> +       if (priv->req_state != ST_REQ_DONE)
>> +               return 0;
>> +
>> +       if (tx_offs < priv->reply_fstart) {
>> +               len = maxlen;
>> +               if (len > priv->reply_fstart - tx_offs)
>> +                       len = priv->reply_fstart - tx_offs;
>> +               memcpy(buf, priv->buf + tx_offs, len);
>> +               buf += len;
>> +               tx_offs += len;
>> +               bytes += len;
>> +               maxlen -= len;
>> +       }
>> +
>> +       if (tx_offs >= priv->reply_fstart) {
>> +               if (tx_offs + maxlen > priv->reply_fstart + priv->reply_flen)
>> +                       maxlen = priv->reply_fstart + priv->reply_flen - tx_offs;
>> +               if (maxlen > 0) {
>> +                       ptr = priv->reply_fdata + tx_offs - priv->reply_fstart;
>> +                       memcpy(buf, ptr, maxlen);
>> +                       bytes += maxlen;
>> +               }
>> +       }
>> +
>> +       return bytes;
>> +}
>> +
>> +static int tcp_stream_on_create(struct tcp_stream *tcp)
>> +{
>> +       struct httpd_priv       *priv;
>> +
>> +       if ((cfg == NULL) || stop_server ||
>> +           (tcp->lport != HTTP_PORT))
>> +               return 0;
>> +
>> +       priv = malloc(sizeof(struct httpd_priv));
>> +       if (priv == NULL)
>> +               return 0;
>> +
>> +       memset(priv, 0, sizeof(struct httpd_priv));
>> +       priv->tcp = tcp;
>> +
>> +       tcp->priv = priv;
>> +       tcp->on_closed = tcp_stream_on_closed;
>> +       tcp->on_rcv_nxt_update = tcp_stream_on_rcv_nxt_update;
>> +       tcp->rx = tcp_stream_rx;
>> +       tcp->on_snd_una_update = tcp_stream_on_snd_una_update;
>> +       tcp->tx = tcp_stream_tx;
>> +       return 1;
>> +}
>> +
>> +void httpd_setup(struct httpd_config *config)
>> +{
>> +       cfg = config;
>> +}
>> +
>> +void httpd_stop(void)
>> +{
>> +       stop_server = 1;
>> +}
>> +
>> +void httpd_start(void)
>> +{
>> +       if (cfg == NULL) {
>> +               net_set_state(NETLOOP_FAIL);
>> +               return;
>> +       }
>> +       stop_server = 0;
>> +       memset(net_server_ethaddr, 0, 6);
>> +       tcp_stream_set_on_create_handler(tcp_stream_on_create);
>> +       printf("HTTPD listening on port %d...\n", HTTP_PORT);
>> +}
>> diff --git a/net/net.c b/net/net.c
>> index 809fe5c4792..ca761c57372 100644
>> --- a/net/net.c
>> +++ b/net/net.c
>> @@ -111,6 +111,7 @@
>>  #include <net/tcp.h>
>>  #include <net/wget.h>
>>  #include <net/netcat.h>
>> +#include <net/httpd.h>
>>  #include "arp.h"
>>  #include "bootp.h"
>>  #include "cdp.h"
>> @@ -575,6 +576,11 @@ restart:
>>                         netcat_store_start();
>>                         break;
>>  #endif
>> +#if defined(CONFIG_HTTPD_COMMON)
>> +               case HTTPD:
>> +                       httpd_start();
>> +                       break;
>> +#endif
>>  #if defined(CONFIG_CMD_CDP)
>>                 case CDP:
>>                         cdp_start();
>> --
>> 2.43.0
>>
Tom Rini July 3, 2024, 4:38 p.m. UTC | #3
On Wed, Jul 03, 2024 at 06:06:52AM +0400, Mikhail Kshevetskiy wrote:
> 
> On 7/1/24 19:54, Peter Robinson wrote:
> > Hi Mikhail,
> >
> >> This patch adds HTTP/1.1 compatible web-server that can be used
> >> by other. Server supports GET, POST, and HEAD requests. On client
> >> request it will call user specified GET/POST callback. Then results
> >> will be transmitted to client.
> > Why are we adding a HTTP server? I don't see a cover letter explaining
> > overall what you're attempting to achieve with this patch set so
> > please add that. Also I suggest you look at the LWIP patch set [1] as
> > that may make what you wish to achieve more straight forward.
> >
> > Peter
> >
> > [1] https://lists.denx.de/pipermail/u-boot/2024-June/556526.html
> 
> This patch series consist of
> * TCP fixes. Current U-Boot implementation of TCP is bad. It is
> especially bad for uploading. This patch series fixes TCP support. I
> know about attempts to add LWIP to u-Boot, but it's not in U-Boot yet.

To be clear, baring some unforseen difficulty, lwIP will be merged. Is
it ready today? No. But I would ask that if people have both networking
background and desire to do U-Boot development they look in that
direction rather than enhancing (rather than bugfixing) the legacy stack
further.
diff mbox series

Patch

diff --git a/include/net.h b/include/net.h
index 235396a171b..6debbf8ed2a 100644
--- a/include/net.h
+++ b/include/net.h
@@ -516,7 +516,7 @@  extern int		net_restart_wrap;	/* Tried all network devices */
 enum proto_t {
 	BOOTP, RARP, ARP, TFTPGET, DHCP, DHCP6, PING, PING6, DNS, NFS, CDP,
 	NETCONS, SNTP, TFTPSRV, TFTPPUT, LINKLOCAL, FASTBOOT_UDP, FASTBOOT_TCP,
-	WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_STORE, RS
+	WOL, UDP, NCSI, WGET, NETCAT_LOAD, NETCAT_STORE, HTTPD, RS
 };
 
 extern char	net_boot_file_name[1024];/* Boot File name */
diff --git a/include/net/httpd.h b/include/net/httpd.h
new file mode 100644
index 00000000000..ff0dc93ecf5
--- /dev/null
+++ b/include/net/httpd.h
@@ -0,0 +1,64 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * httpd support header file
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+ *
+ */
+#ifndef __NET_HTTPD_COMMON_H__
+#define __NET_HTTPD_COMMON_H__
+
+struct http_reply {
+	int		code;
+	const char	*code_msg;
+	const char	*data_type;
+	void		*data;
+	u32		len;
+};
+
+struct httpd_post_data {
+	const char	*name;
+	const char	*filename;
+	void		*addr;
+	u32		size;
+};
+
+enum httpd_req_check {
+	HTTPD_REQ_OK,
+	HTTPD_BAD_URL,
+	HTTPD_BAD_REQ,
+	HTTPD_CLNT_RST
+};
+
+struct httpd_config {
+	enum net_loop_state	(*on_stop)(void);
+	void			(*on_req_end)(void *req_id);
+
+	enum httpd_req_check	(*pre_get)(void *req_id, const char *url);
+	enum httpd_req_check	(*pre_post)(void *req_id, const char *url,
+					    struct httpd_post_data *post);
+
+	struct http_reply *	(*get)(void *req_id, const char *url);
+	struct http_reply *	(*post)(void *req_id, const char *url,
+					struct httpd_post_data *post);
+
+	struct http_reply	*error_400;
+	struct http_reply	*error_404;
+};
+
+/**
+ * httpd_setup() - configure the webserver
+ */
+void httpd_setup(struct httpd_config *config);
+
+/**
+ * httpd_stop() - start stopping of the webserver
+ */
+void httpd_stop(void);
+
+/**
+ * httpd_start() - start the webserver
+ */
+void httpd_start(void);
+
+#endif /* __NET_HTTPD_COMMON_H__ */
diff --git a/net/Kconfig b/net/Kconfig
index 5dff6336293..424c5f0dae8 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -243,6 +243,20 @@  config PROT_TCP_SACK
 	  This option should be turn on if you want to achieve the fastest
 	  file transfer possible.
 
+config HTTPD_COMMON
+	bool "HTTP server common code"
+	depends on PROT_TCP
+	help
+	  HTTP/1.1 compatible web-server common code. It supports standard
+	  GET/POST requests. User MUST provide a configuration to the
+	  web-server. On client request web-server will call user specified
+	  GET/POST callback. Then results will be transmitted to the client.
+	  The following restricions on the POST request are present at the
+	  moment:
+	    * only mulipart/form-data with a single binary object
+	    * object will be stored to a memory area specified in
+	      image_load_addr variable
+
 config IPV6
 	bool "IPv6 support"
 	help
diff --git a/net/Makefile b/net/Makefile
index dac7b4859fb..c1f491fad02 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -34,6 +34,7 @@  obj-$(CONFIG_PROT_UDP) += udp.o
 obj-$(CONFIG_PROT_TCP) += tcp.o
 obj-$(CONFIG_CMD_WGET) += wget.o
 obj-$(CONFIG_CMD_NETCAT) += netcat.o
+obj-$(CONFIG_HTTPD_COMMON) += httpd.o
 
 # Disable this warning as it is triggered by:
 # sprintf(buf, index ? "foo%d" : "foo", index)
diff --git a/net/httpd.c b/net/httpd.c
new file mode 100644
index 00000000000..31c10843a44
--- /dev/null
+++ b/net/httpd.c
@@ -0,0 +1,695 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * httpd support driver
+ * Copyright (C) 2024 IOPSYS Software Solutions AB
+ * Author: Mikhail Kshevetskiy <mikhail.kshevetskiy@iopsys.eu>
+ */
+
+#include <command.h>
+#include <version.h>
+#include <display_options.h>
+#include <env.h>
+#include <image.h>
+#include <mapmem.h>
+#include <malloc.h>
+#include <net.h>
+#include <net/tcp.h>
+#include <net/httpd.h>
+
+#define HTTP_PORT		80
+
+#define MAX_URL_LEN		128
+#define MAX_BOUNDARY_LEN	80
+#define MAX_MPART_NAME_LEN	80
+#define MAX_FILENAME_LEN	256
+#define BUFFER_LEN		2048
+
+enum http_req_state {
+	ST_REQ_LINE = 0,
+	ST_REQ_HDR,
+	ST_REQ_MPBOUNDARY,
+	ST_REQ_MPART,
+	ST_REQ_MPFILE,
+	ST_REQ_MPEND,
+	ST_REQ_DONE,
+};
+
+enum http_reply_state {
+	ST_REPLY_ERR = 0,
+	ST_REPLY_HDR,
+	ST_REPLY_BODY
+};
+
+enum http_method {
+	HTTP_UNKNOWN = -1,
+	HTTP_GET,
+	HTTP_POST,
+	HTTP_HEAD,
+	HTTP_OPTIONS,
+};
+
+struct httpd_priv {
+	enum http_req_state	req_state;
+	enum http_reply_state	reply_state;
+
+	struct tcp_stream	*tcp;
+
+	enum http_method	method;
+	char			url[MAX_URL_LEN];
+	u32			version_major;
+	u32			version_minor;
+	u32			hdr_len;
+
+	u32			post_fstart;
+	u32			post_flen;
+	char			post_fname[MAX_FILENAME_LEN];
+	char			post_name[MAX_MPART_NAME_LEN];
+	char			post_boundary[MAX_BOUNDARY_LEN];
+
+	int			reply_code;
+	u32			reply_fstart;
+	u32			reply_flen;
+	void			*reply_fdata;
+
+	u32			rx_processed;
+	char			buf[BUFFER_LEN];
+};
+
+static struct http_reply options_reply = {
+	.code      = 200,
+	.code_msg  = "OK",
+	.data_type = "text/plain",
+	.data      = NULL,
+	.len       = 0
+};
+
+static int stop_server;
+static int tsize_num_hash;
+
+static struct httpd_config *cfg;
+
+static void show_block_marker(u32 offs, u32 size)
+{
+	int cnt;
+
+	if (offs > size)
+		offs = size;
+
+	cnt = offs * 50 / size;
+	while (tsize_num_hash < cnt) {
+		putc('#');
+		tsize_num_hash++;
+	}
+
+	if (cnt == 50)
+		putc('\n');
+}
+
+static void tcp_stream_on_closed(struct tcp_stream *tcp)
+{
+	struct httpd_priv *priv = tcp->priv;
+
+	if ((priv->req_state != ST_REQ_DONE) &&
+	    (priv->req_state >= ST_REQ_MPFILE)) {
+		printf("\nHTTPD: transfer was terminated\n");
+	}
+
+	if (cfg->on_req_end != NULL)
+		cfg->on_req_end(tcp->priv);
+
+	free(tcp->priv);
+
+	if (stop_server)
+		net_set_state(cfg->on_stop != NULL ?
+			      cfg->on_stop() :
+			      NETLOOP_SUCCESS);
+}
+
+static void http_make_reply(struct httpd_priv *priv, struct http_reply *reply,
+			    int head_only)
+{
+	int offs = 0;
+
+	if (priv->version_major >= 1) {
+		offs = snprintf(priv->buf, sizeof(priv->buf),
+				"HTTP/%d.%d %d %s\r\n"
+				"Server: %s\r\n"
+				"Connection: close\r\n"
+				"Cache-Control: no-store\r\n"
+				"Content-Type: %s\r\n"
+				"Content-Length: %d\r\n",
+				priv->version_major, priv->version_minor,
+				reply->code, reply->code_msg, U_BOOT_VERSION,
+				reply->data_type,
+				head_only ? 0 : reply->len);
+		if (priv->method == HTTP_OPTIONS)
+			offs += snprintf(priv->buf + offs, sizeof(priv->buf) - offs,
+					"Access-Control-Allow-Methods: GET, HEAD, OPTIONS, POST\r\n");
+		offs += snprintf(priv->buf + offs, sizeof(priv->buf) - offs,
+				"\r\n");
+	}
+
+	priv->reply_code = reply->code;
+	priv->reply_fstart = offs;
+	if (!head_only) {
+		priv->reply_flen = reply->len;
+		priv->reply_fdata = reply->data;
+	}
+}
+
+static enum httpd_req_check http_parse_line(struct httpd_priv *priv, char *line)
+{
+	char			*url, *version, *data, *end;
+	u32			len, tmp;
+	enum httpd_req_check	ret;
+	struct httpd_post_data	post;
+
+	switch (priv->req_state) {
+	case ST_REQ_LINE:
+		if (strncasecmp(line, "GET ", 4) == 0) {
+			priv->method = HTTP_GET;
+			url = line + 4;
+		} else if (strncasecmp(line, "POST ", 5) == 0) {
+			priv->method = HTTP_POST;
+			url = line + 5;
+		} else if (strncasecmp(line, "HEAD ", 5) == 0) {
+			priv->method = HTTP_HEAD;
+			url = line + 5;
+		} else if (strncasecmp(line, "OPTIONS ", 8) == 0) {
+			priv->method = HTTP_OPTIONS;
+			url = line + 8;
+		} else {
+			/* unknown request */
+			return HTTPD_CLNT_RST;
+		}
+
+		version = strstr(url, " ");
+		if (version == NULL) {
+			/* check for HTTP 0.9 */
+			if ((*url != '/') || (priv->method != HTTP_GET))
+				return HTTPD_CLNT_RST;
+
+			if (strlen(url) >= MAX_URL_LEN)
+				return HTTPD_CLNT_RST;
+
+			if (cfg->pre_get != NULL) {
+				ret = cfg->pre_get(priv, url);
+				if (ret != HTTPD_REQ_OK)
+					return ret;
+			}
+
+			priv->req_state = ST_REQ_DONE;
+			priv->hdr_len = strlen(line) + 2;
+			priv->version_major = 0;
+			priv->version_minor = 9;
+			strcpy(priv->url, url);
+			return HTTPD_REQ_OK;
+		}
+
+		if (strncasecmp(version + 1, "HTTP/", 5) != 0) {
+			/* version is required for HTTP >= 1.0 */
+			return HTTPD_CLNT_RST;
+		}
+
+		*version++ = '\0';
+		version += strlen("HTTP/");
+
+		priv->version_major = dectoul(version, &end);
+		switch (*end) {
+		case '\0':
+			priv->version_minor = 0;
+			break;
+		case '.':
+			priv->version_minor = dectoul(end + 1, &end);
+			if (*end == '\0')
+				break;
+			fallthrough;
+		default:
+			/* bad version format */
+			return HTTPD_CLNT_RST;
+		}
+
+		if (priv->version_major < 1) {
+			/* bad version */
+			return HTTPD_CLNT_RST;
+		}
+
+		if ((priv->version_major > 1) || (priv->version_minor > 1)) {
+			/* We support HTTP/1.1 or early standards only */
+			priv->version_major = 1;
+			priv->version_minor = 1;
+		}
+
+		if (*url != '/')
+			return HTTPD_CLNT_RST;
+
+		if (strlen(url) >= MAX_URL_LEN)
+			return HTTPD_CLNT_RST;
+
+		priv->req_state = ST_REQ_HDR;
+		strcpy(priv->url, url);
+		return HTTPD_REQ_OK;
+
+	case ST_REQ_HDR:
+		if (*line == '\0') {
+			priv->hdr_len = priv->rx_processed + 2;
+			switch (priv->method) {
+			case HTTP_GET:
+			case HTTP_HEAD:
+				if (cfg->pre_get != NULL) {
+					ret = cfg->pre_get(priv, priv->url);
+					if (ret != HTTPD_REQ_OK)
+						return ret;
+				}
+				fallthrough;
+
+			case HTTP_OPTIONS:
+				priv->req_state = ST_REQ_DONE;
+				return HTTPD_REQ_OK;
+
+			default:
+				break;
+			}
+
+			if (*priv->post_boundary != '\0') {
+				priv->req_state = ST_REQ_MPBOUNDARY;
+				return HTTPD_REQ_OK;
+			}
+			/* NOT multipart/form-data POST request */
+			return HTTPD_BAD_REQ;
+		}
+
+		if (priv->method != HTTP_POST)
+			return HTTPD_REQ_OK;
+
+		len = strlen("Content-Length: ");
+		if (strncasecmp(line, "Content-Length: ", len) == 0) {
+			data = line + len;
+			priv->post_flen = simple_strtol(data, &end, 10);
+			if (*end != '\0') {
+				/* bad Content-Length string */
+				return HTTPD_BAD_REQ;
+			}
+			return HTTPD_REQ_OK;
+		}
+
+		len = strlen("Content-Type: ");
+		if (strncasecmp(line, "Content-Type: ", len) == 0) {
+			data = strstr(line + len, " boundary=");
+			if (data == NULL) {
+				/* expect multipart/form-data format */
+				return HTTPD_BAD_REQ;
+			}
+
+			data += strlen(" boundary=");
+			if (strlen(data) >= sizeof(priv->post_boundary)) {
+				/* no space to keep boundary */
+				return HTTPD_BAD_REQ;
+			}
+
+			strcpy(priv->post_boundary, data);
+			return HTTPD_REQ_OK;
+		}
+
+		return HTTPD_REQ_OK;
+
+	case ST_REQ_MPBOUNDARY:
+		if (*line == '\0')
+			return HTTPD_REQ_OK;
+		if ((line[0] != '-') || (line[1] != '-') ||
+		    (strcmp(line + 2, priv->post_boundary) != 0)) {
+			/* expect boundary line */
+			return HTTPD_BAD_REQ;
+		}
+		priv->req_state = ST_REQ_MPART;
+		return HTTPD_REQ_OK;
+
+	case ST_REQ_MPART:
+		if (*line == '\0') {
+			if (*priv->post_name == '\0')
+				return HTTPD_BAD_REQ;
+
+			priv->post_fstart = priv->rx_processed + 2;
+			priv->post_flen -= priv->post_fstart - priv->hdr_len;
+			/* expect: "\r\n--${boundary}--\r\n", so strlen() + 8 */
+			priv->post_flen -= strlen(priv->post_boundary) + 8;
+
+			if (cfg->pre_post != NULL) {
+				post.addr     = NULL;
+				post.name     = priv->post_name;
+				post.filename = priv->post_fname;
+				post.size     = priv->post_flen;
+
+				ret = cfg->pre_post(priv, priv->url, &post);
+				if (ret != HTTPD_REQ_OK)
+					return ret;
+			}
+
+			tsize_num_hash = 0;
+			printf("File: %s, %u bytes\n", priv->post_fname, priv->post_flen);
+			printf("Loading: ");
+
+			priv->req_state = ST_REQ_MPFILE;
+			return HTTPD_REQ_OK;
+		}
+
+		len = strlen("Content-Disposition: ");
+		if (strncasecmp(line, "Content-Disposition: ", len) == 0) {
+			data = strstr(line + len, " name=\"");
+			if (data == NULL) {
+				/* name attribute not found */
+				return HTTPD_BAD_REQ;
+			}
+
+			data += strlen(" name=\"");
+			end = strstr(data, "\"");
+			if (end == NULL) {
+				/* bad name attribute format */
+				return HTTPD_BAD_REQ;
+			}
+
+			tmp = end - data;
+			if (tmp >= sizeof(priv->post_name)) {
+				/* multipart name is too long */
+				return HTTPD_BAD_REQ;
+			}
+			strncpy(priv->post_name, data, tmp);
+			priv->post_name[tmp] = '\0';
+
+			data = strstr(line + len, " filename=\"");
+			if (data == NULL) {
+				/* filename attribute not found */
+				return HTTPD_BAD_REQ;
+			}
+
+			data += strlen(" filename=\"");
+			end = strstr(data, "\"");
+			if (end == NULL) {
+				/* bad filename attribute format */
+				return HTTPD_BAD_REQ;
+			}
+
+			tmp = end - data;
+			if (tmp >= sizeof(priv->post_fname))
+				tmp = sizeof(priv->post_fname) - 1;
+			strncpy(priv->post_fname, data, tmp);
+			priv->post_fname[tmp] = '\0';
+			return HTTPD_REQ_OK;
+		}
+
+		return HTTPD_REQ_OK;
+
+	case ST_REQ_MPEND:
+		if (*line == '\0')
+			return HTTPD_REQ_OK;
+
+		len = strlen(priv->post_boundary);
+		if ((line[0] != '-') || (line[1] != '-') ||
+		    (strncmp(line + 2, priv->post_boundary, len) != 0) ||
+		    (line[len + 2] != '-') || (line[len + 3] != '-') ||
+		    (line[len + 4] != '\0')) {
+			/* expect final boundary line */
+			return HTTPD_BAD_REQ;
+		}
+		priv->req_state = ST_REQ_DONE;
+		return HTTPD_REQ_OK;
+
+	default:
+		return HTTPD_BAD_REQ;
+	}
+}
+
+static enum httpd_req_check http_parse_buf(struct httpd_priv *priv,
+					   char *buf, u32 size)
+{
+	char			*eol_pos;
+	u32			len;
+	enum httpd_req_check	ret;
+
+	buf[size] = '\0';
+	while (size > 0) {
+		eol_pos = strstr(buf, "\r\n");
+		if (eol_pos == NULL)
+			break;
+
+		*eol_pos = '\0';
+		len = eol_pos + 2 - buf;
+
+		ret = http_parse_line(priv, buf);
+		if (ret != HTTPD_REQ_OK) {
+			/* request processing error */
+			return ret;
+		}
+
+		priv->rx_processed += len;
+		buf += len;
+		size -= len;
+
+		if ((priv->req_state == ST_REQ_MPFILE) ||
+		    (priv->req_state == ST_REQ_DONE))
+			return HTTPD_REQ_OK;
+	}
+	/* continue when more data becomes available */
+	return HTTPD_REQ_OK;
+}
+
+static void tcp_stream_on_rcv_nxt_update(struct tcp_stream *tcp, u32 rx_bytes)
+{
+	struct httpd_priv	*priv;
+	void			*ptr;
+	u32			shift, size;
+	enum httpd_req_check	ret;
+	struct http_reply	*reply;
+	struct httpd_post_data	post;
+
+	priv = tcp->priv;
+
+	switch (priv->req_state) {
+	case ST_REQ_DONE:
+		return;
+
+	case ST_REQ_MPFILE:
+		show_block_marker(rx_bytes - priv->post_fstart,
+				  priv->post_flen);
+		if (rx_bytes < priv->post_fstart + priv->post_flen) {
+			priv->rx_processed = rx_bytes;
+			return;
+		}
+		priv->req_state = ST_REQ_MPEND;
+		priv->rx_processed = priv->post_fstart + priv->post_flen;
+		fallthrough;
+
+	case ST_REQ_MPEND:
+		shift = priv->rx_processed - priv->post_fstart;
+		ptr = map_sysmem(image_load_addr + shift,
+				 rx_bytes - priv->rx_processed);
+		ret = http_parse_buf(priv, ptr,
+				     rx_bytes - priv->rx_processed);
+		unmap_sysmem(ptr);
+
+		if (ret != HTTPD_REQ_OK)
+			goto error;
+		if (priv->req_state != ST_REQ_DONE)
+			return;
+		break;
+
+	default:
+		ret = http_parse_buf(priv, priv->buf + priv->rx_processed,
+				     rx_bytes - priv->rx_processed);
+		if (ret != HTTPD_REQ_OK)
+			goto error;
+
+		if (priv->req_state == ST_REQ_MPFILE) {
+			/*
+			 * We just switched from parsing of HTTP request
+			 * headers to binary data reading. Our tcp->rx
+			 * handler may put some binary data to priv->buf.
+			 * It's time to copy these data to a proper place.
+			 * It's required to copy whole buffer data starting
+			 * from priv->rx_processed position. Otherwise we
+			 * may miss data placed after the first hole.
+			 */
+			size = sizeof(priv->buf) - priv->rx_processed;
+			if (size > 0) {
+				ptr = map_sysmem(image_load_addr, size);
+				memcpy(ptr, priv->buf + priv->rx_processed, size);
+				unmap_sysmem(ptr);
+			}
+
+			show_block_marker(rx_bytes - priv->post_fstart,
+					  priv->post_flen);
+		}
+
+		if (priv->req_state != ST_REQ_DONE)
+			return;
+		break;
+	}
+
+	switch (priv->method) {
+	case HTTP_OPTIONS:
+		reply = &options_reply;
+		break;
+
+	case HTTP_GET:
+	case HTTP_HEAD:
+		if (cfg->get == NULL) {
+			ret = HTTPD_BAD_REQ;
+			goto error;
+		}
+		reply = cfg->get(priv, priv->url);
+		break;
+
+	case HTTP_POST:
+		if (cfg->post == NULL) {
+			ret = HTTPD_BAD_REQ;
+			goto error;
+		}
+		post.name     = priv->post_name;
+		post.filename = priv->post_fname;
+		post.size     = priv->post_flen;
+		post.addr     = map_sysmem(image_load_addr, post.size);
+		reply = cfg->post(priv, priv->url, &post);
+		unmap_sysmem(post.addr);
+		break;
+
+	default:
+		ret = HTTPD_BAD_REQ;
+		goto error;
+	}
+
+	http_make_reply(priv, reply, priv->method == HTTP_HEAD);
+	return;
+
+error:
+	priv->req_state = ST_REQ_DONE;
+	switch (ret) {
+	case HTTPD_BAD_URL:
+		http_make_reply(priv, cfg->error_404, 0);
+		break;
+	case HTTPD_BAD_REQ:
+		http_make_reply(priv, cfg->error_400, 0);
+		break;
+	default:
+		tcp_stream_reset(tcp);
+		break;
+	}
+}
+
+static u32 tcp_stream_rx(struct tcp_stream *tcp, u32 rx_offs, void *buf, u32 len)
+{
+	void			*ptr;
+	struct httpd_priv	*priv;
+	u32			shift;
+
+	priv = tcp->priv;
+	switch (priv->req_state) {
+	case ST_REQ_DONE:
+		return len;
+	case ST_REQ_MPFILE:
+	case ST_REQ_MPEND:
+		shift = rx_offs - priv->post_fstart;
+		ptr = map_sysmem(image_load_addr + shift, len);
+		memcpy(ptr, buf, len);
+		unmap_sysmem(ptr);
+		return len;
+	default:
+		/*
+		 * accept data that fits to buffer,
+		 * reserve space for end of line symbol
+		 */
+		if (rx_offs + len > sizeof(priv->buf) - 1)
+			len = sizeof(priv->buf) - rx_offs - 1;
+		memcpy(priv->buf + rx_offs, buf, len);
+		return len;
+	}
+}
+
+static void tcp_stream_on_snd_una_update(struct tcp_stream *tcp, u32 tx_bytes)
+{
+	struct httpd_priv *priv;
+
+	priv = tcp->priv;
+	if ((priv->req_state == ST_REQ_DONE) &&
+	    (tx_bytes == priv->reply_fstart + priv->reply_flen))
+		tcp_stream_close(tcp);
+}
+
+static u32 tcp_stream_tx(struct tcp_stream *tcp, u32 tx_offs, void *buf, u32 maxlen)
+{
+	struct httpd_priv	*priv;
+	u32			len, bytes = 0;
+	char			*ptr;
+
+	priv = tcp->priv;
+	if (priv->req_state != ST_REQ_DONE)
+		return 0;
+
+	if (tx_offs < priv->reply_fstart) {
+		len = maxlen;
+		if (len > priv->reply_fstart - tx_offs)
+			len = priv->reply_fstart - tx_offs;
+		memcpy(buf, priv->buf + tx_offs, len);
+		buf += len;
+		tx_offs += len;
+		bytes += len;
+		maxlen -= len;
+	}
+
+	if (tx_offs >= priv->reply_fstart) {
+		if (tx_offs + maxlen > priv->reply_fstart + priv->reply_flen)
+			maxlen = priv->reply_fstart + priv->reply_flen - tx_offs;
+		if (maxlen > 0) {
+			ptr = priv->reply_fdata + tx_offs - priv->reply_fstart;
+			memcpy(buf, ptr, maxlen);
+			bytes += maxlen;
+		}
+	}
+
+	return bytes;
+}
+
+static int tcp_stream_on_create(struct tcp_stream *tcp)
+{
+	struct httpd_priv	*priv;
+
+	if ((cfg == NULL) || stop_server ||
+	    (tcp->lport != HTTP_PORT))
+		return 0;
+
+	priv = malloc(sizeof(struct httpd_priv));
+	if (priv == NULL)
+		return 0;
+
+	memset(priv, 0, sizeof(struct httpd_priv));
+	priv->tcp = tcp;
+
+	tcp->priv = priv;
+	tcp->on_closed = tcp_stream_on_closed;
+	tcp->on_rcv_nxt_update = tcp_stream_on_rcv_nxt_update;
+	tcp->rx = tcp_stream_rx;
+	tcp->on_snd_una_update = tcp_stream_on_snd_una_update;
+	tcp->tx = tcp_stream_tx;
+	return 1;
+}
+
+void httpd_setup(struct httpd_config *config)
+{
+	cfg = config;
+}
+
+void httpd_stop(void)
+{
+	stop_server = 1;
+}
+
+void httpd_start(void)
+{
+	if (cfg == NULL) {
+		net_set_state(NETLOOP_FAIL);
+		return;
+	}
+	stop_server = 0;
+	memset(net_server_ethaddr, 0, 6);
+	tcp_stream_set_on_create_handler(tcp_stream_on_create);
+	printf("HTTPD listening on port %d...\n", HTTP_PORT);
+}
diff --git a/net/net.c b/net/net.c
index 809fe5c4792..ca761c57372 100644
--- a/net/net.c
+++ b/net/net.c
@@ -111,6 +111,7 @@ 
 #include <net/tcp.h>
 #include <net/wget.h>
 #include <net/netcat.h>
+#include <net/httpd.h>
 #include "arp.h"
 #include "bootp.h"
 #include "cdp.h"
@@ -575,6 +576,11 @@  restart:
 			netcat_store_start();
 			break;
 #endif
+#if defined(CONFIG_HTTPD_COMMON)
+		case HTTPD:
+			httpd_start();
+			break;
+#endif
 #if defined(CONFIG_CMD_CDP)
 		case CDP:
 			cdp_start();