mirror of
https://github.com/oopuuu/zTC1.git
synced 2025-12-16 15:08:15 +08:00
400 lines
9.9 KiB
C
400 lines
9.9 KiB
C
/**
|
|
******************************************************************************
|
|
* @file httpd_handle.c
|
|
* @author QQ DING
|
|
* @version V1.0.0
|
|
* @date 1-September-2015
|
|
* @brief This is the main processing of an HTTP request. There could be three
|
|
* types of requests, a WSGI, an SSI or a file. This file contains
|
|
* routines that deal with this.
|
|
******************************************************************************
|
|
*
|
|
* The MIT License
|
|
* Copyright (c) 2014 MXCHIP Inc.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is furnished
|
|
* to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*****************************************************************************
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include "httpd.h"
|
|
#include "http_parse.h"
|
|
#include "http-strings.h"
|
|
|
|
#include "httpd_priv.h"
|
|
|
|
#ifdef CONFIG_ENABLE_HTTPS
|
|
|
|
|
|
extern tls_handle_t httpd_tls_handle;
|
|
#endif /* ENABLE_HTTPS */
|
|
|
|
#define STATE_WAITING 0
|
|
#define STATE_OUTPUT 1
|
|
|
|
static httpd_request_t httpd_req;
|
|
|
|
/* Send the chunk header, which is just an ascii size followed by a cr lf.
|
|
*/
|
|
#define CHUNK_SIZE_DIGITS 0x10
|
|
int httpd_send_chunk_begin(int conn, int size)
|
|
{
|
|
|
|
int err;
|
|
char buf[CHUNK_SIZE_DIGITS];
|
|
int i;
|
|
int digit;
|
|
int begin = 1;
|
|
|
|
for (i = CHUNK_SIZE_DIGITS - 1; i >= 0; i--) {
|
|
digit = size & 0xf;
|
|
if (!begin && !size) {
|
|
i++;
|
|
break;
|
|
}
|
|
buf[i] = (digit > 9) ? digit - 0xA + 'a' : digit + '0';
|
|
size = size >> 4;
|
|
begin = 0;
|
|
}
|
|
err = httpd_send(conn, &buf[i], CHUNK_SIZE_DIGITS - i);
|
|
if (err != kNoErr) {
|
|
return err;
|
|
}
|
|
|
|
err = httpd_send_crlf(conn);
|
|
|
|
return err;
|
|
}
|
|
|
|
/* Send the last chunk which is simply an ascii "0\r\n\r\n"
|
|
*/
|
|
int httpd_send_last_chunk(int conn)
|
|
{
|
|
int err;
|
|
|
|
err = httpd_send(conn, http_last_chunk,
|
|
sizeof(http_last_chunk) - 1);
|
|
|
|
if (err != kNoErr) {
|
|
httpd_d("Send last chunk failed");
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/* Send one chunk of data of a given size
|
|
*/
|
|
int httpd_send_chunk(int conn, const char *buf, int len)
|
|
{
|
|
int err;
|
|
|
|
if (len) {
|
|
/* Send chunk begin header */
|
|
err = httpd_send_chunk_begin(conn, len);
|
|
if (err != kNoErr)
|
|
return err;
|
|
/* Send our data */
|
|
err = httpd_send(conn, buf, len);
|
|
if (err != kNoErr)
|
|
return err;
|
|
/* Send chunk end indicator */
|
|
err = httpd_send_crlf(conn);
|
|
if (err != kNoErr)
|
|
return err;
|
|
} else {
|
|
/* Length is 0, last chunk */
|
|
err = httpd_send(conn, http_last_chunk,
|
|
sizeof(http_last_chunk) - 1);
|
|
if (err != kNoErr)
|
|
return err;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/*Helper function to send a buffer over a connection.
|
|
*/
|
|
int httpd_send(int conn, const char *buf, int len)
|
|
{
|
|
int num;
|
|
do {
|
|
#ifdef CONFIG_ENABLE_HTTPS
|
|
if (httpd_is_https_active())
|
|
num = tls_send(httpd_tls_handle, buf, len);
|
|
else
|
|
#endif /* ENABLE_HTTPS */
|
|
num = send(conn, buf, len, 0);
|
|
if (num < 0) {
|
|
httpd_d("send() failed: %d" ,conn);
|
|
return -kInProgressErr;
|
|
}
|
|
len -= num;
|
|
buf += num;
|
|
} while (len > 0);
|
|
|
|
|
|
return kNoErr;
|
|
}
|
|
|
|
int httpd_recv(int fd, void *buf, size_t n, int flags)
|
|
{
|
|
#ifdef CONFIG_ENABLE_HTTPS
|
|
if (httpd_is_https_active())
|
|
return tls_recv(httpd_tls_handle, buf, n);
|
|
else
|
|
#endif /* ENABLE_HTTPS */
|
|
return recv(fd, buf, n, flags);
|
|
}
|
|
|
|
int httpd_send_hdr_from_code(int sock, int stat_code,
|
|
enum http_content_type content_type)
|
|
{
|
|
const char *str;
|
|
unsigned int str_len;
|
|
/*
|
|
* Set the HTTP Response Status 200 OK, 404 NOT FOUND etc.
|
|
* Also set the Transfer-Encoding to chunked.
|
|
*/
|
|
switch (stat_code) {
|
|
case -WM_E_HTTPD_HANDLER_404:
|
|
str = http_header_404;
|
|
str_len = strlen(http_header_404);
|
|
break;
|
|
case -WM_E_HTTPD_HANDLER_400:
|
|
str = http_header_400;
|
|
str_len = strlen(http_header_400);
|
|
break;
|
|
case -WM_E_HTTPD_HANDLER_500:
|
|
str = http_header_500;
|
|
str_len = strlen(http_header_500);
|
|
break;
|
|
default:
|
|
/* The handler doesn't want an HTTP error code, but would return
|
|
* 200 OK with an error in the response */
|
|
str = http_header_200;
|
|
str_len = strlen(http_header_200);
|
|
break;
|
|
}
|
|
|
|
if (httpd_send(sock, str, str_len) != kNoErr) {
|
|
return -kInProgressErr;
|
|
}
|
|
|
|
/* Send the Content-Type */
|
|
switch (content_type) {
|
|
case HTTP_CONTENT_JSON:
|
|
str = http_content_type_json_nocache;
|
|
str_len = sizeof(http_content_type_json_nocache) - 1;
|
|
break;
|
|
case HTTP_CONTENT_XML:
|
|
str = http_content_type_xml_nocache;
|
|
str_len = sizeof(http_content_type_xml_nocache) - 1;
|
|
break;
|
|
case HTTP_CONTENT_HTML:
|
|
str = http_content_type_html;
|
|
str_len = sizeof(http_content_type_html) - 1;
|
|
break;
|
|
case HTTP_CONTENT_JPEG:
|
|
str = http_content_type_jpg;
|
|
str_len = sizeof(http_content_type_jpg) - 1;
|
|
break;
|
|
default:
|
|
str = http_content_type_plain;
|
|
str_len = sizeof(http_content_type_plain) - 1;
|
|
break;
|
|
}
|
|
if (httpd_send(sock, str, str_len) != kNoErr) {
|
|
return -kInProgressErr;
|
|
}
|
|
return kNoErr;
|
|
}
|
|
|
|
|
|
|
|
/* set the system wide error. */
|
|
static char httpd_error[HTTPD_MAX_ERROR_STRING + 1];
|
|
void httpd_set_error(const char *fmt, ...)
|
|
{
|
|
va_list argp;
|
|
|
|
va_start(argp, fmt);
|
|
vsnprintf(httpd_error, HTTPD_MAX_ERROR_STRING + 1, fmt, argp);
|
|
va_end(argp);
|
|
|
|
httpd_d("http set err");
|
|
}
|
|
|
|
/* Helper function to send an error.
|
|
*/
|
|
int httpd_send_error(int conn, int http_error)
|
|
{
|
|
int err = 0;
|
|
|
|
switch (http_error) {
|
|
case HTTP_404:
|
|
err = httpd_send(conn, http_header_404,
|
|
strlen(http_header_404));
|
|
break;
|
|
case HTTP_500:
|
|
err = httpd_send(conn, http_header_500,
|
|
strlen(http_header_500));
|
|
break;
|
|
|
|
case HTTP_505:
|
|
err = httpd_send(conn, http_header_505,
|
|
strlen(http_header_505));
|
|
break;
|
|
}
|
|
|
|
if (err != kNoErr) {
|
|
return err;
|
|
}
|
|
|
|
err = httpd_send(conn, http_header_type_chunked,
|
|
strlen(http_header_type_chunked));
|
|
if (err != kNoErr) {
|
|
return err;
|
|
}
|
|
|
|
err = httpd_send(conn, http_content_type_plain,
|
|
sizeof(http_content_type_plain) - 1);
|
|
if (err != kNoErr) {
|
|
return err;
|
|
}
|
|
|
|
err = httpd_send_crlf(conn);
|
|
if (err != kNoErr) {
|
|
return err;
|
|
}
|
|
|
|
err = httpd_send_chunk(conn, httpd_error, strlen(httpd_error));
|
|
if (err != kNoErr) {
|
|
return err;
|
|
}
|
|
return httpd_send_last_chunk(conn);
|
|
}
|
|
|
|
|
|
/*
|
|
* @pre Only first line of HTTP header (which has the resource name) has
|
|
* been read from the socket. We need to read the remaining header and the
|
|
* data after that.
|
|
*/
|
|
void httpd_purge_socket_data(httpd_request_t *req, char *msg_in,
|
|
int msg_in_len, int conn)
|
|
{
|
|
int status = httpd_parse_hdr_tags(req, conn, msg_in, msg_in_len);
|
|
if (status != kNoErr) {
|
|
/* We were unsuccessful in purging the socket.*/
|
|
httpd_d("Unable to purge socket: %d", status);
|
|
return;
|
|
}
|
|
|
|
unsigned data_remaining = req->body_nbytes;
|
|
|
|
while (data_remaining) {
|
|
unsigned to_read = msg_in_len >= data_remaining ?
|
|
data_remaining : msg_in_len;
|
|
int actually_read = httpd_recv(conn, msg_in, to_read, 0);
|
|
if (actually_read < 0) {
|
|
httpd_d("Unable to read content."
|
|
"Was purging socket data");
|
|
return;
|
|
}
|
|
data_remaining -= actually_read;
|
|
}
|
|
}
|
|
|
|
/* Handle an incoming message (request) from the client. This is the
|
|
* main processing function of the HTTPD.
|
|
*/
|
|
int httpd_handle_message(int conn)
|
|
{
|
|
int err;
|
|
int req_line_len;
|
|
char msg_in[128];
|
|
|
|
/* clear out the httpd_req structure */
|
|
memset(&httpd_req, 0x00, sizeof(httpd_req));
|
|
|
|
httpd_req.sock = conn;
|
|
|
|
/* Read the first line of the HTTP header */
|
|
req_line_len = htsys_getln_soc(conn, msg_in, sizeof(msg_in));
|
|
if (req_line_len == 0)
|
|
return HTTPD_DONE;
|
|
|
|
if (req_line_len < 0) {
|
|
httpd_d("Could not read from socket");
|
|
return -kInProgressErr;
|
|
}
|
|
|
|
/* Parse the first line of the header */
|
|
err = httpd_parse_hdr_main(msg_in, &httpd_req);
|
|
if (err == -WM_E_HTTPD_NOTSUPP)
|
|
/* Send 505 HTTP Version not supported */
|
|
return httpd_send_error(conn, HTTP_505);
|
|
else if (err != kNoErr) {
|
|
/* Send 500 Internal Server Error */
|
|
return httpd_send_error(conn, HTTP_500);
|
|
}
|
|
|
|
/* set a generic error that can be overridden by the wsgi handling. */
|
|
httpd_d("Presetting");
|
|
|
|
/* Web Services Gateway Interface branch point:
|
|
* At this point we have the request type (httpd_req.type) and the path
|
|
* (httpd_req.filename) and all the headers waiting to be read from
|
|
* the socket.
|
|
*
|
|
* The call bellow will iterate through all the url patterns and
|
|
* invoke the handlers that match the request type and pattern. If
|
|
* request type and url patern match, invoke the handler.
|
|
*/
|
|
err = httpd_wsgi(&httpd_req);
|
|
|
|
if (err == HTTPD_DONE) {
|
|
httpd_d("Done processing request.");
|
|
return kNoErr;
|
|
} else if (err == -WM_E_HTTPD_NO_HANDLER) {
|
|
httpd_d("No handler for the given URL %s was found",
|
|
httpd_req.filename);
|
|
/*
|
|
* We have not yet read the complete data from the current
|
|
* request, from the socket. We are in an error state and
|
|
* we wish to cancel this HTTP transaction. We sent
|
|
* appropriate message to the client and read (flush) out
|
|
* all the pending data in the socket. We let the client
|
|
* close the socket for us, if necessary.
|
|
*/
|
|
httpd_purge_socket_data(&httpd_req, msg_in,
|
|
sizeof(msg_in), conn);
|
|
httpd_set_error("File %s not_found", httpd_req.filename);
|
|
httpd_send_error(conn, HTTP_404);
|
|
return kNoErr;
|
|
} else {
|
|
httpd_d("WSGI handler failed.");
|
|
/* Send 500 Internal Server Error */
|
|
return httpd_send_error(conn, HTTP_500);
|
|
}
|
|
|
|
}
|