mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-12-16 10:03:50 +01:00
gcoap/fileserver: implement PUT/POST/DELETE functionality
This commit is contained in:
parent
f375856a7e
commit
688acff5bc
2
dist/tools/doccheck/exclude_patterns
vendored
2
dist/tools/doccheck/exclude_patterns
vendored
@ -14281,6 +14281,8 @@ sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_BLOCK1 \(macro definiti
|
|||||||
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_BLOCK2 \(macro definition\) of group net_coap is not documented\.
|
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_BLOCK2 \(macro definition\) of group net_coap is not documented\.
|
||||||
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_CONTENT_FORMAT \(macro definition\) of group net_coap is not documented\.
|
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_CONTENT_FORMAT \(macro definition\) of group net_coap is not documented\.
|
||||||
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_ETAG \(macro definition\) of group net_coap is not documented\.
|
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_ETAG \(macro definition\) of group net_coap is not documented\.
|
||||||
|
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_IF_MATCH \(macro definition\) of group net_coap is not documented\.
|
||||||
|
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_IF_NONE_MATCH \(macro definition\) of group net_coap is not documented\.
|
||||||
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_MAX_AGE \(macro definition\) of group net_coap is not documented\.
|
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_MAX_AGE \(macro definition\) of group net_coap is not documented\.
|
||||||
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_LOCATION_PATH \(macro definition\) of group net_coap is not documented\.
|
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_LOCATION_PATH \(macro definition\) of group net_coap is not documented\.
|
||||||
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_LOCATION_QUERY \(macro definition\) of group net_coap is not documented\.
|
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_LOCATION_QUERY \(macro definition\) of group net_coap is not documented\.
|
||||||
|
|||||||
@ -27,7 +27,8 @@ static msg_t _main_msg_queue[MAIN_QUEUE_SIZE];
|
|||||||
|
|
||||||
/* CoAP resources. Must be sorted by path (ASCII order). */
|
/* CoAP resources. Must be sorted by path (ASCII order). */
|
||||||
static const coap_resource_t _resources[] = {
|
static const coap_resource_t _resources[] = {
|
||||||
{ "/vfs", COAP_GET | COAP_MATCH_SUBTREE, gcoap_fileserver_handler, VFS_DEFAULT_DATA },
|
{ "/vfs", COAP_GET | COAP_PUT | COAP_DELETE | COAP_MATCH_SUBTREE,
|
||||||
|
gcoap_fileserver_handler, VFS_DEFAULT_DATA },
|
||||||
};
|
};
|
||||||
|
|
||||||
static gcoap_listener_t _listener = {
|
static gcoap_listener_t _listener = {
|
||||||
|
|||||||
@ -35,8 +35,10 @@ extern "C" {
|
|||||||
* @name CoAP option numbers
|
* @name CoAP option numbers
|
||||||
* @{
|
* @{
|
||||||
*/
|
*/
|
||||||
|
#define COAP_OPT_IF_MATCH (1)
|
||||||
#define COAP_OPT_URI_HOST (3)
|
#define COAP_OPT_URI_HOST (3)
|
||||||
#define COAP_OPT_ETAG (4)
|
#define COAP_OPT_ETAG (4)
|
||||||
|
#define COAP_OPT_IF_NONE_MATCH (5)
|
||||||
#define COAP_OPT_OBSERVE (6)
|
#define COAP_OPT_OBSERVE (6)
|
||||||
#define COAP_OPT_LOCATION_PATH (8)
|
#define COAP_OPT_LOCATION_PATH (8)
|
||||||
#define COAP_OPT_URI_PATH (11)
|
#define COAP_OPT_URI_PATH (11)
|
||||||
|
|||||||
@ -32,14 +32,27 @@
|
|||||||
/** Maximum length of an expressible path, including the trailing 0 character. */
|
/** Maximum length of an expressible path, including the trailing 0 character. */
|
||||||
#define COAPFILESERVER_PATH_MAX (64)
|
#define COAPFILESERVER_PATH_MAX (64)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Structure holding information about present options in a request
|
||||||
|
*/
|
||||||
|
struct requestoptions {
|
||||||
|
uint32_t etag; /**< Etag value in an Etag option */
|
||||||
|
uint32_t if_match; /**< Etag value in an If-Match option */
|
||||||
|
char if_match_len; /**< Length of the Etag in an If-Match option */
|
||||||
|
struct {
|
||||||
|
bool if_match :1; /**< Request carries an If-Match option */
|
||||||
|
bool if_none_match :1; /**< Request carries an If-None-Match option */
|
||||||
|
bool etag :1; /**< Request carries an Etag option */
|
||||||
|
bool block2 :1; /**< Request carries a Block2 option */
|
||||||
|
bool block1 :1; /**< Request carries a Block1 option */
|
||||||
|
} exists; /**< Structure holding flags of present request options */
|
||||||
|
};
|
||||||
|
|
||||||
/** Data extracted from a request on a file */
|
/** Data extracted from a request on a file */
|
||||||
struct requestdata {
|
struct requestdata {
|
||||||
/** 0-terminated expanded file name in the VFS */
|
/** 0-terminated expanded file name in the VFS */
|
||||||
char namebuf[COAPFILESERVER_PATH_MAX];
|
char namebuf[COAPFILESERVER_PATH_MAX];
|
||||||
uint32_t blocknum2;
|
struct requestoptions options;
|
||||||
uint32_t etag;
|
|
||||||
bool etag_sent;
|
|
||||||
uint8_t szx2;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -84,69 +97,75 @@ static unsigned _count_char(const char *s, char c)
|
|||||||
/** Build an ETag based on the given file's VFS stat. If the stat fails,
|
/** Build an ETag based on the given file's VFS stat. If the stat fails,
|
||||||
* returns the error and leaves etag in any state; otherwise there's an etag
|
* returns the error and leaves etag in any state; otherwise there's an etag
|
||||||
* in the stattag's field */
|
* in the stattag's field */
|
||||||
static int stat_etag(const char *filename, uint32_t *etag)
|
static void stat_etag(struct stat *stat, uint32_t *etag)
|
||||||
{
|
{
|
||||||
struct stat stat;
|
|
||||||
int err = vfs_stat(filename, &stat);
|
|
||||||
if (err < 0) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Normalizing fields whose value can change without affecting the ETag */
|
/* Normalizing fields whose value can change without affecting the ETag */
|
||||||
stat.st_nlink = 0;
|
stat->st_nlink = 0;
|
||||||
#if defined(CPU_ESP32) || defined(CPU_ESP8266) || defined(CPU_MIPS_PIC32MX) || defined(CPU_MIPS_PIC32MZ)
|
#if defined(CPU_ESP32) || defined(CPU_ESP8266) || defined(CPU_MIPS_PIC32MX) || defined(CPU_MIPS_PIC32MZ)
|
||||||
memset(&stat.st_atime, 0, sizeof(stat.st_atime));
|
memset(&stat->st_atime, 0, sizeof(stat->st_atime));
|
||||||
#else
|
#else
|
||||||
memset(&stat.st_atim, 0, sizeof(stat.st_atim));
|
memset(&stat->st_atim, 0, sizeof(stat->st_atim));
|
||||||
#endif
|
#endif
|
||||||
|
*etag = fletcher32((void *)stat, sizeof(*stat) / 2);
|
||||||
*etag = fletcher32((void *)&stat, sizeof(stat) / 2);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a CoAP response for a given errno (eg. EACCESS -> 4.03 Forbidden
|
/** Create a CoAP response for a given errno (eg. EACCESS -> 4.03 Forbidden
|
||||||
* etc., defaulting to 5.03 Internal Server Error) */
|
* etc., defaulting to 5.03 Internal Server Error), or interpret a positive
|
||||||
static size_t gcoap_fileserver_errno_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len, int err)
|
* value for err as a CoAP response code */
|
||||||
|
static size_t gcoap_fileserver_error_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len, int err)
|
||||||
{
|
{
|
||||||
uint8_t code;
|
uint8_t code = err;
|
||||||
switch (err) {
|
if (err < 0) {
|
||||||
case -EACCES:
|
switch (err) {
|
||||||
code = COAP_CODE_FORBIDDEN;
|
case -EACCES:
|
||||||
break;
|
code = COAP_CODE_FORBIDDEN;
|
||||||
case -ENOENT:
|
break;
|
||||||
code = COAP_CODE_PATH_NOT_FOUND;
|
case -ENOENT:
|
||||||
break;
|
code = COAP_CODE_PATH_NOT_FOUND;
|
||||||
default:
|
break;
|
||||||
code = COAP_CODE_INTERNAL_SERVER_ERROR;
|
default:
|
||||||
};
|
code = COAP_CODE_INTERNAL_SERVER_ERROR;
|
||||||
DEBUG("gcoap_fileserver: Rejecting error %d (%s) as %d.%02d\n", err, strerror(err),
|
};
|
||||||
code >> 5, code & 0x1f);
|
DEBUG("gcoap_fileserver: Rejecting error %d (%s) as %d.%02d\n", err, strerror(err),
|
||||||
|
code >> 5, code & 0x1f);
|
||||||
|
}
|
||||||
return gcoap_response(pdu, buf, len, code);
|
return gcoap_response(pdu, buf, len, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _calc_szx2(coap_pkt_t *pdu, size_t reserve, struct requestdata *request)
|
static void _calc_szx2(coap_pkt_t *pdu, size_t reserve, coap_block1_t *block2)
|
||||||
{
|
{
|
||||||
assert(pdu->payload_len > reserve);
|
assert(pdu->payload_len > reserve);
|
||||||
size_t remaining_length = pdu->payload_len - reserve;
|
size_t remaining_length = pdu->payload_len - reserve;
|
||||||
/* > 0: To not wrap around; if that still won't fit that's later caught in
|
/* > 0: To not wrap around; if that still won't fit that's later caught in
|
||||||
* an assertion */
|
* an assertion */
|
||||||
while ((coap_szx2size(request->szx2) > remaining_length) && (request->szx2 > 0)) {
|
while ((coap_szx2size(block2->szx) > remaining_length) && (block2->szx > 0)) {
|
||||||
request->szx2--;
|
block2->szx--;
|
||||||
request->blocknum2 <<= 1;
|
block2->blknum <<= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t gcoap_fileserver_file_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
static ssize_t _get_file(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
||||||
struct requestdata *request)
|
struct requestdata *request)
|
||||||
{
|
{
|
||||||
|
int err;
|
||||||
uint32_t etag;
|
uint32_t etag;
|
||||||
int err = stat_etag(request->namebuf, &etag);
|
coap_block1_t block2 = { .szx = CONFIG_NANOCOAP_BLOCK_SIZE_EXP_MAX };
|
||||||
if (err < 0) {
|
{
|
||||||
return gcoap_fileserver_errno_handler(pdu, buf, len, err);
|
struct stat stat;
|
||||||
|
if ((err = vfs_stat(request->namebuf, &stat)) < 0) {
|
||||||
|
return gcoap_fileserver_error_handler(pdu, buf, len, err);
|
||||||
|
}
|
||||||
|
stat_etag(&stat, &etag);
|
||||||
}
|
}
|
||||||
|
if (request->options.exists.block2 && !coap_get_block2(pdu, &block2)) {
|
||||||
if (request->etag_sent && etag == request->etag) {
|
return gcoap_fileserver_error_handler(pdu, buf, len, COAP_CODE_BAD_OPTION);
|
||||||
|
}
|
||||||
|
if (request->options.exists.if_match &&
|
||||||
|
memcmp(&etag, &request->options.if_match, request->options.if_match_len)) {
|
||||||
|
return gcoap_fileserver_error_handler(pdu, buf, len, COAP_CODE_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
if (request->options.exists.etag &&
|
||||||
|
!memcmp(&etag, &request->options.etag, sizeof(etag))) {
|
||||||
gcoap_resp_init(pdu, buf, len, COAP_CODE_VALID);
|
gcoap_resp_init(pdu, buf, len, COAP_CODE_VALID);
|
||||||
coap_opt_add_opaque(pdu, COAP_OPT_ETAG, &etag, sizeof(etag));
|
coap_opt_add_opaque(pdu, COAP_OPT_ETAG, &etag, sizeof(etag));
|
||||||
return coap_opt_finish(pdu, COAP_OPT_FINISH_NONE);
|
return coap_opt_finish(pdu, COAP_OPT_FINISH_NONE);
|
||||||
@ -154,7 +173,7 @@ static ssize_t gcoap_fileserver_file_handler(coap_pkt_t *pdu, uint8_t *buf, size
|
|||||||
|
|
||||||
int fd = vfs_open(request->namebuf, O_RDONLY, 0);
|
int fd = vfs_open(request->namebuf, O_RDONLY, 0);
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
return gcoap_fileserver_errno_handler(pdu, buf, len, fd);
|
return gcoap_fileserver_error_handler(pdu, buf, len, fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT);
|
gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT);
|
||||||
@ -162,8 +181,8 @@ static ssize_t gcoap_fileserver_file_handler(coap_pkt_t *pdu, uint8_t *buf, size
|
|||||||
coap_block_slicer_t slicer;
|
coap_block_slicer_t slicer;
|
||||||
_calc_szx2(pdu,
|
_calc_szx2(pdu,
|
||||||
5 + 1 + 1 /* reserve BLOCK2 size + payload marker + more */,
|
5 + 1 + 1 /* reserve BLOCK2 size + payload marker + more */,
|
||||||
request);
|
&block2);
|
||||||
coap_block_slicer_init(&slicer, request->blocknum2, coap_szx2size(request->szx2));
|
coap_block_slicer_init(&slicer, block2.blknum, coap_szx2size(block2.szx));
|
||||||
coap_opt_add_block2(pdu, &slicer, true);
|
coap_opt_add_block2(pdu, &slicer, true);
|
||||||
size_t resp_len = coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD);
|
size_t resp_len = coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD);
|
||||||
|
|
||||||
@ -205,16 +224,162 @@ late_err:
|
|||||||
return coap_get_total_hdr_len(pdu);
|
return coap_get_total_hdr_len(pdu);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t gcoap_fileserver_directory_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
static ssize_t _put_file(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
||||||
struct requestdata *request,
|
struct requestdata *request)
|
||||||
const char *root, const char* resource_dir)
|
|
||||||
{
|
{
|
||||||
|
int ret, fd;
|
||||||
|
uint32_t etag;
|
||||||
|
struct stat stat;
|
||||||
|
coap_block1_t block1 = {0};
|
||||||
|
bool create = (vfs_stat(request->namebuf, &stat) == -ENOENT);
|
||||||
|
if (create) {
|
||||||
|
/* While a file 'f' is initially being created,
|
||||||
|
we save the partial content in '.f' and rename it afterwards */
|
||||||
|
if (!(ret = strlen(request->namebuf)) || (unsigned)ret >= sizeof(request->namebuf) - 1) {
|
||||||
|
/* need one more char '.' */
|
||||||
|
return gcoap_fileserver_error_handler(pdu, buf, len, -ENOBUFS);
|
||||||
|
}
|
||||||
|
char *file = strrchr(request->namebuf, '/');
|
||||||
|
memmove(file + 2, file + 1, strlen(file + 1));
|
||||||
|
*(file + 1) = '.';
|
||||||
|
}
|
||||||
|
if (request->options.exists.block1 && !coap_get_block1(pdu, &block1)) {
|
||||||
|
ret = COAP_CODE_BAD_OPTION;
|
||||||
|
goto unlink_on_error;
|
||||||
|
}
|
||||||
|
if (block1.blknum == 0) {
|
||||||
|
/* "If-None-Match only works correctly on Block1 requests with (NUM=0)
|
||||||
|
and MUST NOT be used on Block1 requests with NUM != 0." */
|
||||||
|
if (request->options.exists.if_none_match && !create) {
|
||||||
|
ret = COAP_CODE_PRECONDITION_FAILED;
|
||||||
|
goto unlink_on_error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (request->options.exists.if_match) {
|
||||||
|
stat_etag(&stat, &etag); /* Etag before write */
|
||||||
|
if (create || memcmp(&etag, &request->options.if_match, request->options.if_match_len)) {
|
||||||
|
ret = COAP_CODE_PRECONDITION_FAILED;
|
||||||
|
goto unlink_on_error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret = O_WRONLY;
|
||||||
|
ret |= (create ? O_CREAT | O_APPEND : 0);
|
||||||
|
if ((fd = vfs_open(request->namebuf, ret, 0)) < 0) {
|
||||||
|
ret = fd;
|
||||||
|
goto unlink_on_error;
|
||||||
|
}
|
||||||
|
if (create) {
|
||||||
|
if ((ret = vfs_lseek(fd, 0, SEEK_END)) < 0) {
|
||||||
|
goto close_on_error;
|
||||||
|
}
|
||||||
|
if (block1.offset != (unsigned)ret) {
|
||||||
|
/* expect block to be in the correct order during initial creation */
|
||||||
|
ret = COAP_CODE_REQUEST_ENTITY_INCOMPLETE;
|
||||||
|
goto close_on_error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (block1.offset > (unsigned)stat.st_size) {
|
||||||
|
/* after initial file creation, random access is fine,
|
||||||
|
as long as it does not result in holes in the file */
|
||||||
|
ret = COAP_CODE_REQUEST_ENTITY_INCOMPLETE;
|
||||||
|
goto close_on_error;
|
||||||
|
}
|
||||||
|
if ((ret = vfs_lseek(fd, block1.offset, SEEK_SET)) < 0) {
|
||||||
|
goto close_on_error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((ret = vfs_write(fd, pdu->payload, pdu->payload_len)) < 0 ||
|
||||||
|
(unsigned)ret != pdu->payload_len) {
|
||||||
|
goto close_on_error;
|
||||||
|
}
|
||||||
|
vfs_close(fd);
|
||||||
|
if (!block1.more) {
|
||||||
|
if ((ret = vfs_stat(request->namebuf, &stat)) < 0) {
|
||||||
|
goto unlink_on_error;
|
||||||
|
}
|
||||||
|
if (create) {
|
||||||
|
char path[COAPFILESERVER_PATH_MAX];
|
||||||
|
strcpy(path, request->namebuf);
|
||||||
|
char *file = strrchr(path, '/');
|
||||||
|
memmove(file + 1, file + 2, strlen(file + 2) + 1);
|
||||||
|
if ((ret = vfs_rename(request->namebuf, path)) < 0) {
|
||||||
|
goto unlink_on_error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stat_etag(&stat, &etag); /* Etag after write */
|
||||||
|
gcoap_resp_init(pdu, buf, len, create ? COAP_CODE_CREATED : COAP_CODE_CHANGED);
|
||||||
|
coap_opt_add_opaque(pdu, COAP_OPT_ETAG, &etag, sizeof(etag));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTINUE);
|
||||||
|
block1.more = true; /* resource is created atomically */
|
||||||
|
coap_opt_add_block1_control(pdu, &block1);
|
||||||
|
}
|
||||||
|
return coap_opt_finish(pdu, COAP_OPT_FINISH_NONE);
|
||||||
|
|
||||||
|
close_on_error:
|
||||||
|
vfs_close(fd);
|
||||||
|
unlink_on_error:
|
||||||
|
if (create) {
|
||||||
|
vfs_unlink(request->namebuf);
|
||||||
|
}
|
||||||
|
return gcoap_fileserver_error_handler(pdu, buf, len, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t _delete_file(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
||||||
|
struct requestdata *request)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
uint32_t etag;
|
||||||
|
struct stat stat;
|
||||||
|
if ((ret = vfs_stat(request->namebuf, &stat)) < 0) {
|
||||||
|
return gcoap_fileserver_error_handler(pdu, buf, len, ret);
|
||||||
|
}
|
||||||
|
if (request->options.exists.if_match && request->options.if_match_len) {
|
||||||
|
stat_etag(&stat, &etag);
|
||||||
|
if (memcmp(&etag, &request->options.if_match, request->options.if_match_len)) {
|
||||||
|
return gcoap_fileserver_error_handler(pdu, buf, len, COAP_CODE_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((ret = vfs_unlink(request->namebuf)) < 0) {
|
||||||
|
return gcoap_fileserver_error_handler(pdu, buf, len, ret);
|
||||||
|
}
|
||||||
|
gcoap_resp_init(pdu, buf, len, COAP_CODE_DELETED);
|
||||||
|
return coap_opt_finish(pdu, COAP_OPT_FINISH_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t gcoap_fileserver_file_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
||||||
|
struct requestdata *request)
|
||||||
|
{
|
||||||
|
switch (coap_get_code(pdu)) {
|
||||||
|
case COAP_METHOD_GET:
|
||||||
|
return _get_file(pdu, buf, len, request);
|
||||||
|
case COAP_METHOD_PUT:
|
||||||
|
return _put_file(pdu, buf, len, request);
|
||||||
|
case COAP_METHOD_DELETE:
|
||||||
|
return _delete_file(pdu, buf, len, request);
|
||||||
|
default:
|
||||||
|
return gcoap_fileserver_error_handler(pdu, buf, len, COAP_CODE_METHOD_NOT_ALLOWED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t _get_directory(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
||||||
|
struct requestdata *request,
|
||||||
|
const char *root, const char* resource_dir)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
vfs_DIR dir;
|
vfs_DIR dir;
|
||||||
coap_block_slicer_t slicer;
|
coap_block_slicer_t slicer;
|
||||||
|
coap_block1_t block2 = { .szx = CONFIG_NANOCOAP_BLOCK_SIZE_EXP_MAX };
|
||||||
int err = vfs_opendir(&dir, request->namebuf);
|
if (request->options.exists.block2 && !coap_get_block2(pdu, &block2)) {
|
||||||
if (err != 0) {
|
return gcoap_fileserver_error_handler(pdu, buf, len, COAP_OPT_FINISH_NONE);
|
||||||
return gcoap_fileserver_errno_handler(pdu, buf, len, err);
|
}
|
||||||
|
if ((err = vfs_opendir(&dir, request->namebuf)) < 0) {
|
||||||
|
return gcoap_fileserver_error_handler(pdu, buf, len, err);
|
||||||
|
}
|
||||||
|
if (request->options.exists.if_match && request->options.if_match_len) {
|
||||||
|
return gcoap_fileserver_error_handler(pdu, buf, len, COAP_CODE_PRECONDITION_FAILED);
|
||||||
}
|
}
|
||||||
DEBUG("gcoap_fileserver: Serving directory listing\n");
|
DEBUG("gcoap_fileserver: Serving directory listing\n");
|
||||||
|
|
||||||
@ -222,8 +387,8 @@ static ssize_t gcoap_fileserver_directory_handler(coap_pkt_t *pdu, uint8_t *buf,
|
|||||||
coap_opt_add_format(pdu, COAP_FORMAT_LINK);
|
coap_opt_add_format(pdu, COAP_FORMAT_LINK);
|
||||||
_calc_szx2(pdu,
|
_calc_szx2(pdu,
|
||||||
5 + 1 /* reserve BLOCK2 size + payload marker */,
|
5 + 1 /* reserve BLOCK2 size + payload marker */,
|
||||||
request);
|
&block2);
|
||||||
coap_block_slicer_init(&slicer, request->blocknum2, coap_szx2size(request->szx2));
|
coap_block_slicer_init(&slicer, block2.blknum, coap_szx2size(block2.szx));
|
||||||
coap_opt_add_block2(pdu, &slicer, true);
|
coap_opt_add_block2(pdu, &slicer, true);
|
||||||
buf += coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD);
|
buf += coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD);
|
||||||
|
|
||||||
@ -236,8 +401,8 @@ static ssize_t gcoap_fileserver_directory_handler(coap_pkt_t *pdu, uint8_t *buf,
|
|||||||
while (vfs_readdir(&dir, &entry) > 0) {
|
while (vfs_readdir(&dir, &entry) > 0) {
|
||||||
const char *entry_name = entry.d_name;
|
const char *entry_name = entry.d_name;
|
||||||
size_t entry_len = strlen(entry_name);
|
size_t entry_len = strlen(entry_name);
|
||||||
if (entry_len <= 2 && memcmp(entry_name, "..", entry_len) == 0) {
|
if (*entry_name == '.') {
|
||||||
/* Up pointers don't work the same way in URI semantics */
|
/* Exclude everything that starts with '.' */
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
bool is_dir = entry_is_dir(request->namebuf, entry_name);
|
bool is_dir = entry_is_dir(request->namebuf, entry_name);
|
||||||
@ -246,7 +411,6 @@ static ssize_t gcoap_fileserver_directory_handler(coap_pkt_t *pdu, uint8_t *buf,
|
|||||||
buf += coap_blockwise_put_char(&slicer, buf, ',');
|
buf += coap_blockwise_put_char(&slicer, buf, ',');
|
||||||
}
|
}
|
||||||
buf += coap_blockwise_put_char(&slicer, buf, '<');
|
buf += coap_blockwise_put_char(&slicer, buf, '<');
|
||||||
|
|
||||||
buf += coap_blockwise_put_bytes(&slicer, buf, resource_dir, resource_dir_len);
|
buf += coap_blockwise_put_bytes(&slicer, buf, resource_dir, resource_dir_len);
|
||||||
buf += coap_blockwise_put_bytes(&slicer, buf, root_dir, root_dir_len);
|
buf += coap_blockwise_put_bytes(&slicer, buf, root_dir, root_dir_len);
|
||||||
buf += coap_blockwise_put_char(&slicer, buf, '/');
|
buf += coap_blockwise_put_char(&slicer, buf, '/');
|
||||||
@ -263,15 +427,65 @@ static ssize_t gcoap_fileserver_directory_handler(coap_pkt_t *pdu, uint8_t *buf,
|
|||||||
return (uintptr_t)buf - (uintptr_t)pdu->hdr;
|
return (uintptr_t)buf - (uintptr_t)pdu->hdr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ssize_t _put_directory(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
||||||
|
struct requestdata *request)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
vfs_DIR dir;
|
||||||
|
if ((err = vfs_opendir(&dir, request->namebuf)) == 0) {
|
||||||
|
vfs_closedir(&dir);
|
||||||
|
if (request->options.exists.if_match && request->options.if_match_len) {
|
||||||
|
return gcoap_fileserver_error_handler(pdu, buf, len, COAP_CODE_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
gcoap_resp_init(pdu, buf, len, COAP_CODE_CHANGED);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (request->options.exists.if_match) {
|
||||||
|
return gcoap_fileserver_error_handler(pdu, buf, len, COAP_CODE_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
if ((err = vfs_mkdir(request->namebuf, 0)) < 0) {
|
||||||
|
return gcoap_fileserver_error_handler(pdu, buf, len, err);
|
||||||
|
}
|
||||||
|
gcoap_resp_init(pdu, buf, len, COAP_CODE_CREATED);
|
||||||
|
}
|
||||||
|
return coap_opt_finish(pdu, COAP_OPT_FINISH_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t _delete_directory(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
||||||
|
struct requestdata *request)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
if (request->options.exists.if_match && request->options.if_match_len) {
|
||||||
|
return gcoap_fileserver_error_handler(pdu, buf, len, COAP_CODE_PRECONDITION_FAILED);
|
||||||
|
}
|
||||||
|
if ((err = vfs_rmdir(request->namebuf)) < 0) {
|
||||||
|
return gcoap_fileserver_error_handler(pdu, buf, len, err);
|
||||||
|
}
|
||||||
|
gcoap_resp_init(pdu, buf, len, COAP_CODE_DELETED);
|
||||||
|
return coap_opt_finish(pdu, COAP_OPT_FINISH_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t gcoap_fileserver_directory_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
||||||
|
struct requestdata *request,
|
||||||
|
const char *root, const char* resource_dir)
|
||||||
|
{
|
||||||
|
switch (coap_get_code(pdu)) {
|
||||||
|
case COAP_METHOD_GET:
|
||||||
|
return _get_directory(pdu, buf, len, request, root, resource_dir);
|
||||||
|
case COAP_METHOD_PUT:
|
||||||
|
return _put_directory(pdu, buf, len, request);
|
||||||
|
case COAP_METHOD_DELETE:
|
||||||
|
return _delete_directory(pdu, buf, len, request);
|
||||||
|
default:
|
||||||
|
return gcoap_fileserver_error_handler(pdu, buf, len, COAP_CODE_METHOD_NOT_ALLOWED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ssize_t gcoap_fileserver_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
ssize_t gcoap_fileserver_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
||||||
coap_request_ctx_t *ctx) {
|
coap_request_ctx_t *ctx) {
|
||||||
const char *root = coap_request_ctx_get_context(ctx);
|
const char *root = coap_request_ctx_get_context(ctx);
|
||||||
const char *resource = coap_request_ctx_get_path(ctx);
|
const char *resource = coap_request_ctx_get_path(ctx);
|
||||||
struct requestdata request = {
|
struct requestdata request = {0};
|
||||||
.etag_sent = false,
|
|
||||||
.blocknum2 = 0,
|
|
||||||
.szx2 = CONFIG_NANOCOAP_BLOCK_SIZE_EXP_MAX,
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Index in request.namebuf. Must not point at the last entry as that will be
|
/** Index in request.namebuf. Must not point at the last entry as that will be
|
||||||
* zeroed to get a 0-terminated string. */
|
* zeroed to get a 0-terminated string. */
|
||||||
@ -284,75 +498,108 @@ ssize_t gcoap_fileserver_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
|||||||
strncpy(request.namebuf, root, sizeof(request.namebuf));
|
strncpy(request.namebuf, root, sizeof(request.namebuf));
|
||||||
namelength = strlen(root);
|
namelength = strlen(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_directory = true; /* either no path component at all or trailing '/' */
|
bool is_directory = true; /* either no path component at all or trailing '/' */
|
||||||
coap_optpos_t opt = {
|
{
|
||||||
.offset = coap_get_total_hdr_len(pdu),
|
coap_optpos_t opt = { .offset = coap_get_total_hdr_len(pdu) };
|
||||||
};
|
uint8_t *value;
|
||||||
uint8_t *value;
|
ssize_t optlen;
|
||||||
ssize_t optlen;
|
while ((optlen = coap_opt_get_next(pdu, &opt, &value, 0)) != -ENOENT) {
|
||||||
while ((optlen = coap_opt_get_next(pdu, &opt, &value, 0)) != -ENOENT) {
|
if (optlen < 0) {
|
||||||
|
errorcode = COAP_CODE_BAD_REQUEST;
|
||||||
if (optlen < 0) {
|
goto error;
|
||||||
errorcode = COAP_CODE_BAD_REQUEST;
|
}
|
||||||
|
switch (opt.opt_num) {
|
||||||
|
case COAP_OPT_IF_MATCH:
|
||||||
|
/* support only one if-match option, although it is repeatable */
|
||||||
|
if (request.options.exists.if_match) {
|
||||||
|
errorcode = COAP_CODE_NOT_IMPLEMENTED;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (optlen > 8) {
|
||||||
|
errorcode = COAP_CODE_BAD_OPTION;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (optlen && optlen != sizeof(request.options.if_match)) {
|
||||||
|
errorcode = COAP_CODE_PRECONDITION_FAILED;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
memcpy(&request.options.if_match, value,
|
||||||
|
request.options.if_match_len = optlen);
|
||||||
|
request.options.exists.if_match = true;
|
||||||
|
break;
|
||||||
|
case COAP_OPT_IF_NONE_MATCH:
|
||||||
|
if (optlen || request.options.exists.if_none_match) {
|
||||||
|
errorcode = COAP_CODE_BAD_OPTION;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
request.options.exists.if_none_match = true;
|
||||||
|
break;
|
||||||
|
case COAP_OPT_URI_PATH:
|
||||||
|
if (strip_remaining != 0) {
|
||||||
|
strip_remaining -= 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((is_directory = (optlen == 0))) { /* '/' */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (memchr(value, '\0', optlen) != NULL ||
|
||||||
|
memchr(value, '/', optlen) != NULL) {
|
||||||
|
/* Path can not be expressed in the file system */
|
||||||
|
errorcode = COAP_CODE_PATH_NOT_FOUND;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
size_t newlength = namelength + 1 + optlen;
|
||||||
|
if (newlength > sizeof(request.namebuf) - 1) {
|
||||||
|
/* Path too long, therefore can't exist in this mapping */
|
||||||
|
errorcode = COAP_CODE_PATH_NOT_FOUND;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
request.namebuf[namelength] = '/';
|
||||||
|
memcpy(&request.namebuf[namelength] + 1, value, optlen);
|
||||||
|
namelength = newlength;
|
||||||
|
break;
|
||||||
|
case COAP_OPT_ETAG:
|
||||||
|
if (optlen != sizeof(request.options.etag)) {
|
||||||
|
/* Can't be a matching tag, no use in carrying that */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (request.options.exists.etag) {
|
||||||
|
/* We can reasonably only check for a limited sized set,
|
||||||
|
* and it size is 1 here (sending multiple ETags is
|
||||||
|
* possible but rare) */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
request.options.exists.etag = true;
|
||||||
|
memcpy(&request.options.etag, value, sizeof(request.options.etag));
|
||||||
|
break;
|
||||||
|
case COAP_OPT_BLOCK2:
|
||||||
|
if (optlen > 4 || request.options.exists.block2) {
|
||||||
|
errorcode = COAP_CODE_BAD_OPTION;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
request.options.exists.block2 = true;
|
||||||
|
break;
|
||||||
|
case COAP_OPT_BLOCK1:
|
||||||
|
if (optlen > 4 || request.options.exists.block1) {
|
||||||
|
errorcode = COAP_CODE_BAD_OPTION;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
request.options.exists.block1 = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (opt.opt_num & 1) {
|
||||||
|
errorcode = COAP_CODE_BAD_OPTION;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (request.options.exists.if_match &&
|
||||||
|
request.options.exists.if_none_match) {
|
||||||
|
errorcode = COAP_CODE_PRECONDITION_FAILED;
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (opt.opt_num) {
|
|
||||||
case COAP_OPT_URI_PATH:
|
|
||||||
if (strip_remaining != 0) {
|
|
||||||
strip_remaining -= 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ((is_directory = (optlen == 0))) { /* '/' */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (memchr(value, '\0', optlen) != NULL ||
|
|
||||||
memchr(value, '/', optlen) != NULL) {
|
|
||||||
/* Path can not be expressed in the file system */
|
|
||||||
errorcode = COAP_CODE_PATH_NOT_FOUND;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
size_t newlength = namelength + 1 + optlen;
|
|
||||||
if (newlength > sizeof(request.namebuf) - 1) {
|
|
||||||
/* Path too long, therefore can't exist in this mapping */
|
|
||||||
errorcode = COAP_CODE_PATH_NOT_FOUND;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
request.namebuf[namelength] = '/';
|
|
||||||
memcpy(&request.namebuf[namelength] + 1, value, optlen);
|
|
||||||
namelength = newlength;
|
|
||||||
break;
|
|
||||||
case COAP_OPT_ETAG:
|
|
||||||
if (optlen != sizeof(request.etag)) {
|
|
||||||
/* Can't be a matching tag, no use in carrying that */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (request.etag_sent) {
|
|
||||||
/* We can reasonably only check for a limited sized set,
|
|
||||||
* and it size is 1 here (sending multiple ETags is
|
|
||||||
* possible but rare) */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
request.etag_sent = true;
|
|
||||||
memcpy(&request.etag, value, sizeof(request.etag));
|
|
||||||
break;
|
|
||||||
case COAP_OPT_BLOCK2:
|
|
||||||
/* Could be more efficient now that we already know where it
|
|
||||||
* is, but meh */
|
|
||||||
coap_get_blockopt(pdu, COAP_OPT_BLOCK2, &request.blocknum2, &request.szx2);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (opt.opt_num & 1) {
|
|
||||||
errorcode = COAP_CODE_BAD_REQUEST;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
/* Ignoring elective option */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
request.namebuf[namelength] = '\0';
|
request.namebuf[namelength] = '\0';
|
||||||
|
|
||||||
DEBUG("request: '%s'\n", request.namebuf);
|
DEBUG("request: '%s'\n", request.namebuf);
|
||||||
@ -360,13 +607,9 @@ ssize_t gcoap_fileserver_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len,
|
|||||||
/* Note to self: As we parse more options than just Uri-Path, we'll likely
|
/* Note to self: As we parse more options than just Uri-Path, we'll likely
|
||||||
* pass a struct pointer later. So far, those could even be hooked into the
|
* pass a struct pointer later. So far, those could even be hooked into the
|
||||||
* resource list, but that'll go away once we parse more options */
|
* resource list, but that'll go away once we parse more options */
|
||||||
if (is_directory) {
|
return is_directory
|
||||||
return gcoap_fileserver_directory_handler(pdu, buf, len, &request, root, resource);
|
? gcoap_fileserver_directory_handler(pdu, buf, len, &request, root, resource)
|
||||||
}
|
: gcoap_fileserver_file_handler(pdu, buf, len, &request);
|
||||||
else {
|
|
||||||
return gcoap_fileserver_file_handler(pdu, buf, len, &request);
|
|
||||||
}
|
|
||||||
|
|
||||||
error:
|
error:
|
||||||
return gcoap_response(pdu, buf, len, errorcode);
|
return gcoap_response(pdu, buf, len, errorcode);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user