tool: add "variable" support

Add support for command line variables. Set variables with --variable
name=content or --variable name@file (where "file" can be stdin if set
to a single dash (-)).

Variable content is expanded in option parameters using "{{name}}"
(without the quotes) if the option name is prefixed with
"--expand-". This gets the contents of the variable "name" inserted, or
a blank if the name does not exist as a variable. Insert "{{" verbatim
in the string by prefixing it with a backslash, like "\\{{".

Import an environment variable with --variable %name. It makes curl exit
with an error if the environment variable is not set. It can also rather
get a default value if the variable does not exist, using =content or
@file like shown above.

Example: get the USER environment variable into the URL:

 --variable %USER
 --expand-url = "https://example.com/api/{{USER}}/method"

When expanding variables, curl supports a set of functions that can make
the variable contents more convenient to use. It can trim leading and
trailing white space with "trim", output the contents as a JSON quoted
string with "json", URL encode it with "url" and base 64 encode it with
"b64". To apply functions to a variable expansion, add them colon
separated to the right side of the variable. They are then performed in
a left to right order.

Example: get the contents of a file called $HOME/.secret into a variable
called "fix". Make sure that the content is trimmed and percent-encoded
sent as POST data:

  --variable %HOME=/home/default
  --expand-variable fix@{{HOME}}/.secret
  --expand-data "{{fix:trim:url}}"
  https://example.com/

Documented. Many new test cases.

Co-brainstormed-by: Emanuele Torre
Assisted-by: Jat Satiro
Closes #11346
This commit is contained in:
Daniel Stenberg 2023-07-31 11:50:28 +02:00
parent 47a3e6e577
commit 2e160c9c65
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
32 changed files with 1562 additions and 258 deletions

View file

