/*
* File : wn_module_upload.c
* This file is part of RT-Thread RTOS
* COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
*
* This software is dual-licensed: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation. For the terms of this
* license, see .
*
* You are free to use this software under the terms of the GNU General
* Public License, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* Alternatively for commercial application, you can contact us
* by email for commercial license.
*
* Change Logs:
* Date Author Notes
* 2012-06-25 Bernard the first version
*/
#include
#include
#include
#ifdef RT_USING_DFS
#if RT_VER_NUM >= 0x40100
#include /* fix O_WRONLY */
#else
#include
#endif /*RT_VER_NUM >= 0x40100*/
#endif
#if defined(WEBNET_USING_UPLOAD)
#define MULTIPART_FORM_DATA_STRING "multipart/form-data"
#define BOUNDARY_STRING "boundary="
#define CONTENT_DISPOSITION_STRING "Content-Disposition:"
#define CONTENT_TYPE_STRING "Content-Type:"
#define CONTENT_RANGE_STRING "Content-Range:"
#define FORM_DATA_STRING "form-data"
#define FILENAME_STRING "filename=\""
#define FIELDNAME_STRING "name=\""
#define FIRST_BOUNDARY 2, upload_session->boundary, "\r\n"
#define NORMAL_BOUNDARY 3, "\r\n", upload_session->boundary, "\r\n"
#define COMMON_BOUNDARY 2, "\r\n", upload_session->boundary
#define LAST_BOUNDARY 3, "\r\n", upload_session->boundary, "--\r\n"
#define FIRST_BOUNDARY_SIZE (strlen(upload_session->boundary) + 2)
#define NORMAL_BOUNDARY_SIZE (strlen(upload_session->boundary) + 4)
#define COMMON_BOUNDARY_SIZE (strlen(upload_session->boundary) + 2)
#define LAST_BOUNDARY_SIZE (strlen(upload_session->boundary) + 6)
struct webnet_upload_name_entry
{
char *name;
char *value;
};
static const struct webnet_module_upload_entry **_upload_entries = RT_NULL;
static rt_uint16_t _upload_entries_count = 0;
struct webnet_module_upload_session
{
char* boundary;
char* filename;
char* content_type;
struct webnet_upload_name_entry* name_entries;
rt_uint16_t name_entries_count;
rt_uint16_t file_opened;
/* upload entry */
const struct webnet_module_upload_entry* entry;
/* user data */
rt_uint32_t user_data;
};
static int str_begin_with_strs(const char* str, int num, ...)
{
char *match;
va_list args;
int index, result;
result = 1;
va_start(args,num);
for (index = 0; index < num; index ++)
{
match = va_arg(args, char*);
if (strncasecmp(str, match, strlen(match)) != 0)
{
result = 0;
break;
}
str += strlen(match);
}
va_end(args);
return result;
}
/* Search string on binary data */
static char *memstr(const char *haystack, size_t length, const char *needle)
{
size_t nl=strlen(needle);
size_t hl=length;
size_t i;
if (!nl) goto __found;
if (nl > length) return 0;
for (i=hl-nl+1; i; --i)
{
if (*haystack==*needle && !memcmp(haystack,needle,nl))
__found:
return (char*)haystack;
++haystack;
}
return 0;
}
/* Search string array on binary data */
static char* memstrs(const char* str, size_t length, int num, ...)
{
char *substr;
char *index_str, *ptr;
va_list args;
int index;
int total_size;
/* get the match total string length */
va_start(args, num);
total_size = 0;
for (index = 0; index < num; index ++)
{
index_str = va_arg(args, char*);
total_size += strlen(index_str);
}
va_end(args);
substr = wn_malloc(total_size + 1);
ptr = substr;
va_start(args, num);
for (index = 0; index < num; index++)
{
index_str = va_arg(args, char*);
rt_memcpy(ptr, index_str, strlen(index_str));
ptr += strlen(index_str);
}
substr[total_size] = '\0';
va_end(args);
/* find in binary data */
ptr = memstr(str, length, substr);
wn_free(substr);
return ptr;
}
#if !(defined(__GNUC__) && !defined(__ARMCC_VERSION)/*GCC*/)
static void* memrchr(const void *s, int c, size_t n)
{
register const char* t=s;
register const char* last=0;
int i;
t = t + n;
for (i=n; i; --i)
{
if (*t==c)
{
last=t;
break;
}
t--;
}
return (void*)last; /* man, what an utterly b0rken prototype */
}
#endif
static char* _webnet_module_upload_parse_header(struct webnet_session* session, char* buffer, rt_size_t length)
{
char *ptr, *ch, *end_ptr;
struct webnet_module_upload_session *upload_session;
char *name = RT_NULL, *filename = RT_NULL, *content_type = RT_NULL;
/* get upload session */
upload_session = (struct webnet_module_upload_session *)session->user_data;
ptr = buffer;
end_ptr = buffer + length;
/* begin with last boundary */
if (str_begin_with_strs(ptr, LAST_BOUNDARY))
{
ptr += LAST_BOUNDARY_SIZE;
return ptr;
}
/* begin with normal boundary */
if (str_begin_with_strs(ptr, NORMAL_BOUNDARY))
ptr += NORMAL_BOUNDARY_SIZE;
else if (str_begin_with_strs(ptr, FIRST_BOUNDARY))
ptr += FIRST_BOUNDARY_SIZE;
if (ptr == buffer) return RT_NULL; /* not begin with boundary */
if (upload_session->filename != RT_NULL &&
upload_session->content_type != RT_NULL)
{
/* end of file name section */
wn_free(upload_session->filename);
wn_free(upload_session->content_type);
upload_session->filename = RT_NULL;
upload_session->content_type = RT_NULL;
}
while ((rt_uint32_t)ptr - (rt_uint32_t)buffer < length)
{
/* handle Content-Disposition: */
if (str_begin_with(ptr, CONTENT_DISPOSITION_STRING))
{
ptr += sizeof(CONTENT_DISPOSITION_STRING) - 1;
while (*ptr == ' ') ptr ++;
/* form-data */
if (str_begin_with(ptr, FORM_DATA_STRING))
{
ptr += sizeof(FORM_DATA_STRING) - 1;
while (*ptr == ' ' || *ptr == ';') ptr ++;
/* get name="str" */
if (str_begin_with(ptr, FIELDNAME_STRING))
{
ptr += sizeof(FIELDNAME_STRING) - 1;
name = ptr;
ch = memchr(ptr, '"', buffer - ptr);
if (ch != RT_NULL)
{
*ch = '\0';
ch ++;
while (*ch == ' ' || *ch ==';') ch ++;
ptr = ch;
}
}
/* get filename="str" */
if (str_begin_with(ptr, FILENAME_STRING))
{
ptr += sizeof(FILENAME_STRING) - 1;
filename = ptr;
ch = memchr(ptr, '"', buffer - ptr);
if (ch != RT_NULL)
{
*ch = '\0';
ch ++;
ptr = ch;
}
}
}
}
/* handle Content-Type */
else if (str_begin_with(ptr, CONTENT_TYPE_STRING))
{
ptr += sizeof(CONTENT_TYPE_STRING) - 1;
while (*ptr == ' ') ptr ++;
content_type = ptr;
}
/* handle Content-Range: */
else if (str_begin_with(ptr, CONTENT_RANGE_STRING))
{
ptr += sizeof(CONTENT_RANGE_STRING) - 1;
while (*ptr == ' ') ptr ++;
}
/* whether end of header */
else if (str_begin_with(ptr, "\r\n"))
{
ptr += 2;
if (upload_session->filename != RT_NULL)
{
wn_free(upload_session->filename);
upload_session->filename = RT_NULL;
}
if (upload_session->content_type != RT_NULL)
{
wn_free(upload_session->content_type);
upload_session->content_type = RT_NULL;
}
if (filename != RT_NULL)
{
upload_session->filename = wn_strdup(filename);
}
if (content_type != RT_NULL)
{
upload_session->content_type = wn_strdup(content_type);
}
if (name != RT_NULL)
{
/* add a name entry in the upload session */
if (upload_session->name_entries == RT_NULL)
{
upload_session->name_entries_count = 1;
upload_session->name_entries = (struct webnet_upload_name_entry*)
wn_malloc(sizeof(struct webnet_upload_name_entry));
}
else
{
upload_session->name_entries_count += 1;
upload_session->name_entries = (struct webnet_upload_name_entry*)
wn_realloc(upload_session->name_entries,
sizeof(struct webnet_upload_name_entry) * upload_session->name_entries_count);
}
upload_session->name_entries[upload_session->name_entries_count - 1].name = wn_strdup(name);
upload_session->name_entries[upload_session->name_entries_count - 1].value = RT_NULL;
}
return ptr;
}
/* skip this request header line */
ch = memstr(ptr, end_ptr - ptr, "\r\n");
*ch = '\0';
ch ++;
*ch = '\0';
ch ++;
ptr = ch;
}
return ptr;
}
static char* _next_possible_boundary(struct webnet_session* session, char* buffer, rt_size_t length)
{
char *ptr;
struct webnet_module_upload_session *upload_session;
/* get upload session */
upload_session = (struct webnet_module_upload_session *)session->user_data;
/* try the beginning */
if (str_begin_with_strs(buffer, COMMON_BOUNDARY)) return buffer;
/* search in all the string */
ptr = memstrs(buffer, length, COMMON_BOUNDARY);
if (ptr != RT_NULL) return ptr;
/* search from the end of string */
ptr = memrchr(buffer, '\r', length);
if (ptr == RT_NULL ) return RT_NULL;
if (ptr - buffer == length - 1) return ptr; /* only \r */
if (*(ptr + 1) == '\n' && ptr - buffer == length - 2) return RT_NULL; /* only \r\n */
if (memcmp(ptr + 2, upload_session->boundary, length - (ptr - buffer + 2)) == 0) return ptr;
return RT_NULL;
}
static void _handle_section(struct webnet_session* session, char* buffer, rt_size_t length)
{
char *ptr;
struct webnet_module_upload_session *upload_session;
#define name_entry \
(upload_session->name_entries[upload_session->name_entries_count - 1])
/* get upload session */
upload_session = (struct webnet_module_upload_session *)session->user_data;
if (upload_session->filename != RT_NULL &&
upload_session->content_type != RT_NULL&&
length != 0)
{
upload_session->entry->upload_write(session, buffer, length);
}
else
{
/* get name value */
ptr = memstr(buffer, length, "\r\n");
if (ptr != RT_NULL)
{
int value_size = ptr - buffer + 1;
name_entry.value = wn_malloc(value_size);
rt_memcpy(name_entry.value, buffer, value_size - 1);
name_entry.value[value_size - 1] = '\0';
}
}
}
static void _webnet_module_upload_handle(struct webnet_session* session, int event)
{
int length;
char *ptr, *end_ptr;
char *upload_buffer;
rt_uint32_t chunk_size;
struct webnet_module_upload_session *upload_session;
if (event != WEBNET_EVENT_READ) return;
/* get upload session */
upload_session = (struct webnet_module_upload_session *)session->user_data;
upload_buffer = (char*)session->buffer;
/* read stream */
if (memstrs((const char *)session->buffer, session->buffer_offset, LAST_BOUNDARY))
{
length = session->buffer_offset;
}
else
{
if (session->buffer_offset != 0)
{
length = webnet_session_read(session,
(char*)&session->buffer[session->buffer_offset],
sizeof(session->buffer) - session->buffer_offset - 1);
if (length > 0)
{
length += session->buffer_offset;
}
else
{
length = session->buffer_offset;
}
}
else
{
length = webnet_session_read(session, (char *)session->buffer, sizeof(session->buffer) - 1);
}
}
/* connection break out */
if (length <= 0)
{
/* read stream failed (connection break out), close this session */
session->session_phase = WEB_PHASE_CLOSE;
return;
}
end_ptr = (char*)(session->buffer + length);
session->buffer[length] = '\0';
while (upload_buffer < end_ptr)
{
if (str_begin_with_strs(upload_buffer, LAST_BOUNDARY))
{
/* upload done */
upload_session->entry->upload_done(session);
session->session_phase = WEB_PHASE_CLOSE;
return;
}
/* read more data */
if (end_ptr - upload_buffer < sizeof(session->buffer)/3 &&
memstrs(upload_buffer, end_ptr - upload_buffer, LAST_BOUNDARY) == RT_NULL)
{
/* read more data */
rt_memmove(session->buffer, upload_buffer, end_ptr - upload_buffer);
session->buffer_offset = end_ptr - upload_buffer;
return ;
}
ptr = _webnet_module_upload_parse_header(session, upload_buffer,
(rt_uint32_t)end_ptr - (rt_uint32_t)upload_buffer);
if (ptr == RT_NULL)
{
/* not begin with a boundary */
ptr = _next_possible_boundary(session, upload_buffer,
(rt_uint32_t)end_ptr - (rt_uint32_t)upload_buffer);
if (ptr == RT_NULL)
{
/* all are the data section */
_handle_section(session, upload_buffer, (rt_uint32_t)end_ptr - (rt_uint32_t)upload_buffer);
upload_buffer = end_ptr;
}
else
{
chunk_size = (rt_uint32_t)ptr - (rt_uint32_t)upload_buffer;
_handle_section(session, upload_buffer, chunk_size);
upload_buffer += chunk_size;
}
}
else
{
if (upload_session->filename != RT_NULL &&
upload_session->content_type != RT_NULL)
{
if (upload_session->file_opened == 0)
{
/* open file */
upload_session->user_data = upload_session->entry->upload_open(session);
upload_session->file_opened = 1;
}
}
else
{
if (upload_session->file_opened == 1)
{
/* close file */
upload_session->entry->upload_close(session);
upload_session->user_data = 0;
upload_session->file_opened = 0;
}
}
upload_buffer = ptr;
ptr = _next_possible_boundary(session, upload_buffer,
(rt_uint32_t)end_ptr - (rt_uint32_t)upload_buffer);
if (ptr == RT_NULL)
{
/* all are the data section */
_handle_section(session, upload_buffer, (rt_uint32_t)end_ptr - (rt_uint32_t)upload_buffer);
upload_buffer = end_ptr;
}
else
{
chunk_size = (rt_uint32_t)ptr - (rt_uint32_t)upload_buffer;
_handle_section(session, upload_buffer, chunk_size);
upload_buffer += chunk_size;
}
}
}
session->buffer_offset = 0;
}
static void _webnet_module_upload_close(struct webnet_session* session)
{
struct webnet_module_upload_session *upload_session;
/* get upload session */
upload_session = (struct webnet_module_upload_session *)session->user_data;
if (upload_session == RT_NULL) return;
/* close file */
if (upload_session->file_opened == 1)
{
upload_session->entry->upload_close(session);
upload_session->file_opened = 0;
}
/* free session */
if (upload_session->filename != RT_NULL)
wn_free(upload_session->filename);
if (upload_session->content_type != RT_NULL)
wn_free(upload_session->content_type);
if (upload_session->boundary != RT_NULL)
wn_free(upload_session->boundary);
if (upload_session->entry != RT_NULL)
{
rt_uint32_t index;
for (index = 0; index < upload_session->name_entries_count; index ++)
{
if (upload_session->name_entries[index].value != RT_NULL)
wn_free(upload_session->name_entries[index].value);
if (upload_session->name_entries[index].name != RT_NULL)
wn_free(upload_session->name_entries[index].name);
}
wn_free(upload_session->name_entries);
upload_session->name_entries = RT_NULL;
}
wn_free(upload_session);
/* remove private data */
session->user_data = 0;
session->session_ops = RT_NULL;
session->session_phase = WEB_PHASE_CLOSE;
}
static const struct webnet_session_ops _upload_ops =
{
_webnet_module_upload_handle,
_webnet_module_upload_close
};
int webnet_module_upload_open(struct webnet_session* session)
{
char* boundary;
rt_uint32_t index, length;
const struct webnet_module_upload_entry *entry = RT_NULL;
struct webnet_module_upload_session *upload_session;
if (session->request->method != WEBNET_POST)
return WEBNET_MODULE_CONTINUE;
/* get upload entry */
for (index = 0; index < _upload_entries_count; index ++)
{
if (str_begin_with(session->request->path, _upload_entries[index]->url))
{
length = rt_strlen(_upload_entries[index]->url);
if (session->request->path[length] == '\0' ||
session->request->path[length] == '?')
{
/* found entry */
entry = _upload_entries[index];
break;
}
}
}
if (entry == RT_NULL) /* no this entry */
return WEBNET_MODULE_CONTINUE;
/* create a uploading session */
upload_session = (struct webnet_module_upload_session*) wn_malloc (
sizeof (struct webnet_module_upload_session));
if (upload_session == RT_NULL) return 0; /* no memory */
/* get boundary */
boundary = strstr(session->request->content_type, BOUNDARY_STRING);
if (boundary != RT_NULL)
{
int boundary_size;
/* make boundary */
boundary_size = strlen(boundary + sizeof(BOUNDARY_STRING) - 1) + 3;
upload_session->boundary = wn_malloc(boundary_size);
rt_sprintf(upload_session->boundary, "--%s", boundary + sizeof(BOUNDARY_STRING) - 1);
}
upload_session->filename = RT_NULL;
upload_session->content_type = RT_NULL;
upload_session->name_entries = RT_NULL;
upload_session->name_entries_count = 0;
upload_session->file_opened = 0;
upload_session->entry = entry;
upload_session->user_data = 0;
/* add this upload session into webnet session */
session->user_data = (rt_uint32_t) upload_session;
/* set webnet session operations */
session->session_ops = &_upload_ops;
session->session_ops->session_handle(session, WEBNET_EVENT_READ);
return WEBNET_MODULE_FINISHED;
}
int webnet_module_upload(struct webnet_session* session, int event)
{
if (event == WEBNET_EVENT_URI_PHYSICAL)
{
return webnet_module_upload_open(session);
}
return WEBNET_MODULE_CONTINUE;
}
/* webnet upload module APIs */
void webnet_upload_add(const struct webnet_module_upload_entry* entry)
{
if (_upload_entries == RT_NULL)
{
_upload_entries_count = 1;
/* first entries, malloc upload entry */
_upload_entries = (const struct webnet_module_upload_entry**)
wn_malloc (sizeof(struct webnet_module_upload_entry));
}
else
{
_upload_entries_count += 1;
_upload_entries = (const struct webnet_module_upload_entry**) wn_realloc (_upload_entries,
sizeof(void*) * _upload_entries_count);
}
RT_ASSERT(_upload_entries != RT_NULL);
_upload_entries[_upload_entries_count - 1] = entry;
}
RTM_EXPORT(webnet_upload_add);
const char* webnet_upload_get_filename(struct webnet_session* session)
{
struct webnet_module_upload_session *upload_session;
/* get upload session */
upload_session = (struct webnet_module_upload_session *)session->user_data;
if (upload_session == RT_NULL) return RT_NULL;
return upload_session->filename;
}
RTM_EXPORT(webnet_upload_get_filename);
const char* webnet_upload_get_content_type(struct webnet_session* session)
{
struct webnet_module_upload_session *upload_session;
/* get upload session */
upload_session = (struct webnet_module_upload_session *)session->user_data;
if (upload_session == RT_NULL) return RT_NULL;
return upload_session->content_type;
}
RTM_EXPORT(webnet_upload_get_content_type);
const char* webnet_upload_get_nameentry(struct webnet_session* session, const char* name)
{
rt_uint32_t index;
struct webnet_module_upload_session *upload_session;
/* get upload session */
upload_session = (struct webnet_module_upload_session *)session->user_data;
if (upload_session == RT_NULL) return RT_NULL;
for (index = 0; index < upload_session->name_entries_count; index ++)
{
if (strcasecmp(upload_session->name_entries[index].name, name) == 0)
{
return upload_session->name_entries[index].value;
}
}
return RT_NULL;
}
RTM_EXPORT(webnet_upload_get_nameentry);
const void* webnet_upload_get_userdata(struct webnet_session* session)
{
struct webnet_module_upload_session *upload_session;
/* get upload session */
upload_session = (struct webnet_module_upload_session *)session->user_data;
if (upload_session == RT_NULL) return RT_NULL;
return (const void*) upload_session->user_data;
}
RTM_EXPORT(webnet_upload_get_userdata);
/* ---- upload file interface ---- */
int webnet_upload_file_open(struct webnet_session* session)
{
struct webnet_module_upload_session* upload_session;
/* get upload session */
upload_session = (struct webnet_module_upload_session *)session->user_data;
if (upload_session->filename != RT_NULL)
{
/* open file */
upload_session->user_data = open(upload_session->filename, O_WRONLY, 0);
return upload_session->user_data;
}
return -1;
}
int webnet_upload_file_close(struct webnet_session* session)
{
int fd;
struct webnet_module_upload_session* upload_session;
/* get upload session */
upload_session = (struct webnet_module_upload_session *)session->user_data;
fd = upload_session->user_data;
if (fd >= 0)
{
return close(fd);
}
return -1;
}
int webnet_upload_file_write(struct webnet_session* session, const void* data, rt_size_t length)
{
int fd;
struct webnet_module_upload_session* upload_session;
/* get upload session */
upload_session = (struct webnet_module_upload_session *)session->user_data;
fd = upload_session->user_data;
if (fd >= 0)
{
return write(fd, data, length);
}
return 0;
}
#endif /* WEBNET_USING_UPLOAD */