text: /*
   * Copyright (c) 2017 Intel Corporation.
   *
   * SPDX-License-Identifier: Apache-2.0
   */
  
  #if defined(CONFIG_NET_DEBUG_HTTP)
  #define SYS_LOG_DOMAIN "http"
  #define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG
  #define NET_LOG_ENABLED 1
  #endif
  
  #include <zephyr.h>
  #include <string.h>
  #include <strings.h>
  #include <errno.h>
  #include <stdlib.h>
  #include <version.h>
  #include <misc/printk.h>
  
  #include <net/net_core.h>
  #include <net/net_ip.h>
  #include <net/http.h>
  
  int http_set_cb(struct http_ctx *ctx,
  		http_connect_cb_t connect_cb,
  		http_recv_cb_t recv_cb,
  		http_send_cb_t send_cb,
  		http_close_cb_t close_cb)
  {
  	if (!ctx) {
  		return -EINVAL;
  	}
  
  	if (!ctx->is_init) {
  		return -ENOENT;
  	}
  
  	ctx->cb.connect = connect_cb;
  	ctx->cb.recv = recv_cb;
  	ctx->cb.send = send_cb;
  	ctx->cb.close = close_cb;
  
  	return 0;
  }
  
  int http_close(struct http_ctx *ctx)
  {
  	if (!ctx) {
  		return -EINVAL;
  	}
  
  	if (!ctx->is_init) {
  		return -ENOENT;
  	}
  
  	http_send_flush(ctx, NULL);
  	if (ctx->pending) {
  		net_pkt_unref(ctx->pending);
  		ctx->pending = NULL;
  	}
  
  #if defined(CONFIG_HTTP_SERVER) && defined(CONFIG_NET_DEBUG_HTTP_CONN)
  	if (!ctx->is_client) {
  		http_server_conn_del(ctx);
  	}
  #endif
  
  #if defined(CONFIG_HTTP_SERVER) && defined(CONFIG_WEBSOCKET)
  	if (ctx->websocket.pending) {
  		net_pkt_unref(ctx->websocket.pending);
  		ctx->websocket.pending = NULL;
  	}
  
  	ctx->websocket.data_waiting = 0;
  #endif
  
  	return net_app_close(&ctx->app_ctx);
  }
  
  int http_release(struct http_ctx *ctx)
  {
  	if (!ctx) {
  		return -EINVAL;
  	}
  
  	if (!ctx->is_init) {
  		return -ENOENT;
  	}
  
  	ctx->is_tls = false;
  
  #if defined(CONFIG_HTTP_SERVER) && defined(CONFIG_NET_DEBUG_HTTP_CONN)
  	if (!ctx->is_client) {
  		http_server_conn_del(ctx);
  		http_server_disable(ctx);
  	}
  #endif
  
  	if (ctx->pending) {
  		net_pkt_unref(ctx->pending);
  		ctx->pending = NULL;
  	}
  
  	ctx->is_init = false;
  
  	return net_app_release(&ctx->app_ctx);
  }
  
  int http_send_msg_raw(struct http_ctx *ctx, struct net_pkt *pkt,
  		      void *user_send_data)
  {
  	int ret;
  
  	NET_DBG("[%p] Sending %zd bytes data", ctx, net_pkt_get_len(pkt));
  
  	ret = net_app_send_pkt(&ctx->app_ctx, pkt, NULL, 0, 0,
  			       user_send_data);
  	if (!ret) {
  		/* We must let the system to send the packet, otherwise TCP
  		 * might timeout before the packet is actually sent. This is
  		 * easily seen if the application calls this functions many
  		 * times in a row.
  		 */
  		k_yield();
  	}
  
  	return ret;
  }
  
  static inline struct net_pkt *get_net_pkt(struct http_ctx *ctx,
  					  const struct sockaddr *dst)
  {
  	if (!dst) {
  		return net_app_get_net_pkt(&ctx->app_ctx, AF_UNSPEC,
  					   ctx->timeout);
  	}
  
  	return net_app_get_net_pkt_with_dst(&ctx->app_ctx, dst, ctx->timeout);
  }
  
  int http_prepare_and_send(struct http_ctx *ctx,
  			  const char *payload,
  			  size_t payload_len,
  			  const struct sockaddr *dst,
  			  void *user_send_data)
  {
  	size_t added;
  	int ret;
  
  	do {
  		if (!ctx->pending) {
  			ctx->pending = get_net_pkt(ctx, dst);
  			if (!ctx->pending) {
  				return -ENOMEM;
  			}
  		}
  
  		ret = net_pkt_append(ctx->pending, payload_len, payload,
  				     ctx->timeout);
  		if (!ret || ret > payload_len) {
  			ret = -EINVAL;
  			goto error;
  		}
  
  		added = ret;
  
  		payload_len -= added;
  		if (payload_len) {
  			payload += added;
  			/* Not all data could be added, send what we have now
  			 * and allocate new stuff to be sent.
  			 */
  			ret = http_send_flush(ctx, user_send_data);
  			if (ret < 0) {
  				goto error;
  			}
  		}
  	} while (payload_len);
  
  	return 0;
  
  error:
  	if (ctx->pending) {
  		net_pkt_unref(ctx->pending);
  		ctx->pending = NULL;
  	}
  
  	return ret;
  }
  
  int http_send_flush(struct http_ctx *ctx, void *user_send_data)
  {
  	int ret;
  
  	if (!ctx->pending) {
  		return 0;
  	}
  
  	ret = http_send_msg_raw(ctx, ctx->pending, user_send_data);
  	if (ret < 0) {
  		return ret;
  	}
  
  	ctx->pending = NULL;
  
  	return ret;
  }
  
  int http_send_chunk(struct http_ctx *ctx, const char *buf, size_t len,
  		    const struct sockaddr *dst, void *user_send_data)
  {
  	char chunk_header[16];
  	int ret;
  
  	if (!buf) {
  		len = 0;
  	}
  
  	snprintk(chunk_header, sizeof(chunk_header), "%x" HTTP_CRLF,
  		 (unsigned int)len);
  
  	ret = http_prepare_and_send(ctx, chunk_header, strlen(chunk_header),
  				    dst, user_send_data);
  	if (ret < 0) {
  		return ret;
  	}
  
  	if (len) {
  		ret = http_prepare_and_send(ctx, buf, len, dst, user_send_data);
  		if (ret < 0) {
  			return ret;
  		}
  	}
  
  	ret = http_prepare_and_send(ctx, HTTP_CRLF, sizeof(HTTP_CRLF) - 1, dst,
  				    user_send_data);
  	if (ret < 0) {
  		return ret;
  	}
  
  	return 0;
  }
  
  static int _http_add_header(struct http_ctx *ctx, s32_t timeout,
  			    const char *name, const char *value,
  			    const struct sockaddr *dst,
  			    void *user_send_data)
  {
  	int ret;
  
  	ret = http_prepare_and_send(ctx, name, strlen(name), dst,
  				    user_send_data);
  	if (value && ret >= 0) {
  		ret = http_prepare_and_send(ctx, ": ", strlen(": "), dst,
  					    user_send_data);
  		if (ret < 0) {
  			goto out;
  		}
  
  		ret = http_prepare_and_send(ctx, value, strlen(value), dst,
  					    user_send_data);
  		if (ret < 0) {
  			goto out;
  		}
  
  		ret = http_prepare_and_send(ctx, HTTP_CRLF, strlen(HTTP_CRLF),
  					    dst, user_send_data);
  		if (ret < 0) {
  			goto out;
  		}
  	}
  
  out:
  	return ret;
  }
  
  int http_add_header(struct http_ctx *ctx, const char *field,
  		    const struct sockaddr *dst,
  		    void *user_send_data)
  {
  	return _http_add_header(ctx, ctx->timeout, field, NULL, dst,
  				user_send_data);
  }
  
  int http_add_header_field(struct http_ctx *ctx, const char *name,
  			  const char *value,
  			  const struct sockaddr *dst,
  			  void *user_send_data)
  {
  	return _http_add_header(ctx, ctx->timeout, name, value, dst,
  				user_send_data);
  }
  