@ -63,7 +63,7 @@ endif()
# CURL_CFILES, CURLX_CFILES, CURL_HFILES come from Makefile.inc
if(BUILD_STATIC_CURL)
set(CURLX_CFILES ../lib/dynbuf.c)
set(CURLX_CFILES ../lib/dynbuf.c ../lib/base64.c)
endif()
add_executable(

View file

@ -32,13 +32,14 @@
# libcurl has sources that provide functions named curlx_* that aren't part of
# the official API, but we re-use the code here to avoid duplication.
CURLX_CFILES = \
../lib/base64.c \
../lib/curl_multibyte.c \
../lib/dynbuf.c \
../lib/nonblock.c \
../lib/strtoofft.c \
../lib/timediff.c \
../lib/nonblock.c \
../lib/warnless.c \
../lib/curl_multibyte.c \
../lib/version_win32.c \
../lib/dynbuf.c
../lib/warnless.c
CURLX_HFILES = \
../lib/curl_setup.h \
@ -91,7 +92,8 @@ CURL_CFILES = \
tool_vms.c \
tool_writeout.c \
tool_writeout_json.c \
tool_xattr.c
tool_xattr.c \
var.c
CURL_HFILES = \
slist_wc.h \
@ -135,7 +137,8 @@ CURL_HFILES = \
tool_vms.h \
tool_writeout.h \
tool_writeout_json.h \
tool_xattr.h
tool_xattr.h \
var.h
CURL_RCFILES = curl.rc

View file

@ -26,6 +26,7 @@
#include "tool_setup.h"
#include "tool_sdecls.h"
#include "tool_urlglob.h"
#include "var.h"
struct GlobalConfig;
@ -322,6 +323,7 @@ struct GlobalConfig {
unsigned short parallel_max; /* MAX_PARALLEL is the maximum */
bool parallel_connect;
char *help_category; /* The help category, if set */
struct var *variables;
struct OperationConfig *first;
struct OperationConfig *current;
struct OperationConfig *last; /* Always last in the struct */

File diff suppressed because it is too large Load diff

View file

@ -48,6 +48,7 @@ typedef enum {
PARAM_CONTDISP_SHOW_HEADER, /* --include and --remote-header-name */
PARAM_CONTDISP_RESUME_FROM, /* --continue-at and --remote-header-name */
PARAM_READ_ERROR,
PARAM_EXPAND_ERROR, /* --expand problem */
PARAM_LAST
} ParameterError;

View file

@ -76,6 +76,8 @@ const char *param2text(int res)
return "--continue-at and --remote-header-name cannot be combined";
case PARAM_READ_ERROR:
return "error encountered when reading a file";
case PARAM_EXPAND_ERROR:
return "variable expansion failure";
default:
return "unknown error";
}

View file

@ -246,12 +246,12 @@ const struct helptxt helptext[] = {
{" --happy-eyeballs-timeout-ms <milliseconds>",
"Time for IPv6 before trying IPv4",
CURLHELP_CONNECTION},
{" --haproxy-clientip",
"Sets client IP in HAProxy PROXY protocol v1 header",
CURLHELP_HTTP | CURLHELP_PROXY},
{" --haproxy-protocol",
"Send HAProxy PROXY protocol v1 header",
CURLHELP_HTTP | CURLHELP_PROXY},
{" --haproxy-clientip",
"Sets the HAProxy PROXY protocol v1 client IP",
CURLHELP_HTTP | CURLHELP_PROXY},
{"-I, --head",
"Show document info only",
CURLHELP_HTTP | CURLHELP_FTP | CURLHELP_FILE},
@ -760,7 +760,7 @@ const struct helptxt helptext[] = {
"Like --trace, but without hex output",
CURLHELP_VERBOSE},
{" --trace-ids",
"Add transfer/connection identifiers to trace/verbose output",
"Add transfer and connection identifiers to trace/verbose output",
CURLHELP_VERBOSE},
{" --trace-time",
"Add time stamps to trace/verbose output",
@ -786,6 +786,9 @@ const struct helptxt helptext[] = {
{"-A, --user-agent <name>",
"Send User-Agent <name> to server",
CURLHELP_IMPORTANT | CURLHELP_HTTP},
{" --variable <name=text/@file>",
"Set variable",
CURLHELP_CURL},
{"-v, --verbose",
"Make the operation more talkative",
CURLHELP_IMPORTANT | CURLHELP_VERBOSE},

View file

@ -2756,36 +2756,39 @@ CURLcode operate(struct GlobalConfig *global, int argc, argv_item_t argv[])
/* Cleanup the libcurl source output */
easysrc_cleanup();
}
return CURLE_OUT_OF_MEMORY;
result = CURLE_OUT_OF_MEMORY;
}
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS);
if(!result) {
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
curl_share_setopt(share, CURLSHOPT_SHARE,
CURL_LOCK_DATA_SSL_SESSION);
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS);
/* Get the required arguments for each operation */
do {
result = get_args(operation, count++);
/* Get the required arguments for each operation */
do {
result = get_args(operation, count++);
operation = operation->next;
} while(!result && operation);
operation = operation->next;
} while(!result && operation);
/* Set the current operation pointer */
global->current = global->first;
/* Set the current operation pointer */
global->current = global->first;
/* now run! */
result = run_all_transfers(global, share, result);
/* now run! */
result = run_all_transfers(global, share, result);
curl_share_cleanup(share);
if(global->libcurl) {
/* Cleanup the libcurl source output */
easysrc_cleanup();
curl_share_cleanup(share);
if(global->libcurl) {
/* Cleanup the libcurl source output */
easysrc_cleanup();
/* Dump the libcurl code if previously enabled */
dumpeasysrc(global);
/* Dump the libcurl code if previously enabled */
dumpeasysrc(global);
}
}
}
else
@ -2793,5 +2796,7 @@ CURLcode operate(struct GlobalConfig *global, int argc, argv_item_t argv[])
}
}
varcleanup(global);
return result;
}

View file

