Http data server changes
* Indirect redirects from directory to `index.html` * Added `HTTP_DATA_INTERNAL_REDIRECT` compile-time option * Added CLICON_RESTCONF_API_ROOT to clixon-config.yang * Added 404 return without body if neither restconf, data or streams prefix match * Fixed: HTTP/1 parse error for '/' path
This commit is contained in:
parent
84d88c8ad8
commit
f11e4bad6c
10 changed files with 197 additions and 113 deletions
|
|
@ -41,6 +41,7 @@
|
|||
%union {
|
||||
char *string;
|
||||
int intval;
|
||||
void *stack;
|
||||
}
|
||||
|
||||
%token SP
|
||||
|
|
@ -62,8 +63,8 @@
|
|||
%token <intval> DIGIT
|
||||
|
||||
%type <string> body
|
||||
%type <string> absolute_paths
|
||||
%type <string> absolute_paths1
|
||||
%type <stack> absolute_paths
|
||||
%type <stack> absolute_paths1
|
||||
%type <string> absolute_path
|
||||
%type <string> field_vchars
|
||||
%type <string> field_values
|
||||
|
|
@ -259,16 +260,16 @@ method : TOKEN
|
|||
*/
|
||||
request_target : absolute_paths1
|
||||
{
|
||||
if (restconf_param_set(_HY->hy_h, "REQUEST_URI", $1) < 0)
|
||||
if (restconf_param_set(_HY->hy_h, "REQUEST_URI", cbuf_get($1)) < 0)
|
||||
YYABORT;
|
||||
free($1);
|
||||
cbuf_free($1);
|
||||
_PARSE_DEBUG("request-target -> absolute-paths1");
|
||||
}
|
||||
| absolute_paths1 QMARK QUERY
|
||||
{
|
||||
if (restconf_param_set(_HY->hy_h, "REQUEST_URI", $1) < 0)
|
||||
if (restconf_param_set(_HY->hy_h, "REQUEST_URI", cbuf_get($1)) < 0)
|
||||
YYABORT;
|
||||
free($1);
|
||||
cbuf_free($1);
|
||||
if (http1_parse_query(_HY, $3) < 0)
|
||||
YYABORT;
|
||||
free($3);
|
||||
|
|
@ -285,21 +286,25 @@ absolute_paths1 : absolute_paths
|
|||
{ $$ = $1;_PARSE_DEBUG("absolute-paths1 -> absolute-paths / "); }
|
||||
;
|
||||
|
||||
/* absolute-path = 1*( "/" segment ) */
|
||||
/* absolute-path = 1*( "/" segment )
|
||||
*/
|
||||
absolute_paths : absolute_paths absolute_path
|
||||
{
|
||||
if (($$ = clixon_string_del_join($1, "/", $2)) == NULL) { free($2); YYABORT;}
|
||||
free($2);
|
||||
$$ = $1;
|
||||
cprintf($$, "/");
|
||||
if ($2)
|
||||
cprintf($$, "%s", $2);
|
||||
_PARSE_DEBUG("absolute-paths -> absolute-paths absolute-path");
|
||||
}
|
||||
| absolute_path
|
||||
{
|
||||
if (($$ = clixon_string_del_join(NULL, "/", $1)) == NULL) { free($1); YYABORT;}
|
||||
free($1);
|
||||
if (($$ = cbuf_new()) == NULL){ YYABORT;}
|
||||
cprintf($$, "/");
|
||||
if ($1)
|
||||
cprintf($$, "%s", $1);
|
||||
_PARSE_DEBUG("absolute-paths -> absolute-path");
|
||||
}
|
||||
;
|
||||
|
||||
/* segment = <segment, see [RFC3986], Section 3.3>
|
||||
* segment = *pchar
|
||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||
|
|
@ -310,8 +315,13 @@ absolute_paths : absolute_paths absolute_path
|
|||
*/
|
||||
absolute_path : SLASH PCHARS
|
||||
{
|
||||
if (($$=strdup($2)) == NULL) YYABORT;
|
||||
_PARSE_DEBUG("absolute-path -> PCHARS");
|
||||
$$ = $2;
|
||||
_PARSE_DEBUG("absolute-path -> / PCHARS");
|
||||
}
|
||||
| SLASH
|
||||
{
|
||||
$$ = NULL;
|
||||
_PARSE_DEBUG("absolute-path -> /");
|
||||
}
|
||||
;
|
||||
|
||||
|
|
|
|||
|
|
@ -160,69 +160,105 @@ api_http_data_err(clicon_handle h,
|
|||
|
||||
/*! Check validity of path, may only be regular dir or file
|
||||
* No .., soft link, ~, etc
|
||||
* @param[in] prefix Prefix of path0, where to start file check
|
||||
* @param[in] path0 Filepath
|
||||
* @param[out] code Error code, if retval = 0
|
||||
* @retval -1 Error
|
||||
* @retval 0 Invalid, code set
|
||||
* @retval 1 OK
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] req Generic Www handle (can be part of clixon handle)
|
||||
* @param[in] prefix Prefix of path0, where to start file check
|
||||
* @param[in,out] cbpath Filepath as cbuf, internal redirection may change it
|
||||
* @param[out] fp Open file, if retval = 1
|
||||
* @param[out] fsz Size of file, if retval = 1
|
||||
* @retval -1 Error
|
||||
* @retval 0 Invalid
|
||||
* @retval 1 OK, fp,fsz set
|
||||
*/
|
||||
static int
|
||||
check_file_path(char *prefix,
|
||||
char *path0,
|
||||
int *code)
|
||||
http_data_check_file_path(clicon_handle h,
|
||||
void *req,
|
||||
char *prefix,
|
||||
cbuf *cbpath,
|
||||
FILE **fp,
|
||||
off_t *fsz)
|
||||
{
|
||||
int retval = -1;
|
||||
char *path = NULL;
|
||||
int i;
|
||||
int retval = -1;
|
||||
struct stat fstat;
|
||||
char *p;
|
||||
int i;
|
||||
int code = 0;
|
||||
FILE *f;
|
||||
|
||||
if (prefix == NULL || path0 == NULL || code == NULL){
|
||||
clicon_err(OE_UNIX, EINVAL, "prefix, path0 or code is NULL");
|
||||
if (prefix == NULL || cbpath == NULL || fp == NULL){
|
||||
clicon_err(OE_UNIX, EINVAL, "prefix, cbpath0 or fp is NULL");
|
||||
goto done;
|
||||
}
|
||||
if ((path = strdup(path0)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "strdup");
|
||||
p = cbuf_get(cbpath);
|
||||
clicon_debug(1, "%s %s", __FUNCTION__, p);
|
||||
if (strncmp(prefix, p, strlen(prefix)) != 0){
|
||||
clicon_err(OE_UNIX, EINVAL, "prefix is not prefix of cbpath");
|
||||
goto done;
|
||||
}
|
||||
for (i=strlen(prefix); i<strlen(path0); i++){
|
||||
if (path[i] == '/'){ /* Check valid dir */
|
||||
path[i] = '\0';
|
||||
for (i=strlen(prefix); i<strlen(p); i++){
|
||||
if (p[i] == '/'){ /* Check valid dir */
|
||||
p[i] = '\0';
|
||||
/* Ensure not soft link */
|
||||
if (lstat(path, &fstat) < 0){
|
||||
*code = 404;
|
||||
if (lstat(p, &fstat) < 0){
|
||||
clicon_debug(1, "%s Error lstat(%s):%s", __FUNCTION__, p, strerror(errno));
|
||||
code = 404;
|
||||
goto invalid;
|
||||
}
|
||||
if (!S_ISDIR(fstat.st_mode)){
|
||||
*code = 403;
|
||||
clicon_debug(1, "%s Error lstat(%s): Not dir", __FUNCTION__, p);
|
||||
code = 403;
|
||||
goto invalid;
|
||||
}
|
||||
path[i] = '/';
|
||||
p[i] = '/';
|
||||
}
|
||||
else if (path[i] == '~'){
|
||||
*code = 403;
|
||||
else if (p[i] == '~'){
|
||||
clicon_debug(1, "%s Error lstat(%s): ~ not allowed in file path", __FUNCTION__, p);
|
||||
code = 403;
|
||||
goto invalid;
|
||||
}
|
||||
else if (path[i] == '.' && i>strlen(prefix) && path[i-1] == '.'){
|
||||
*code = 403;
|
||||
else if (p[i] == '.' && i>strlen(prefix) && p[i-1] == '.'){
|
||||
clicon_debug(1, "%s Error lstat(%s): .. not allowed in file path", __FUNCTION__, p);
|
||||
code = 403;
|
||||
goto invalid;
|
||||
}
|
||||
}
|
||||
/* Resulting file (ensure not soft link) */
|
||||
if (lstat(path, &fstat) < 0){
|
||||
*code = 404;
|
||||
if (lstat(p, &fstat) < 0){
|
||||
clicon_debug(1, "%s Error lstat(%s):%s", __FUNCTION__, p, strerror(errno));
|
||||
code = 404;
|
||||
goto invalid;
|
||||
}
|
||||
#ifdef HTTP_DATA_INTERNAL_REDIRECT
|
||||
/* If dir try redirect, not cbpath is extended */
|
||||
if (S_ISDIR(fstat.st_mode)){
|
||||
cprintf(cbpath, "/%s", HTTP_DATA_INTERNAL_REDIRECT);
|
||||
p = cbuf_get(cbpath);
|
||||
clicon_debug(1, "%s internal redirect: %s", __FUNCTION__, p);
|
||||
if (lstat(p, &fstat) < 0){
|
||||
clicon_debug(1, "%s Error lstat(%s):%s", __FUNCTION__, p, strerror(errno));
|
||||
code = 404;
|
||||
goto invalid;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!S_ISREG(fstat.st_mode)){
|
||||
*code = 403;
|
||||
clicon_debug(1, "%s Error lstat(%s): Not regular file", __FUNCTION__, p);
|
||||
code = 403;
|
||||
goto invalid;
|
||||
}
|
||||
*fsz = fstat.st_size;
|
||||
if ((f = fopen(p, "rb")) == NULL){
|
||||
clicon_debug(1, "%s Error fopen(%s) %s", __FUNCTION__, p, strerror(errno));
|
||||
code = 403;
|
||||
goto invalid;
|
||||
}
|
||||
*fp = f;
|
||||
retval = 1; /* OK */
|
||||
done:
|
||||
if (path)
|
||||
free(path);
|
||||
return retval;
|
||||
invalid:
|
||||
if (api_http_data_err(h, req, code) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
|
@ -231,10 +267,9 @@ check_file_path(char *prefix,
|
|||
* @param[in] h Clicon handle
|
||||
* @param[in] req Generic Www handle (can be part of clixon handle)
|
||||
* @param[in] pathname With stripped prefix (eg /data), ultimately a filename
|
||||
* @param[in] head HEAD not GET
|
||||
* @note: primitive file handling, just check if file exists and read it all
|
||||
* XXX 1: Buffer copying once too many, see #if 0 below
|
||||
* XXX 2: Generic file system below CLICON_HTTP_DATA_ROOT, no checks for links or ..
|
||||
* Need pathname santitization: no .. or ~, just a directory structure.
|
||||
*/
|
||||
static int
|
||||
api_http_data_file(clicon_handle h,
|
||||
|
|
@ -247,15 +282,16 @@ api_http_data_file(clicon_handle h,
|
|||
char *filename;
|
||||
cbuf *cbdata = NULL;
|
||||
FILE *f = NULL;
|
||||
off_t fsz = 0;
|
||||
long fsize;
|
||||
char *www_data_root = NULL;
|
||||
char *suffix;
|
||||
char *media;
|
||||
int ret;
|
||||
int code = 0;
|
||||
char *str = NULL;
|
||||
size_t sz;
|
||||
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
if ((cbfile = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
|
|
@ -265,7 +301,24 @@ api_http_data_file(clicon_handle h,
|
|||
goto done;
|
||||
}
|
||||
|
||||
if ((suffix = rindex(pathname, '.')) == NULL){
|
||||
cprintf(cbfile, "%s", www_data_root);
|
||||
if (pathname){
|
||||
if (strlen(pathname) && pathname[0] != '/'){
|
||||
clicon_debug(1, "%s Error fopen(%s) pathname not prefixed with /",
|
||||
__FUNCTION__, pathname);
|
||||
if (api_http_data_err(h, req, 404) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
cprintf(cbfile, "%s", pathname); /* Assume pathname starts with '/' */
|
||||
}
|
||||
if ((ret = http_data_check_file_path(h, req, www_data_root, cbfile, &f, &fsz)) < 0)
|
||||
goto done;
|
||||
if (ret == 0) /* Invalid, return code set */
|
||||
goto ok;
|
||||
filename = cbuf_get(cbfile);
|
||||
/* Find media from file suffix, note there may have been internal indirection */
|
||||
if ((suffix = rindex(filename, '.')) == NULL){
|
||||
media = "application/octet-stream";
|
||||
}
|
||||
else {
|
||||
|
|
@ -273,29 +326,19 @@ api_http_data_file(clicon_handle h,
|
|||
if ((media = clicon_str2str(mime_map, suffix)) == NULL)
|
||||
media = "application/octet-stream";
|
||||
}
|
||||
cprintf(cbfile, "%s", www_data_root);
|
||||
if (pathname)
|
||||
cprintf(cbfile, "/%s", pathname);
|
||||
filename = cbuf_get(cbfile);
|
||||
clicon_debug(1, "%s %s", __FUNCTION__, filename);
|
||||
if ((ret = check_file_path(www_data_root, filename, &code)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
clicon_debug(1, "%s code:%d", __FUNCTION__, code);
|
||||
if (api_http_data_err(h, req, code) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
if ((f = fopen(filename, "rb")) == NULL){
|
||||
if (api_http_data_err(h, req, 403) < 0) /* Forbidden or 500? */
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
/* Size could have been taken from stat() but this reduces the race condition interval
|
||||
* There is still one without flock
|
||||
*/
|
||||
fseek(f, 0, SEEK_END);
|
||||
fsize = ftell(f);
|
||||
/* Extra sanity check, had some problems with wrong file types */
|
||||
if (fsz != fsize){
|
||||
clicon_debug(1, "%s Error file %s size mismatch sz:%zu vs %zu",
|
||||
__FUNCTION__, filename, fsz, fsize);
|
||||
if (api_http_data_err(h, req, 500) < 0) /* Internal error? */
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
fseek(f, 0, SEEK_SET); /* same as rewind(f); */
|
||||
if ((cbdata = cbuf_new_alloc(fsize+1)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new_alloc");
|
||||
|
|
@ -313,11 +356,11 @@ api_http_data_file(clicon_handle h,
|
|||
goto done;
|
||||
}
|
||||
if (sz != 1){
|
||||
clicon_debug(1, "%s Error fread(%s) sz:%zu", __FUNCTION__, filename, sz);
|
||||
if (api_http_data_err(h, req, 500) < 0) /* Internal error? */
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
clicon_debug(1, "%s code:%d", __FUNCTION__, code);
|
||||
str[fsize] = 0;
|
||||
if (cbuf_append_str(cbdata, str) < 0){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_append_str");
|
||||
|
|
@ -329,6 +372,7 @@ api_http_data_file(clicon_handle h,
|
|||
goto done;
|
||||
cbdata = NULL; /* consumed by reply-send */
|
||||
ok:
|
||||
clicon_debug(1, "%s Read %s OK", __FUNCTION__, filename);
|
||||
retval = 0;
|
||||
done:
|
||||
if (str)
|
||||
|
|
|
|||
|
|
@ -438,8 +438,8 @@ restconf_http1_path_root(clicon_handle h,
|
|||
if (api_http_data(h, sd, sd->sd_qvec) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (api_root_restconf(h, sd, sd->sd_qvec) < 0) /* error handling */
|
||||
goto done;
|
||||
else
|
||||
sd->sd_code = 404; /* catch all without body/media */
|
||||
fail:
|
||||
if (restconf_param_del_all(h) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -473,9 +473,9 @@ http2_exec(restconf_conn *rc,
|
|||
if ((sd->sd_path = restconf_uripath(rc->rc_h)) == NULL)
|
||||
goto done;
|
||||
sd->sd_proto = HTTP_2; /* XXX is this necessary? */
|
||||
if (strncmp(sd->sd_path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0
|
||||
|| strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0
|
||||
|| api_path_is_data(rc->rc_h)){
|
||||
if (strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0
|
||||
|| api_path_is_restconf(rc->rc_h)
|
||||
|| api_path_is_data(rc->rc_h)){
|
||||
if (restconf_nghttp2_path(sd) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,15 +80,15 @@ api_path_is_restconf(clicon_handle h)
|
|||
{
|
||||
int retval = 0;
|
||||
char *path = NULL;
|
||||
char *restconf_path = RESTCONF_API;
|
||||
char *restconf_api_path;
|
||||
|
||||
if ((path = restconf_uripath(h)) == NULL)
|
||||
goto done;
|
||||
if (strlen(path) < 1 + strlen(restconf_path)) /* "/" + restconf */
|
||||
if ((restconf_api_path = clicon_option_str(h, "CLICON_RESTCONF_API_ROOT")) == NULL)
|
||||
goto done;
|
||||
if (strlen(path) < strlen(restconf_api_path)) /* "/" + restconf */
|
||||
goto done;
|
||||
if (path[0] != '/')
|
||||
goto done;
|
||||
if (strncmp(path+1, restconf_path, strlen(restconf_path)) != 0)
|
||||
if (strncmp(path, restconf_api_path, strlen(restconf_api_path)) != 0)
|
||||
goto done;
|
||||
retval = 1;
|
||||
done:
|
||||
|
|
@ -510,14 +510,7 @@ api_root_restconf(clicon_handle h,
|
|||
goto ok;
|
||||
}
|
||||
if (strlen(pvec[0]) != 0){
|
||||
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /restconf/ expected") < 0)
|
||||
goto done;
|
||||
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
goto ok;
|
||||
}
|
||||
if (strcmp(pvec[1], RESTCONF_API)){
|
||||
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /restconf/ expected") < 0)
|
||||
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, restconf api root expected") < 0)
|
||||
goto done;
|
||||
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@
|
|||
/*
|
||||
* Constants
|
||||
*/
|
||||
#define RESTCONF_API "restconf"
|
||||
#define IETF_YANG_LIBRARY_REVISION "2019-01-04"
|
||||
|
||||
/* RESTCONF enables deployments to specify where the RESTCONF API is
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue