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:
Olof hagsand 2022-04-28 16:07:26 +02:00
parent 84d88c8ad8
commit f11e4bad6c
10 changed files with 197 additions and 113 deletions

View file

@ -40,20 +40,19 @@ Expected: May 2022
### New features ### New features
* Extended the Restconf implementation with a limited http-data static service * Extended the Restconf implementation with a limited http-data static service
* Added two new config options to clixon-config.yang: * The limited implementation is as follows:
* `CLICON_HTTP_DATA_PATH` * path: Local static files within `CLICON_WWW_DATA_ROOT`
* `CLICON_HTTP_DATA_ROOT` * operation GET, HEAD, or OPTIONS
* Added feature http-data to restconf-config.yang and the following option: * query parameters not supported
* `enable-http-data` * no indata
* The limited implementation is as follows: * media: html, css, js, fonts, image.
* path: Local static files within `CLICON_WWW_DATA_ROOT` * authentication, TLS, http/2 as restconf
* operation GET, HEAD, or OPTIONS * Added two new config options to clixon-config.yang:
* query parameters not supported * `CLICON_HTTP_DATA_PATH`
* no indata * `CLICON_HTTP_DATA_ROOT`
* media: html, css, js, fonts, image, * Added feature http-data to restconf-config.yang and the following option that needs to be true
7. Authentication, TLS, http/2 as restconf * `enable-http-data`
Generic changes: * Added `HTTP_DATA_INTERNAL_REDIRECT` compile-time option for internal redirects to `index.html`
* Uniform path selection across fcgi, native http/1 + http/2
* Implementation of "chunked framing" according to RFC6242 for Netconf 1.1. * Implementation of "chunked framing" according to RFC6242 for Netconf 1.1.
* First hello is 1.0 EOM framing, then successing rpc is chunked framing * First hello is 1.0 EOM framing, then successing rpc is chunked framing
@ -65,10 +64,13 @@ Generic changes:
Users may have to change how they access the system Users may have to change how they access the system
* Restconf
* Added 404 return without body if neither restconf, data or streams prefix match
* Netconf: Usage of chunked framing" * Netconf: Usage of chunked framing"
* To keep existing end-of-message encoding, set `CLICON_NETCONF_BASE_CAPABILITY` to `0` * To keep existing end-of-message encoding, set `CLICON_NETCONF_BASE_CAPABILITY` to `0`
* New `clixon-config@2022-03-21.yang` revision * New `clixon-config@2022-03-21.yang` revision
* Added option: * Added option:
* `CLICON_RESTCONF_API_ROOT`
* `CLICON_NETCONF_BASE_CAPABILITY` * `CLICON_NETCONF_BASE_CAPABILITY`
* `CLICON_HTTP_DATA_PATH` * `CLICON_HTTP_DATA_PATH`
* `CLICON_HTTP_DATA_ROOT` * `CLICON_HTTP_DATA_ROOT`
@ -106,6 +108,7 @@ Users may have to change how they access the system
### Corrected Bugs ### Corrected Bugs
* Fixed: HTTP/1 parse error for '/' path
* Fixed: YANG if-feature in config file of disables feature did not work, was always on * Fixed: YANG if-feature in config file of disables feature did not work, was always on
* This does not apply to the datastore, only the config file itself. * This does not apply to the datastore, only the config file itself.
* Fixed: YANG key list check bad performance * Fixed: YANG key list check bad performance

View file

@ -41,6 +41,7 @@
%union { %union {
char *string; char *string;
int intval; int intval;
void *stack;
} }
%token SP %token SP
@ -62,8 +63,8 @@
%token <intval> DIGIT %token <intval> DIGIT
%type <string> body %type <string> body
%type <string> absolute_paths %type <stack> absolute_paths
%type <string> absolute_paths1 %type <stack> absolute_paths1
%type <string> absolute_path %type <string> absolute_path
%type <string> field_vchars %type <string> field_vchars
%type <string> field_values %type <string> field_values
@ -259,16 +260,16 @@ method : TOKEN
*/ */
request_target : absolute_paths1 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; YYABORT;
free($1); cbuf_free($1);
_PARSE_DEBUG("request-target -> absolute-paths1"); _PARSE_DEBUG("request-target -> absolute-paths1");
} }
| absolute_paths1 QMARK QUERY | 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; YYABORT;
free($1); cbuf_free($1);
if (http1_parse_query(_HY, $3) < 0) if (http1_parse_query(_HY, $3) < 0)
YYABORT; YYABORT;
free($3); free($3);
@ -285,21 +286,25 @@ absolute_paths1 : absolute_paths
{ $$ = $1;_PARSE_DEBUG("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 absolute_paths : absolute_paths absolute_path
{ {
if (($$ = clixon_string_del_join($1, "/", $2)) == NULL) { free($2); YYABORT;} $$ = $1;
free($2); cprintf($$, "/");
if ($2)
cprintf($$, "%s", $2);
_PARSE_DEBUG("absolute-paths -> absolute-paths absolute-path"); _PARSE_DEBUG("absolute-paths -> absolute-paths absolute-path");
} }
| absolute_path | absolute_path
{ {
if (($$ = clixon_string_del_join(NULL, "/", $1)) == NULL) { free($1); YYABORT;} if (($$ = cbuf_new()) == NULL){ YYABORT;}
free($1); cprintf($$, "/");
if ($1)
cprintf($$, "%s", $1);
_PARSE_DEBUG("absolute-paths -> absolute-path"); _PARSE_DEBUG("absolute-paths -> absolute-path");
} }
; ;
/* segment = <segment, see [RFC3986], Section 3.3> /* segment = <segment, see [RFC3986], Section 3.3>
* segment = *pchar * segment = *pchar
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@" * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
@ -310,8 +315,13 @@ absolute_paths : absolute_paths absolute_path
*/ */
absolute_path : SLASH PCHARS absolute_path : SLASH PCHARS
{ {
if (($$=strdup($2)) == NULL) YYABORT; $$ = $2;
_PARSE_DEBUG("absolute-path -> PCHARS"); _PARSE_DEBUG("absolute-path -> / PCHARS");
}
| SLASH
{
$$ = NULL;
_PARSE_DEBUG("absolute-path -> /");
} }
; ;

View file

@ -160,69 +160,105 @@ api_http_data_err(clicon_handle h,
/*! Check validity of path, may only be regular dir or file /*! Check validity of path, may only be regular dir or file
* No .., soft link, ~, etc * No .., soft link, ~, etc
* @param[in] prefix Prefix of path0, where to start file check * @param[in] h Clicon handle
* @param[in] path0 Filepath * @param[in] req Generic Www handle (can be part of clixon handle)
* @param[out] code Error code, if retval = 0 * @param[in] prefix Prefix of path0, where to start file check
* @retval -1 Error * @param[in,out] cbpath Filepath as cbuf, internal redirection may change it
* @retval 0 Invalid, code set * @param[out] fp Open file, if retval = 1
* @retval 1 OK * @param[out] fsz Size of file, if retval = 1
* @retval -1 Error
* @retval 0 Invalid
* @retval 1 OK, fp,fsz set
*/ */
static int static int
check_file_path(char *prefix, http_data_check_file_path(clicon_handle h,
char *path0, void *req,
int *code) char *prefix,
cbuf *cbpath,
FILE **fp,
off_t *fsz)
{ {
int retval = -1; int retval = -1;
char *path = NULL;
int i;
struct stat fstat; struct stat fstat;
char *p;
int i;
int code = 0;
FILE *f;
if (prefix == NULL || path0 == NULL || code == NULL){ if (prefix == NULL || cbpath == NULL || fp == NULL){
clicon_err(OE_UNIX, EINVAL, "prefix, path0 or code is NULL"); clicon_err(OE_UNIX, EINVAL, "prefix, cbpath0 or fp is NULL");
goto done; goto done;
} }
if ((path = strdup(path0)) == NULL){ p = cbuf_get(cbpath);
clicon_err(OE_UNIX, errno, "strdup"); 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; goto done;
} }
for (i=strlen(prefix); i<strlen(path0); i++){ for (i=strlen(prefix); i<strlen(p); i++){
if (path[i] == '/'){ /* Check valid dir */ if (p[i] == '/'){ /* Check valid dir */
path[i] = '\0'; p[i] = '\0';
/* Ensure not soft link */ /* Ensure not soft link */
if (lstat(path, &fstat) < 0){ if (lstat(p, &fstat) < 0){
*code = 404; clicon_debug(1, "%s Error lstat(%s):%s", __FUNCTION__, p, strerror(errno));
code = 404;
goto invalid; goto invalid;
} }
if (!S_ISDIR(fstat.st_mode)){ if (!S_ISDIR(fstat.st_mode)){
*code = 403; clicon_debug(1, "%s Error lstat(%s): Not dir", __FUNCTION__, p);
code = 403;
goto invalid; goto invalid;
} }
path[i] = '/'; p[i] = '/';
} }
else if (path[i] == '~'){ else if (p[i] == '~'){
*code = 403; clicon_debug(1, "%s Error lstat(%s): ~ not allowed in file path", __FUNCTION__, p);
code = 403;
goto invalid; goto invalid;
} }
else if (path[i] == '.' && i>strlen(prefix) && path[i-1] == '.'){ else if (p[i] == '.' && i>strlen(prefix) && p[i-1] == '.'){
*code = 403; clicon_debug(1, "%s Error lstat(%s): .. not allowed in file path", __FUNCTION__, p);
code = 403;
goto invalid; goto invalid;
} }
} }
/* Resulting file (ensure not soft link) */ /* Resulting file (ensure not soft link) */
if (lstat(path, &fstat) < 0){ if (lstat(p, &fstat) < 0){
*code = 404; clicon_debug(1, "%s Error lstat(%s):%s", __FUNCTION__, p, strerror(errno));
code = 404;
goto invalid; 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)){ if (!S_ISREG(fstat.st_mode)){
*code = 403; clicon_debug(1, "%s Error lstat(%s): Not regular file", __FUNCTION__, p);
code = 403;
goto invalid; 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 */ retval = 1; /* OK */
done: done:
if (path)
free(path);
return retval; return retval;
invalid: invalid:
if (api_http_data_err(h, req, code) < 0)
goto done;
retval = 0; retval = 0;
goto done; goto done;
} }
@ -231,10 +267,9 @@ check_file_path(char *prefix,
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] req Generic Www handle (can be part of clixon 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] 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 * @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 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 static int
api_http_data_file(clicon_handle h, api_http_data_file(clicon_handle h,
@ -247,15 +282,16 @@ api_http_data_file(clicon_handle h,
char *filename; char *filename;
cbuf *cbdata = NULL; cbuf *cbdata = NULL;
FILE *f = NULL; FILE *f = NULL;
off_t fsz = 0;
long fsize; long fsize;
char *www_data_root = NULL; char *www_data_root = NULL;
char *suffix; char *suffix;
char *media; char *media;
int ret; int ret;
int code = 0;
char *str = NULL; char *str = NULL;
size_t sz; size_t sz;
clicon_debug(1, "%s", __FUNCTION__);
if ((cbfile = cbuf_new()) == NULL){ if ((cbfile = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new"); clicon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
@ -265,7 +301,24 @@ api_http_data_file(clicon_handle h,
goto done; 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"; media = "application/octet-stream";
} }
else { else {
@ -273,29 +326,19 @@ api_http_data_file(clicon_handle h,
if ((media = clicon_str2str(mime_map, suffix)) == NULL) if ((media = clicon_str2str(mime_map, suffix)) == NULL)
media = "application/octet-stream"; 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 /* Size could have been taken from stat() but this reduces the race condition interval
* There is still one without flock * There is still one without flock
*/ */
fseek(f, 0, SEEK_END); fseek(f, 0, SEEK_END);
fsize = ftell(f); 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); */ fseek(f, 0, SEEK_SET); /* same as rewind(f); */
if ((cbdata = cbuf_new_alloc(fsize+1)) == NULL){ if ((cbdata = cbuf_new_alloc(fsize+1)) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new_alloc"); clicon_err(OE_UNIX, errno, "cbuf_new_alloc");
@ -313,11 +356,11 @@ api_http_data_file(clicon_handle h,
goto done; goto done;
} }
if (sz != 1){ 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? */ if (api_http_data_err(h, req, 500) < 0) /* Internal error? */
goto done; goto done;
goto ok; goto ok;
} }
clicon_debug(1, "%s code:%d", __FUNCTION__, code);
str[fsize] = 0; str[fsize] = 0;
if (cbuf_append_str(cbdata, str) < 0){ if (cbuf_append_str(cbdata, str) < 0){
clicon_err(OE_UNIX, errno, "cbuf_append_str"); clicon_err(OE_UNIX, errno, "cbuf_append_str");
@ -329,6 +372,7 @@ api_http_data_file(clicon_handle h,
goto done; goto done;
cbdata = NULL; /* consumed by reply-send */ cbdata = NULL; /* consumed by reply-send */
ok: ok:
clicon_debug(1, "%s Read %s OK", __FUNCTION__, filename);
retval = 0; retval = 0;
done: done:
if (str) if (str)

View file

@ -438,8 +438,8 @@ restconf_http1_path_root(clicon_handle h,
if (api_http_data(h, sd, sd->sd_qvec) < 0) if (api_http_data(h, sd, sd->sd_qvec) < 0)
goto done; goto done;
} }
else if (api_root_restconf(h, sd, sd->sd_qvec) < 0) /* error handling */ else
goto done; sd->sd_code = 404; /* catch all without body/media */
fail: fail:
if (restconf_param_del_all(h) < 0) if (restconf_param_del_all(h) < 0)
goto done; goto done;

View file

@ -473,9 +473,9 @@ http2_exec(restconf_conn *rc,
if ((sd->sd_path = restconf_uripath(rc->rc_h)) == NULL) if ((sd->sd_path = restconf_uripath(rc->rc_h)) == NULL)
goto done; goto done;
sd->sd_proto = HTTP_2; /* XXX is this necessary? */ sd->sd_proto = HTTP_2; /* XXX is this necessary? */
if (strncmp(sd->sd_path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0 if (strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0
|| strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0 || api_path_is_restconf(rc->rc_h)
|| api_path_is_data(rc->rc_h)){ || api_path_is_data(rc->rc_h)){
if (restconf_nghttp2_path(sd) < 0) if (restconf_nghttp2_path(sd) < 0)
goto done; goto done;
} }

View file

@ -80,15 +80,15 @@ api_path_is_restconf(clicon_handle h)
{ {
int retval = 0; int retval = 0;
char *path = NULL; char *path = NULL;
char *restconf_path = RESTCONF_API; char *restconf_api_path;
if ((path = restconf_uripath(h)) == NULL) if ((path = restconf_uripath(h)) == NULL)
goto done; 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; goto done;
if (path[0] != '/') if (strncmp(path, restconf_api_path, strlen(restconf_api_path)) != 0)
goto done;
if (strncmp(path+1, restconf_path, strlen(restconf_path)) != 0)
goto done; goto done;
retval = 1; retval = 1;
done: done:
@ -510,14 +510,7 @@ api_root_restconf(clicon_handle h,
goto ok; goto ok;
} }
if (strlen(pvec[0]) != 0){ if (strlen(pvec[0]) != 0){
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;
goto ok;
}
if (strcmp(pvec[1], RESTCONF_API)){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /restconf/ expected") < 0)
goto done; goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0) if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done; goto done;

View file

@ -41,7 +41,6 @@
/* /*
* Constants * Constants
*/ */
#define RESTCONF_API "restconf"
#define IETF_YANG_LIBRARY_REVISION "2019-01-04" #define IETF_YANG_LIBRARY_REVISION "2019-01-04"
/* RESTCONF enables deployments to specify where the RESTCONF API is /* RESTCONF enables deployments to specify where the RESTCONF API is

View file

@ -99,6 +99,12 @@
*/ */
#define RESTCONF_NETNS_DEFAULT "default" #define RESTCONF_NETNS_DEFAULT "default"
/*! If set make an internal redirect if URI path indetifies a directory
* For example, path is /local, and redirect is 'index.html, the request
* will be redirected to /local/index.html
*/
#define HTTP_DATA_INTERNAL_REDIRECT "index.html"
/*! Set a temporary parent for use in special case "when" xpath calls /*! Set a temporary parent for use in special case "when" xpath calls
* Problem is when changing an existing (candidate) in-memory datastore that yang "when" conditionals * Problem is when changing an existing (candidate) in-memory datastore that yang "when" conditionals
* should be changed in clixon_datastore_write.c:text_modify(). * should be changed in clixon_datastore_write.c:text_modify().

View file

@ -4,7 +4,7 @@
# Get them via http and https # Get them via http and https
# Send options and head request # Send options and head request
# Errors: not found, post, # Errors: not found, post,
# XXX: feature disabled # See RFC 7230
# Magic line must be first in script (see README.md) # Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
@ -125,12 +125,15 @@ function testrun()
RESTCONFIG=$(restconf_config none false $proto $enable) RESTCONFIG=$(restconf_config none false $proto $enable)
datapath=/data if true; then
wdir=$dir/www # Proper test setup
# Host setup: datapath=/data
# datapath=/ wdir=$dir/www
# wdir=/var/www/html else
# Experiments with local host
datapath=/
wdir=/var/www/html
fi
# Clixon config # Clixon config
cat <<EOF > $cfg cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config"> <clixon-config xmlns="http://clicon.org/config">
@ -191,9 +194,24 @@ EOF
# echo "curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/index.html" # echo "curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/index.html"
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/index.html)" 0 "HTTP/$HVER 404" expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/index.html)" 0 "HTTP/$HVER 404"
else else
new "WWW get html" new "WWW get root expect 404 without body"
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/)" 0 "HTTP/$HVER 404" --not-- "Content-Type"
new "WWW get index.html"
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/index.html)" 0 "HTTP/$HVER 200" "Content-Type: text/html" "<title>Welcome to Clixon!</title>" expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/index.html)" 0 "HTTP/$HVER 200" "Content-Type: text/html" "<title>Welcome to Clixon!</title>"
new "WWW get dir -> expect index.html"
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data)" 0 "HTTP/$HVER 200" "Content-Type: text/html" "<title>Welcome to Clixon!</title>"
# remove index
mv $dir/www/data/index.html $dir/www/data/tmp.index.html
new "WWW get dir -> no indirection expect 404"
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data)" 0 "HTTP/$HVER 404" "Content-Type: text/html" "<title>404 Not Found</title>"
# move index back
mv $dir/www/data/tmp.index.html $dir/www/data/index.html
new "WWW get css" new "WWW get css"
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/example.css)" 0 "HTTP/$HVER 200" "Content-Type: text/css" "display: inline;" --not-- "Content-Type: text/html" expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/example.css)" 0 "HTTP/$HVER 200" "Content-Type: text/css" "display: inline;" --not-- "Content-Type: text/html"
@ -210,6 +228,7 @@ EOF
new "WWW get http soft link" new "WWW get http soft link"
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/inside.html)" 0 "HTTP/$HVER 403" "Content-Type: text/html" "<title>403 Forbidden</title>" --not-- "<title>Dont access this</title>" expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/inside.html)" 0 "HTTP/$HVER 403" "Content-Type: text/html" "<title>403 Forbidden</title>" --not-- "<title>Dont access this</title>"
if [ ! -f /.dockerenv ] ; then # XXX Privs dont not work on docker/alpine? if [ ! -f /.dockerenv ] ; then # XXX Privs dont not work on docker/alpine?
new "WWW get http not read access" new "WWW get http not read access"
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/noread.html)" 0 "HTTP/$HVER 403" "Content-Type: text/html" "<title>403 Forbidden</title>" expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/noread.html)" 0 "HTTP/$HVER 403" "Content-Type: text/html" "<title>403 Forbidden</title>"

View file

@ -49,6 +49,7 @@ module clixon-config {
revision 2022-03-21 { revision 2022-03-21 {
description description
"Added option: "Added option:
CLICON_RESTCONF_API_ROOT
CLICON_NETCONF_BASE_CAPABILITY CLICON_NETCONF_BASE_CAPABILITY
CLICON_HTTP_DATA_PATH CLICON_HTTP_DATA_PATH
CLICON_HTTP_DATA_ROOT CLICON_HTTP_DATA_ROOT
@ -535,6 +536,13 @@ module clixon-config {
RFC6242 for example. RFC6242 for example.
This only applies to the external NETCONF"; This only applies to the external NETCONF";
} }
leaf CLICON_RESTCONF_API_ROOT {
type string;
default "/restconf";
description
"The RESTCONF API root path
See RFC 8040 Sec 1.16 and 3.1";
}
leaf CLICON_RESTCONF_DIR { leaf CLICON_RESTCONF_DIR {
type string; type string;
description description
@ -617,7 +625,7 @@ module clixon-config {
Both feature clixon-restconf:http-data and restconf/enable-http-data Both feature clixon-restconf:http-data and restconf/enable-http-data
must be enabled for this match to occur."; must be enabled for this match to occur.";
} }
leaf CLICON_HTTP_DATA_ROOT { leaf CLICON_HTTP_DATA_ROOT{
if-feature "clrc:http-data"; if-feature "clrc:http-data";
type string; type string;
default "/var/www"; default "/var/www";
@ -1065,7 +1073,9 @@ module clixon-config {
default "streams"; default "streams";
description description
"Stream path appended to CLICON_STREAM_URL to form "Stream path appended to CLICON_STREAM_URL to form
stream subscription URL."; stream subscription URL.
See CLICON_RESTCONF_API_ROOT and CLICON_HTTP_DATA_ROOT
Should be changed to include '/' ";
} }
leaf CLICON_STREAM_URL { leaf CLICON_STREAM_URL {
type string; type string;