commit 88b9a3187c6f41b8a752a13c04478bf6b4ba0593
parent d5b86e4e68c1121d0375544a4c389a88075cce30
Author: Santtu Lakkala <santtu.lakkala@unikie.com>
Date: Fri, 21 Feb 2025 13:52:00 +0200
Add fmt helpers
Diffstat:
M | activitypub.c | | | 43 | ++++++++++++++----------------------------- |
M | data.c | | | 71 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | format.c | | | 37 | ++++++++++++------------------------- |
M | html.c | | | 38 | +++++++++++++++++++++++++++----------- |
M | http.c | | | 22 | ++++++++++++++++++++++ |
M | http_codes.h | | | 1 | + |
M | httpd.c | | | 2 | +- |
M | snac.h | | | 5 | ++++- |
M | xs.h | | | 86 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------- |
9 files changed, 221 insertions(+), 84 deletions(-)
diff --git a/activitypub.c b/activitypub.c
@@ -883,19 +883,15 @@ xs_str *process_tags(snac *snac, const char *content, xs_list **tag)
if (valid_status(status) && actor && uid) {
xs *d = xs_dict_new();
- xs *n = xs_fmt("@%s", uid);
d = xs_dict_append(d, "type", "Mention");
d = xs_dict_append(d, "href", actor);
- d = xs_dict_append(d, "name", n);
+ d = xs_dict_set_fmt(d, "name", "@%s", uid);
tl = xs_list_append(tl, d);
- link = xs_fmt("<span class=\"h-card\"><a href=\"%s\" class=\"u-url mention\">%s</a></span>", actor, n);
+ nc = xs_str_cat_fmt(nc, "<span class=\"h-card\"><a href=\"%s\" class=\"u-url mention\">@%s</a></span>", actor, uid);
}
-
- if (!xs_is_null(link))
- nc = xs_str_cat(nc, link);
else
nc = xs_str_cat(nc, v);
}
@@ -904,17 +900,15 @@ xs_str *process_tags(snac *snac, const char *content, xs_list **tag)
/* hashtag */
xs *d = xs_dict_new();
xs *n = xs_utf8_to_lower(v);
- xs *h = xs_fmt("%s?t=%s", srv_baseurl, n + 1);
- xs *l = xs_fmt("<a href=\"%s\" class=\"mention hashtag\" rel=\"tag\">%s</a>", h, v);
d = xs_dict_append(d, "type", "Hashtag");
- d = xs_dict_append(d, "href", h);
+ d = xs_dict_set_fmt(d, "href", "%s?t=%s", srv_baseurl, n + 1);
d = xs_dict_append(d, "name", n);
tl = xs_list_append(tl, d);
/* add the code */
- nc = xs_str_cat(nc, l);
+ nc = xs_str_cat_fmt(nc, "<a href=\"%s?t=%s\" class=\"mention hashtag\" rel=\"tag\">%s</a>", srv_baseurl, n + 1, v);
}
else
if (*v == '&') {
@@ -974,8 +968,8 @@ void notify(snac *snac, const char *type, const char *utype, const char *actor,
return;
/* if it's an announce by our own relay, done */
- xs *relay_id = xs_fmt("%s/relay", srv_baseurl);
- if (xs_startswith(id, relay_id))
+ if (xs_startswith(id, srv_baseurl) &&
+ xs_startswith(id + strlen(srv_baseurl), "/relay"))
return;
}
@@ -1008,22 +1002,16 @@ void notify(snac *snac, const char *type, const char *utype, const char *actor,
);
if (strcmp(utype, "(null)") != 0) {
- xs *s1 = xs_fmt("Type : %s + %s\n", type, utype);
- body = xs_str_cat(body, s1);
+ body = xs_str_cat_fmt(body, "Type : %s + %s\n", type, utype);
}
else {
- xs *s1 = xs_fmt("Type : %s\n", type);
- body = xs_str_cat(body, s1);
+ body = xs_str_cat_fmt(body, "Type : %s\n", type);
}
- {
- xs *s1 = xs_fmt("Actor : %s\n", actor);
- body = xs_str_cat(body, s1);
- }
+ body = xs_str_cat_fmt(body, "Actor: %s\n", actor);
if (objid != NULL) {
- xs *s1 = xs_fmt("Object: %s\n", objid);
- body = xs_str_cat(body, s1);
+ body = xs_str_cat_fmt(body, "Object: %s\n", objid);
}
/* email */
@@ -1343,20 +1331,17 @@ xs_dict *msg_actor(snac *snac)
char *folders[] = { "inbox", "outbox", "followers", "following", "featured", NULL };
for (n = 0; folders[n]; n++) {
- xs *f = xs_fmt("%s/%s", snac->actor, folders[n]);
- msg = xs_dict_set(msg, folders[n], f);
+ msg = xs_dict_set_fmt(msg, folders[n], "%s/%s", snac->actor, folders[n]);
}
p = xs_dict_get(snac->config, "avatar");
if (*p == '\0')
- avtr = xs_fmt("%s/susie.png", srv_baseurl);
- else
- avtr = xs_dup(p);
+ p = avtr = xs_fmt("%s/susie.png", srv_baseurl);
icon = xs_dict_append(icon, "type", "Image");
- icon = xs_dict_append(icon, "mediaType", xs_mime_by_ext(avtr));
- icon = xs_dict_append(icon, "url", avtr);
+ icon = xs_dict_append(icon, "mediaType", xs_mime_by_ext(p));
+ icon = xs_dict_append(icon, "url", p);
msg = xs_dict_set(msg, "icon", icon);
kid = xs_fmt("%s#main-key", snac->actor);
diff --git a/data.c b/data.c
@@ -2542,6 +2542,68 @@ static int _load_raw_file(const char *fn, xs_val **data, int *size,
return status;
}
+static int _load_raw_file_partial(const char *fn, xs_val **data, int *size,
+ const char *inm, xs_str **etag, int start, int *end)
+/* loads a cached file */
+{
+ int status = HTTP_STATUS_NOT_FOUND;
+
+ if (fn) {
+ double tm = mtime(fn);
+
+ if (tm > 0.0) {
+ /* file exists; build the etag */
+ xs *e = xs_fmt("W/\"snac-%.0lf\"", tm);
+
+ /* if if-none-match is set, check if it's the same */
+ if (!xs_is_null(inm) && strcmp(e, inm) == 0) {
+ /* client has the newest version */
+ status = HTTP_STATUS_NOT_MODIFIED;
+ }
+ else {
+ /* newer or never downloaded; read the full file */
+ FILE *f;
+
+ if ((f = fopen(fn, "rb")) != NULL) {
+ struct stat st;
+ int n;
+ if (fstat(fileno(f), &st) == 0) {
+ *size = st.st_size;
+ if (*end == XS_ALL)
+ *end = *size - 1;
+ } else {
+ *size = XS_ALL;
+ }
+
+ n = *end == XS_ALL ? XS_ALL : *end - start + 1;
+
+ if (*end != XS_ALL && *end > *size)
+ status = HTTP_STATUS_RANGE_NOT_SATISFIABLE;
+ else
+ if (start > *size || fseek(f, start, SEEK_SET) != 0)
+ status = HTTP_STATUS_RANGE_NOT_SATISFIABLE;
+ else
+ *data = xs_read(f, &n);
+
+ *end = (n + start - 1);
+ fclose(f);
+
+ if (status == HTTP_STATUS_NOT_FOUND)
+ status = HTTP_STATUS_PARTIAL_CONTENT;
+ }
+ }
+
+ /* if caller wants the etag, return it */
+ if (etag != NULL)
+ *etag = xs_dup(e);
+
+ srv_debug(1, xs_fmt("_load_raw_file(): %s %d", fn, status));
+ }
+ }
+
+ return status;
+}
+
xs_str *_static_fn(snac *snac, const char *id)
/* gets the filename for a static file */
@@ -2562,6 +2624,15 @@ int static_get(snac *snac, const char *id, xs_val **data, int *size,
return _load_raw_file(fn, data, size, inm, etag);
}
+int static_get_partial(snac *snac, const char *id, xs_val **data, int *size,
+ const char *inm, xs_str **etag, int start, int *end)
+/* returns static content */
+{
+ xs *fn = _static_fn(snac, id);
+
+ return _load_raw_file_partial(fn, data, size, inm, etag, start, end);
+}
+
void static_put(snac *snac, const char *id, const char *data, int size)
/* writes status content */
diff --git a/format.c b/format.c
@@ -110,41 +110,35 @@ static xs_str *format_line(const char *line, xs_list **attach)
if (xs_startswith(v, "`")) {
xs *s1 = xs_strip_chars_i(xs_dup(v), "`");
xs *e1 = encode_html(s1);
- xs *s2 = xs_fmt("<code>%s</code>", e1);
- s = xs_str_cat(s, s2);
+ s = xs_str_cat_fmt(s, "<code>%s</code>", e1);
}
else
if (xs_startswith(v, "***")) {
xs *s1 = xs_strip_chars_i(xs_dup(v), "*");
- xs *s2 = xs_fmt("<b><i>%s</i></b>", s1);
- s = xs_str_cat(s, s2);
+ s = xs_str_cat_fmt(s, "<b><i>%s</i></b>", s1);
}
else
if (xs_startswith(v, "**")) {
xs *s1 = xs_strip_chars_i(xs_dup(v), "*");
- xs *s2 = xs_fmt("<b>%s</b>", s1);
- s = xs_str_cat(s, s2);
+ s = xs_str_cat_fmt(s, "<b>%s</b>", s1);
}
else
if (xs_startswith(v, "*")) {
xs *s1 = xs_strip_chars_i(xs_dup(v), "*");
- xs *s2 = xs_fmt("<i>%s</i>", s1);
- s = xs_str_cat(s, s2);
+ s = xs_str_cat_fmt(s, "<i>%s</i>", s1);
}
//anzu - begin
else
if (xs_startswith(v, "__")) {
xs *s1 = xs_strip_chars_i(xs_dup(v), "_");
- xs *s2 = xs_fmt("<u>%s</u>", s1);
- s = xs_str_cat(s, s2);
+ s = xs_str_cat_fmt(s, "<u>%s</u>", s1);
}
//anzu - end
else
if (xs_startswith(v, "~~")) {
xs *s1 = xs_strip_chars_i(xs_dup(v), "~");
xs *e1 = encode_html(s1);
- xs *s2 = xs_fmt("<s>%s</s>", e1);
- s = xs_str_cat(s, s2);
+ s = xs_str_cat_fmt(s, "<s>%s</s>", e1);
}
else
if (*v == '[') {
@@ -159,9 +153,8 @@ static xs_str *format_line(const char *line, xs_list **attach)
name = xs_crop_i(name, 1, 0);
url = xs_crop_i(url, 0, -1);
- xs *link = xs_fmt("<a href=\"%s\">%s</a>", url, name);
-
- s = xs_str_cat(s, link);
+ s = xs_str_cat_fmt(s, "<a href=\"%s\">%s</a>",
+ url, name);
}
else
s = xs_str_cat(s, v);
@@ -204,9 +197,7 @@ static xs_str *format_line(const char *line, xs_list **attach)
}
}
else {
- xs *link = xs_fmt("<a href=\"%s\">%s</a>", img_url, alt_text);
-
- s = xs_str_cat(s, link);
+ s = xs_str_cat_fmt(s, "<a href=\"%s\">%s</a>", img_url, alt_text);
}
}
else
@@ -245,8 +236,7 @@ static xs_str *format_line(const char *line, xs_list **attach)
}
}
else {
- xs *s1 = xs_fmt("<a href=\"%s\" target=\"_blank\">%s</a>", v2, u);
- s = xs_str_cat(s, s1);
+ s = xs_str_cat_fmt(s, "<a href=\"%s\" target=\"_blank\">%s</a>", v2, u);
}
}
else
@@ -255,8 +245,7 @@ static xs_str *format_line(const char *line, xs_list **attach)
xs *v2 = xs_strip_chars_i(xs_dup(u), ".,)");
- xs *s1 = xs_fmt("<a href=\"%s\" target=\"_blank\">%s</a>", v2, u);
- s = xs_str_cat(s, s1);
+ s = xs_str_cat_fmt("<a href=\"%s\" target=\"_blank\">%s</a>", v2, u);
}
else
s = xs_str_cat(s, v);
@@ -462,10 +451,8 @@ xs_str *sanitize(const char *content)
xs *el = xs_regex_select(v, "(src|href|rel|class|target)=(\"[^\"]*\"|'[^']*')");
xs *s3 = xs_join(el, " ");
- s2 = xs_fmt("<%s%s%s%s>",
+ s = xs_str_cat_fmt(s, "<%s%s%s%s>",
v[1] == '/' ? "/" : "", tag, xs_list_len(el) ? " " : "", s3);
-
- s = xs_str_cat(s, s2);
} else {
/* treat end of divs as paragraph breaks */
if (strcmp(v, "</div>"))
diff --git a/html.c b/html.c
@@ -1312,22 +1312,18 @@ xs_html *html_top_controls(snac *user)
if (xs_type(md) == XSTYPE_DICT) {
const xs_str *k;
const xs_str *v;
+ const char *s = "";
metadata = xs_str_new(NULL);
xs_dict_foreach(md, k, v) {
- xs *kp = xs_fmt("%s=%s", k, v);
-
- if (*metadata)
- metadata = xs_str_cat(metadata, "\n");
- metadata = xs_str_cat(metadata, kp);
+ metadata = xs_str_cat_fmt(metadata, "%s%s=%s", s, k, v);
+ s = "\n";
}
}
else
if (xs_type(md) == XSTYPE_STRING)
metadata = xs_dup(md);
- else
- metadata = xs_str_new(NULL);
/* ui language */
xs_html *lang_select = xs_html_tag("select",
@@ -1568,7 +1564,7 @@ xs_html *html_top_controls(snac *user)
xs_html_attr("rows", "4"),
xs_html_attr("placeholder", "Blog=https:/"
"/example.com/my-blog\nGPG Key=1FA54\n..."),
- xs_html_text(metadata))),
+ xs_html_text(metadata ? metadata : ""))),
xs_html_tag("p",
xs_html_text(L("Web interface language:")),
@@ -3604,7 +3600,8 @@ void set_user_lang(snac *user)
int html_get_handler(const xs_dict *req, const char *q_path,
char **body, int *b_size, char **ctype,
- xs_str **etag, xs_str **last_modified)
+ xs_str **etag, xs_str **last_modified,
+ xs_dict **headers)
{
const char *accept = xs_dict_get(req, "accept");
int status = HTTP_STATUS_NOT_FOUND;
@@ -4111,10 +4108,29 @@ int html_get_handler(const xs_dict *req, const char *q_path,
int sz;
if (id && *id) {
- status = static_get(&snac, id, body, &sz,
+ int start;
+ int end;
+
+ if (parse_range(req, &start, &end)) {
+ status = static_get_partial(&snac, id, body, &sz,
+ xs_dict_get(req, "if-none-match"), etag,
+ start, &end);
+ if (status == HTTP_STATUS_PARTIAL_CONTENT) {
+ xs *part = NULL;
+ if (sz != XS_ALL)
+ part = xs_fmt("bytes %d-%d/%d", start, end, sz);
+ else
+ part = xs_fmt("bytes %d-%d/*", start, end);
+ *headers = xs_dict_append(*headers, "Content-Range", part);
+ *b_size = end - start + 1;
+ *ctype = (char *)xs_mime_by_ext(id);
+ }
+ } else {
+ status = static_get(&snac, id, body, &sz,
xs_dict_get(req, "if-none-match"), etag);
+ }
- if (valid_status(status)) {
+ if (valid_status(status) && status != HTTP_STATUS_PARTIAL_CONTENT) {
*b_size = sz;
*ctype = (char *)xs_mime_by_ext(id);
}
diff --git a/http.c b/http.c
@@ -260,3 +260,25 @@ int check_signature(const xs_dict *req, xs_str **err)
return 1;
}
+
+int parse_range(const xs_dict *req, int *start, int *end)
+{
+ const char *rng = xs_dict_get(req, "range");
+ if (rng && xs_str_in(rng, ",") == -1 && xs_startswith(rng, "bytes=")) {
+ xs *l = xs_split_n(rng + strlen("bytes="), "-", 2);
+ if (xs_list_len(l) == 2) {
+ const char *ss = xs_list_get(l, 0);
+ const char *es = xs_list_get(l, 1);
+
+ if (ss[strspn(ss, "0123456789")] == '\0' &&
+ es[strspn(es, "0123456789")] == '\0') {
+ *start = atoi(ss);
+ *end = es[0] == '\0' ? XS_ALL : atoi(es);
+
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/http_codes.h b/http_codes.h
@@ -33,6 +33,7 @@ HTTP_STATUS(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required)
HTTP_STATUS(408, REQUEST_TIMEOUT, Request Timeout)
HTTP_STATUS(409, CONFLICT, Conflict)
HTTP_STATUS(410, GONE, Gone)
+HTTP_STATUS(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable)
HTTP_STATUS(421, MISDIRECTED_REQUEST, Misdirected Request)
HTTP_STATUS(422, UNPROCESSABLE_CONTENT, Unprocessable Content)
HTTP_STATUS(499, CLIENT_CLOSED_REQUEST, Client Closed Request)
diff --git a/httpd.c b/httpd.c
@@ -440,7 +440,7 @@ void httpd_connection(FILE *f)
#endif /* NO_MASTODON_API */
if (status == 0)
- status = html_get_handler(req, q_path, &body, &b_size, &ctype, &etag, &last_modified);
+ status = html_get_handler(req, q_path, &body, &b_size, &ctype, &etag, &last_modified, &headers);
}
else
if (strcmp(method, "POST") == 0) {
diff --git a/snac.h b/snac.h
@@ -239,6 +239,7 @@ int actor_get(const char *actor, xs_dict **data);
int actor_get_refresh(snac *user, const char *actor, xs_dict **data);
int static_get(snac *snac, const char *id, xs_val **data, int *size, const char *inm, xs_str **etag);
+int static_get_partial(snac *snac, const char *id, xs_val **data, int *size, const char *inm, xs_str **etag, int start, int *end);
void static_put(snac *snac, const char *id, const char *data, int size);
void static_put_meta(snac *snac, const char *id, const char *str);
xs_str *static_get_meta(snac *snac, const char *id);
@@ -318,6 +319,7 @@ xs_dict *http_signed_request(snac *snac, const char *method, const char *url,
int *status, xs_str **payload, int *p_size,
int timeout);
int check_signature(const xs_dict *req, xs_str **err);
+int parse_range(const xs_dict *req, int *start, int *end);
srv_state *srv_state_op(xs_str **fname, int op);
void httpd(void);
@@ -392,7 +394,8 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
int html_get_handler(const xs_dict *req, const char *q_path,
char **body, int *b_size, char **ctype,
- xs_str **etag, xs_str **last_modified);
+ xs_str **etag, xs_str **last_modified,
+ xs_dict **headers);
int html_post_handler(const xs_dict *req, const char *q_path,
char *payload, int p_size,
diff --git a/xs.h b/xs.h
@@ -75,6 +75,7 @@ xs_str *xs_str_wrap_i(const char *prefix, xs_str *str, const char *suffix);
#define xs_str_prepend_i(str, prefix) xs_str_wrap_i(prefix, str, NULL)
xs_str *_xs_str_cat(xs_str *str, const char *strs[]);
#define xs_str_cat(str, ...) _xs_str_cat(str, (const char *[]){ __VA_ARGS__, NULL })
+xs_str *xs_str_cat_fmt(xs_str *str, const char *fmt, ...);
xs_str *xs_replace_in(xs_str *str, const char *sfrom, const char *sto, int times);
#define xs_replace_i(str, sfrom, sto) xs_replace_in(str, sfrom, sto, XS_ALL)
#define xs_replace(str, sfrom, sto) xs_replace_in(xs_dup(str), sfrom, sto, XS_ALL)
@@ -124,6 +125,7 @@ const xs_val *xs_dict_get(const xs_dict *dict, const xs_str *key);
#define xs_dict_get_def(dict, key, def) xs_or(xs_dict_get(dict, key), def)
xs_dict *xs_dict_del(xs_dict *dict, const xs_str *key);
xs_dict *xs_dict_set(xs_dict *dict, const xs_str *key, const xs_val *data);
+xs_dict *xs_dict_set_fmt(xs_dict *dict, const xs_str *key, const char *fmt, ...);
xs_dict *xs_dict_gc(const xs_dict *dict);
const xs_val *xs_dict_get_path_sep(const xs_dict *dict, const char *path, const char *sep);
@@ -517,6 +519,26 @@ xs_str *xs_str_wrap_i(const char *prefix, xs_str *str, const char *suffix)
return str;
}
+xs_str *xs_str_cat_fmt(xs_str *str, const char *fmt, ...)
+{
+ int o = strlen(str);
+ int n;
+ xs_str *s = NULL;
+ va_list ap;
+
+ va_start(ap, fmt);
+ n = vsnprintf(NULL, 0, fmt, ap);
+ va_end(ap);
+
+ if (n++ > 0) {
+ str = xs_realloc(str, o + n);
+ va_start(ap, fmt);
+ vsnprintf(str + o, n, fmt, ap);
+ va_end(ap);
+ }
+
+ return str;
+}
xs_str *_xs_str_cat(xs_str *str, const char *strs[])
/* concatenates all strings after str */
@@ -1119,13 +1141,8 @@ static int *_xs_dict_locate(const xs_dict *dict, const char *key)
return off;
}
-
-xs_dict *xs_dict_set(xs_dict *dict, const xs_str *key, const xs_val *value)
-/* sets a key/value pair */
+xs_dict *_xs_dict_ensure(xs_dict *dict, const xs_str *key, int vsz, int *offset)
{
- if (value == NULL)
- value = xs_stock(XSTYPE_NULL);
-
if (xs_type(dict) == XSTYPE_DICT) {
int *o = _xs_dict_locate(dict, key);
int end = xs_size(dict);
@@ -1135,7 +1152,6 @@ xs_dict *xs_dict_set(xs_dict *dict, const xs_str *key, const xs_val *value)
*o = end;
int ksz = xs_size(key);
- int vsz = xs_size(value);
int dsz = sizeof(ditem_hdr) + ksz + vsz;
/* open room in the dict for the full ditem */
@@ -1153,9 +1169,6 @@ xs_dict *xs_dict_set(xs_dict *dict, const xs_str *key, const xs_val *value)
/* copy the key */
memcpy(di->key, key, ksz);
- /* copy the value */
- memcpy(dict + di->value_offset, value, vsz);
-
/* chain to the sequential list */
if (dh->first == 0)
dh->first = end;
@@ -1166,6 +1179,8 @@ xs_dict *xs_dict_set(xs_dict *dict, const xs_str *key, const xs_val *value)
}
dh->last = end;
+
+ *offset = di->value_offset;
}
else {
/* ditem already exists */
@@ -1182,20 +1197,57 @@ xs_dict *xs_dict_set(xs_dict *dict, const xs_str *key, const xs_val *value)
xs_val *o_value = dict + *i;
/* will new value fit over the old one? */
- if (xs_size(value) <= xs_size(o_value)) {
- /* just overwrite */
- /* (difference is leaked inside the dict) */
- memcpy(o_value, value, xs_size(value));
- }
- else {
+ if (vsz > xs_size(o_value)) {
/* not enough room: new value will live at the end of the dict */
/* (old value is leaked inside the dict) */
*i = end;
- dict = xs_insert(dict, end, value);
+ dict = xs_expand(dict, end, vsz);
}
+ *offset = *i;
}
}
+ else {
+ *offset = -1;
+ }
+
+ return dict;
+}
+
+xs_dict *xs_dict_set_fmt(xs_dict *dict, const xs_str *key, const char *fmt, ...)
+{
+ if (xs_type(dict) == XSTYPE_DICT) {
+ int o;
+ int vsz;
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsz = vsnprintf(NULL, 0, fmt, ap);
+ va_end(ap);
+
+ if (vsz++ < 0)
+ return dict;
+
+ dict = _xs_dict_ensure(dict, key, vsz, &o);
+
+ va_start(ap, fmt);
+ vsnprintf(dict + o, vsz, fmt, ap);
+ va_end(ap);
+ }
+}
+
+xs_dict *xs_dict_set(xs_dict *dict, const xs_str *key, const xs_val *value)
+/* sets a key/value pair */
+{
+ if (value == NULL)
+ value = xs_stock(XSTYPE_NULL);
+
+ if (xs_type(dict) == XSTYPE_DICT) {
+ int vsz = xs_size(value);
+ int o;
+ dict = _xs_dict_ensure(dict, key, vsz, &o);
+ memcpy(dict + o, value, vsz);
+ }
return dict;
}