@ -31,50 +31,73 @@
#include "tool_writeout_json.h"
#include "tool_writeout.h"
void jsonWriteString(FILE *stream, const char *in, bool lowercase)
#define MAX_JSON_STRING 100000
/* provide the given string in dynbuf as a quoted json string, but without the
outer quotes. The buffer is not inited by this function.
Return 0 on success, non-zero on error.
*/
int jsonquoted(const char *in, size_t len,
struct curlx_dynbuf *out, bool lowercase)
{
const char *i = in;
const char *in_end = in + strlen(in);
const char *in_end = &in[len];
CURLcode result = CURLE_OK;
fputc('\"', stream);
for(; i < in_end; i++) {
for(; (i < in_end) && !result; i++) {
switch(*i) {
case '\\':
fputs("\\\\", stream);
result = curlx_dyn_addn(out, "\\\\", 2);
break;
case '\"':
fputs("\\\"", stream);
result = curlx_dyn_addn(out, "\\\"", 2);
break;
case '\b':
fputs("\\b", stream);
result = curlx_dyn_addn(out, "\\b", 2);
break;
case '\f':
fputs("\\f", stream);
result = curlx_dyn_addn(out, "\\f", 2);
break;
case '\n':
fputs("\\n", stream);
result = curlx_dyn_addn(out, "\\n", 2);
break;
case '\r':
fputs("\\r", stream);
result = curlx_dyn_addn(out, "\\r", 2);
break;
case '\t':
fputs("\\t", stream);
result = curlx_dyn_addn(out, "\\t", 2);
break;
default:
if(*i < 32) {
fprintf(stream, "\\u%04x", *i);
}
if(*i < 32)
result = curlx_dyn_addf(out, "\\u%04x", *i);
else {
char out = *i;
if(lowercase && (out >= 'A' && out <= 'Z'))
char o = *i;
if(lowercase && (o >= 'A' && o <= 'Z'))
/* do not use tolower() since that's locale specific */
out |= ('a' - 'A');
fputc(out, stream);
o |= ('a' - 'A');
result = curlx_dyn_addn(out, &o, 1);
}
break;
}
}
fputc('\"', stream);
if(result)
return (int)result;
return 0;
}
void jsonWriteString(FILE *stream, const char *in, bool lowercase)
{
struct curlx_dynbuf out;
curlx_dyn_init(&out, MAX_JSON_STRING);
if(!jsonquoted(in, strlen(in), &out, lowercase)) {
fputc('\"', stream);
if(curlx_dyn_len(&out))
fputs(curlx_dyn_ptr(&out), stream);
fputc('\"', stream);
}
curlx_dyn_free(&out);
}
void ourWriteOutJSON(FILE *stream, const struct writeoutvar mappings[],

View file

@ -26,6 +26,9 @@
#include "tool_setup.h"
#include "tool_writeout.h"
int jsonquoted(const char *in, size_t len,
struct curlx_dynbuf *out, bool lowercase);
void ourWriteOutJSON(FILE *stream, const struct writeoutvar mappings[],
struct per_transfer *per, CURLcode per_result);
void headerJSON(FILE *stream, struct per_transfer *per);

462
src/var.c Normal file
View file

@ -0,0 +1,462 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "tool_setup.h"
#define ENABLE_CURLX_PRINTF
/* use our own printf() functions */
#include "curlx.h"
#include "tool_cfgable.h"
#include "tool_getparam.h"
#include "tool_helpers.h"
#include "tool_findfile.h"
#include "tool_msgs.h"
#include "tool_parsecfg.h"
#include "dynbuf.h"
#include "curl_base64.h"
#include "tool_paramhlp.h"
#include "tool_writeout_json.h"
#include "var.h"
#include "memdebug.h" /* keep this as LAST include */
#define MAX_EXPAND_CONTENT 10000000
static char *Memdup(const char *data, size_t len)
{
char *p = malloc(len + 1);
if(!p)
return NULL;
if(len)
memcpy(p, data, len);
p[len] = 0;
return p;
}
/* free everything */
void varcleanup(struct GlobalConfig *global)
{
struct var *list = global->variables;
while(list) {
struct var *t = list;
list = list->next;
free((char *)t->content);
free((char *)t->name);
free(t);
}
}
static const struct var *varcontent(struct GlobalConfig *global,
const char *name, size_t nlen)
{
struct var *list = global->variables;
while(list) {
if((strlen(list->name) == nlen) &&
!strncmp(name, list->name, nlen)) {
return list;
}
list = list->next;
}
return NULL;
}
#define ENDOFFUNC(x) (((x) == '}') || ((x) == ':'))
#define FUNCMATCH(ptr,name,len) \
(!strncmp(ptr, name, len) && ENDOFFUNC(ptr[len]))
#define FUNC_TRIM "trim"
#define FUNC_TRIM_LEN (sizeof(FUNC_TRIM) - 1)
#define FUNC_JSON "json"
#define FUNC_JSON_LEN (sizeof(FUNC_JSON) - 1)
#define FUNC_URL "url"
#define FUNC_URL_LEN (sizeof(FUNC_URL) - 1)
#define FUNC_B64 "b64"
#define FUNC_B64_LEN (sizeof(FUNC_B64) - 1)
static ParameterError varfunc(struct GlobalConfig *global,
char *c, /* content */
size_t clen, /* content length */
char *f, /* functions */
size_t flen, /* function string length */
struct curlx_dynbuf *out)
{
bool alloc = FALSE;
ParameterError err = PARAM_OK;
const char *finput = f;
/* The functions are independent and runs left to right */
while(*f && !err) {
if(*f == '}')
/* end of functions */
break;
/* On entry, this is known to be a colon already. In subsequent laps, it
is also known to be a colon since that is part of the FUNCMATCH()
checks */
f++;
if(FUNCMATCH(f, FUNC_TRIM, FUNC_TRIM_LEN)) {
size_t len = clen;
f += FUNC_TRIM_LEN;
if(clen) {
/* skip leading white space, including CRLF */
while(*c && ISSPACE(*c)) {
c++;
len--;
}
while(len && ISSPACE(c[len-1]))
len--;
}
/* put it in the output */
curlx_dyn_reset(out);
if(curlx_dyn_addn(out, c, len)) {
err = PARAM_NO_MEM;
break;
}
}
else if(FUNCMATCH(f, FUNC_JSON, FUNC_JSON_LEN)) {
f += FUNC_JSON_LEN;
curlx_dyn_reset(out);
if(clen) {
if(jsonquoted(c, clen, out, FALSE)) {
err = PARAM_NO_MEM;
break;
}
}
}
else if(FUNCMATCH(f, FUNC_URL, FUNC_URL_LEN)) {
f += FUNC_URL_LEN;
curlx_dyn_reset(out);
if(clen) {
char *enc = curl_easy_escape(NULL, c, (int)clen);
if(!enc) {
err = PARAM_NO_MEM;
break;
}
/* put it in the output */
if(curlx_dyn_add(out, enc)) {
err = PARAM_NO_MEM;
break;
}
curl_free(enc);
}
}
else if(FUNCMATCH(f, FUNC_B64, FUNC_B64_LEN)) {
f += FUNC_B64_LEN;
curlx_dyn_reset(out);
if(clen) {
char *enc;
size_t elen;
CURLcode result = curlx_base64_encode(c, clen, &enc, &elen);
if(result) {
err = PARAM_NO_MEM;
break;
}
/* put it in the output */
if(curlx_dyn_addn(out, enc, elen))
err = PARAM_NO_MEM;
curl_free(enc);
if(err)
break;
}
}
else {
/* unsupported function */
errorf(global, "unknown variable function in '%.*s'",
(int)flen, finput);
err = PARAM_EXPAND_ERROR;
break;
}
if(alloc)
free(c);
clen = curlx_dyn_len(out);
c = Memdup(curlx_dyn_ptr(out), clen);
if(!c) {
err = PARAM_NO_MEM;
break;
}
alloc = TRUE;
}
if(alloc)
free(c);
if(err)
curlx_dyn_free(out);
return err;
}
ParameterError varexpand(struct GlobalConfig *global,
const char *line, struct curlx_dynbuf *out,
bool *replaced)
{
CURLcode result;
char *envp;
bool added = FALSE;
const char *input = line;
*replaced = FALSE;
curlx_dyn_init(out, MAX_EXPAND_CONTENT);
do {
envp = strstr(line, "{{");
if((envp > line) && envp[-1] == '\\') {
/* preceding backslash, we want this verbatim */
/* insert the text up to this point, minus the backslash */
result = curlx_dyn_addn(out, line, envp - line - 1);
if(result)
return PARAM_NO_MEM;
/* output '{{' then continue from here */
result = curlx_dyn_addn(out, "{{", 2);
if(result)
return PARAM_NO_MEM;
line = &envp[2];
}
else if(envp) {
char name[128];
size_t nlen;
size_t i;
char *funcp;
char *clp = strstr(envp, "}}");
size_t prefix;
if(!clp) {
/* uneven braces */
warnf(global, "missing close '}}' in '%s'", input);
break;
}
prefix = 2;
envp += 2; /* move over the {{ */
/* if there is a function, it ends the name with a colon */
funcp = memchr(envp, ':', clp - envp);
if(funcp)
nlen = funcp - envp;
else
nlen = clp - envp;
if(!nlen || (nlen >= sizeof(name))) {
warnf(global, "bad variable name length '%s'", input);
/* insert the text as-is since this is not an env variable */
result = curlx_dyn_addn(out, line, clp - line + prefix);
if(result)
return PARAM_NO_MEM;
}
else {
/* insert the text up to this point */
result = curlx_dyn_addn(out, line, envp - prefix - line);
if(result)
return PARAM_NO_MEM;
/* copy the name to separate buffer */
memcpy(name, envp, nlen);
name[nlen] = 0;
/* verify that the name looks sensible */
for(i = 0; (i < nlen) &&
(ISALNUM(name[i]) || (name[i] == '_')); i++);
if(i != nlen) {
warnf(global, "bad variable name: %s", name);
/* insert the text as-is since this is not an env variable */
result = curlx_dyn_addn(out, envp - prefix,
clp - envp + prefix + 2);
if(result)
return PARAM_NO_MEM;
}
else {
char *value;
size_t vlen = 0;
struct curlx_dynbuf buf;
const struct var *v = varcontent(global, name, nlen);
if(v) {
value = (char *)v->content;
vlen = v->clen;
}
else
value = NULL;
curlx_dyn_init(&buf, MAX_EXPAND_CONTENT);
if(funcp) {
/* apply the list of functions on the value */
size_t flen = clp - funcp;
ParameterError err = varfunc(global, value, vlen, funcp, flen,
&buf);
if(err)
return err;
value = curlx_dyn_ptr(&buf);
vlen = curlx_dyn_len(&buf);
}
if(value && *value) {
/* A variable might contain null bytes. Such bytes cannot be shown
using normal means, this is an error. */
char *nb = memchr(value, '\0', vlen);
if(nb) {
errorf(global, "variable contains null byte");
return PARAM_EXPAND_ERROR;
}
}
/* insert the value */
result = curlx_dyn_addn(out, value, vlen);
curlx_dyn_free(&buf);
if(result)
return PARAM_NO_MEM;
added = true;
}
}
line = &clp[2];
}
} while(envp);
if(added && *line) {
/* add the "suffix" as well */
result = curlx_dyn_add(out, line);
if(result)
return PARAM_NO_MEM;
}
*replaced = added;
if(!added)
curlx_dyn_free(out);
return PARAM_OK;
}
/*
* Created in a way that is not revealing how variables is actually stored so
* that we can improve this if we want better performance when managing many
* at a later point.
*/
static ParameterError addvariable(struct GlobalConfig *global,
const char *name,
size_t nlen,
const char *content,
size_t clen,
bool contalloc)
{
struct var *p;
const struct var *check = varcontent(global, name, nlen);
if(check)
notef(global, "Overwriting variable '%s'", check->name);
p = calloc(sizeof(struct var), 1);
if(!p)
return PARAM_NO_MEM;
p->name = Memdup(name, nlen);
if(!p->name)
goto err;
p->content = contalloc ? content: Memdup(content, clen);
if(!p->content)
goto err;
p->clen = clen;
p->next = global->variables;
global->variables = p;
return PARAM_OK;
err:
free((char *)p->content);
free((char *)p->name);
free(p);
return PARAM_NO_MEM;
}
ParameterError setvariable(struct GlobalConfig *global,
const char *input)
{
const char *name;
size_t nlen;
char *content = NULL;
size_t clen = 0;
bool contalloc = FALSE;
const char *line = input;
ParameterError err = PARAM_OK;
bool import = FALSE;
char *ge = NULL;
if(*input == '%') {
import = TRUE;
line++;
}
name = line;
while(*line && (ISALNUM(*line) || (*line == '_')))
line++;
nlen = line - name;
if(!nlen || (nlen > 128)) {
warnf(global, "Bad variable name length (%zd), skipping", nlen);
return PARAM_OK;
}
if(import) {
ge = curl_getenv(name);
if(!*line && !ge) {
/* no assign, no variable, fail */
errorf(global, "Variable '%s' import fail, not set", name);
return PARAM_EXPAND_ERROR;
}
else if(ge) {
/* there is a value to use */
content = ge;
clen = strlen(ge);
}
}
if(content)
;
else if(*line == '@') {
/* read from file or stdin */
FILE *file;
bool use_stdin;
line++;
use_stdin = !strcmp(line, "-");
if(use_stdin)
file = stdin;
else {
file = fopen(line, "rb");
}
if(file) {
err = file2memory(&content, &clen, file);
/* in case of out of memory, this should fail the entire operation */
contalloc = TRUE;
}
if(!use_stdin)
fclose(file);
if(err)
return err;
}
else if(*line == '=') {
line++;
/* this is the exact content */
content = (char *)line;
clen = strlen(line);
}
else {
warnf(global, "Bad --variable syntax, skipping: %s", input);
return PARAM_OK;
}
err = addvariable(global, name, nlen, content, clen, contalloc);
if(err) {
if(contalloc)
free(content);
}
curl_free(ge);
return err;
}

48
src/var.h Normal file
View file

@ -0,0 +1,48 @@
#ifndef HEADER_CURL_VAR_H
#define HEADER_CURL_VAR_H
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* SPDX-License-Identifier: curl
*
***************************************************************************/
#include "tool_getparam.h"
#include "dynbuf.h"
struct var {
struct var *next;
const char *name;
const char *content;
size_t clen; /* content length */
};
struct GlobalConfig;
ParameterError setvariable(struct GlobalConfig *global, const char *input);
ParameterError varexpand(struct GlobalConfig *global,
const char *line, struct curlx_dynbuf *out,
bool *replaced);
/* free everything */
void varcleanup(struct GlobalConfig *global);
#endif /* HEADER_CURL_VAR_H */