diff options
| -rw-r--r-- | html.c | 106 | ||||
| -rw-r--r-- | httpd.c | 2 |
2 files changed, 40 insertions, 68 deletions
| @@ -1,4 +1,4 @@ | |||
| 1 | /* snac - A simple, minimalistic ActivityPub instance */ | 1 | /* snac - A simple, minimalistic ActivityPub instance */ |
| 2 | /* copyright (c) 2022 - 2023 grunfink / MIT license */ | 2 | /* copyright (c) 2022 - 2023 grunfink / MIT license */ |
| 3 | 3 | ||
| 4 | #include "xs.h" | 4 | #include "xs.h" |
| @@ -325,8 +325,8 @@ d_char *html_user_header(snac *snac, d_char *s, int local) | |||
| 325 | s1 = xs_fmt( | 325 | s1 = xs_fmt( |
| 326 | "<a href=\"%s.rss\">%s</a> - " | 326 | "<a href=\"%s.rss\">%s</a> - " |
| 327 | "<a href=\"%s/admin\" rel=\"nofollow\">%s</a></nav>\n", | 327 | "<a href=\"%s/admin\" rel=\"nofollow\">%s</a></nav>\n", |
| 328 | snac->actor, L("RSS"), | 328 | snac->uid, L("RSS"), |
| 329 | snac->actor, L("private")); | 329 | snac->uid, L("private")); |
| 330 | else { | 330 | else { |
| 331 | xs *n_list = notify_list(snac, 1); | 331 | xs *n_list = notify_list(snac, 1); |
| 332 | int n_len = xs_list_len(n_list); | 332 | int n_len = xs_list_len(n_list); |
| @@ -340,18 +340,14 @@ d_char *html_user_header(snac *snac, d_char *s, int local) | |||
| 340 | n_str = xs_str_new(""); | 340 | n_str = xs_str_new(""); |
| 341 | 341 | ||
| 342 | s1 = xs_fmt( | 342 | s1 = xs_fmt( |
| 343 | "<a href=\"%s\">%s</a> - " | 343 | "<a href=\"../%s\">%s</a> - " |
| 344 | "<a href=\"%s/admin\">%s</a> - " | 344 | "<a href=\"admin\">%s</a> - " |
| 345 | "<a href=\"%s/notifications\">%s</a>%s - " | 345 | "<a href=\"notifications\">%s</a>%s - " |
| 346 | "<a href=\"%s/people\">%s</a> - " | 346 | "<a href=\"people\">%s</a></nav>\n", |
| 347 | "<a href=\"%s/settings\">%s</a>" | 347 | snac->uid, L("public"), |
| 348 | "</nav>\n", | 348 | L("private"), |
| 349 | snac->actor, L("public"), | 349 | L("notifications"), n_str, |
| 350 | snac->actor, L("private"), | 350 | L("people")); |
| 351 | snac->actor, L("notifications"), n_str, | ||
| 352 | snac->actor, L("people"), | ||
| 353 | snac->actor, L("settings") | ||
| 354 | ); | ||
| 355 | } | 351 | } |
| 356 | 352 | ||
| 357 | s = xs_str_cat(s, s1); | 353 | s = xs_str_cat(s, s1); |
| @@ -389,16 +385,16 @@ d_char *html_user_header(snac *snac, d_char *s, int local) | |||
| 389 | } | 385 | } |
| 390 | 386 | ||
| 391 | 387 | ||
| 392 | xs_str *html_top_controls(snac *snac, xs_str *s, int settings) | 388 | d_char *html_top_controls(snac *snac, d_char *s) |
| 393 | /* generates the top controls */ | 389 | /* generates the top controls */ |
| 394 | { | 390 | { |
| 395 | char *_tmpl = | 391 | char *_tmpl = |
| 396 | "<div class=\"snac-top-controls\">\n" | 392 | "<div class=\"snac-top-controls\">\n" |
| 397 | 393 | ||
| 398 | "<div class=\"snac-note\" %s>\n" | 394 | "<div class=\"snac-note\">\n" |
| 399 | "<details><summary>%s</summary>\n" | 395 | "<details><summary>%s</summary>\n" |
| 400 | "<form autocomplete=\"off\" method=\"post\" " | 396 | "<form autocomplete=\"off\" method=\"post\" " |
| 401 | "action=\"%s/admin/note\" enctype=\"multipart/form-data\">\n" | 397 | "action=\"admin/note\" enctype=\"multipart/form-data\">\n" |
| 402 | "<textarea class=\"snac-textarea\" name=\"content\" " | 398 | "<textarea class=\"snac-textarea\" name=\"content\" " |
| 403 | "rows=\"8\" wrap=\"virtual\" required=\"required\" placeholder=\"What's on your mind?\"></textarea>\n" | 399 | "rows=\"8\" wrap=\"virtual\" required=\"required\" placeholder=\"What's on your mind?\"></textarea>\n" |
| 404 | "<input type=\"hidden\" name=\"in_reply_to\" value=\"\">\n" | 400 | "<input type=\"hidden\" name=\"in_reply_to\" value=\"\">\n" |
| @@ -432,28 +428,25 @@ xs_str *html_top_controls(snac *snac, xs_str *s, int settings) | |||
| 432 | "</div>\n" | 428 | "</div>\n" |
| 433 | "</details>\n" | 429 | "</details>\n" |
| 434 | 430 | ||
| 435 | "<div class=\"snac-top-controls-more\" %s>\n" | 431 | "<div class=\"snac-top-controls-more\">\n" |
| 436 | "<p><details><summary>%s</summary>\n" | 432 | "<details><summary>%s</summary>\n" |
| 437 | 433 | ||
| 438 | "<form autocomplete=\"off\" method=\"post\" action=\"%s/admin/action\">\n" /** follow **/ | 434 | "<form autocomplete=\"off\" method=\"post\" action=\"admin/action\">\n" /** follow **/ |
| 439 | "<input type=\"text\" name=\"actor\" required=\"required\" placeholder=\"bob@example.com\">\n" | 435 | "<input type=\"text\" name=\"actor\" required=\"required\" placeholder=\"bob@example.com\">\n" |
| 440 | "<input type=\"submit\" name=\"action\" value=\"%s\"> %s\n" | 436 | "<input type=\"submit\" name=\"action\" value=\"%s\"> %s\n" |
| 441 | "</form><p>\n" | 437 | "</form><p>\n" |
| 442 | 438 | ||
| 443 | "<form autocomplete=\"off\" method=\"post\" action=\"%s/admin/action\">\n" /** boost **/ | 439 | "<form autocomplete=\"off\" method=\"post\" action=\"admin/action\">\n" /** boost **/ |
| 444 | "<input type=\"text\" name=\"id\" required=\"required\" placeholder=\"https://fedi.example.com/bob/....\">\n" | 440 | "<input type=\"text\" name=\"id\" required=\"required\" placeholder=\"https://fedi.example.com/bob/....\">\n" |
| 445 | "<input type=\"submit\" name=\"action\" value=\"%s\"> %s\n" | 441 | "<input type=\"submit\" name=\"action\" value=\"%s\"> %s\n" |
| 446 | "</form><p>\n" | 442 | "</form><p>\n" |
| 447 | "</details>\n" | 443 | "</details>\n" |
| 448 | "</div>\n" | ||
| 449 | 444 | ||
| 450 | "<div class=\"snac-user-settings\" %s>\n" | 445 | "<details><summary>%s</summary>\n" |
| 451 | |||
| 452 | "<h2 class=\"snac-header\">%s</h2>\n" | ||
| 453 | 446 | ||
| 454 | "<div class=\"snac-user-setup\">\n" /** user setup **/ | 447 | "<div class=\"snac-user-setup\">\n" /** user setup **/ |
| 455 | "<form autocomplete=\"off\" method=\"post\" " | 448 | "<form autocomplete=\"off\" method=\"post\" " |
| 456 | "action=\"%s/admin/user-setup\" enctype=\"multipart/form-data\">\n" | 449 | "action=\"admin/user-setup\" enctype=\"multipart/form-data\">\n" |
| 457 | "<p>%s:<br>\n" | 450 | "<p>%s:<br>\n" |
| 458 | "<input type=\"text\" name=\"name\" value=\"%s\" placeholder=\"Your name.\"></p>\n" | 451 | "<input type=\"text\" name=\"name\" value=\"%s\" placeholder=\"Your name.\"></p>\n" |
| 459 | 452 | ||
| @@ -491,6 +484,7 @@ xs_str *html_top_controls(snac *snac, xs_str *s, int settings) | |||
| 491 | "</form>\n" | 484 | "</form>\n" |
| 492 | 485 | ||
| 493 | "</div>\n" | 486 | "</div>\n" |
| 487 | "</details>\n" | ||
| 494 | "</div>\n" | 488 | "</div>\n" |
| 495 | "</div>\n"; | 489 | "</div>\n"; |
| 496 | 490 | ||
| @@ -536,9 +530,7 @@ xs_str *html_top_controls(snac *snac, xs_str *s, int settings) | |||
| 536 | xs *es6 = encode_html(purge_days); | 530 | xs *es6 = encode_html(purge_days); |
| 537 | 531 | ||
| 538 | xs *s1 = xs_fmt(_tmpl, | 532 | xs *s1 = xs_fmt(_tmpl, |
| 539 | settings ? "style=\"display: none\"" : "", | ||
| 540 | L("New Post..."), | 533 | L("New Post..."), |
| 541 | snac->actor, | ||
| 542 | L("Sensitive content"), | 534 | L("Sensitive content"), |
| 543 | L("Sensitive content description"), | 535 | L("Sensitive content description"), |
| 544 | L("Only for mentioned people"), | 536 | L("Only for mentioned people"), |
| @@ -557,19 +549,11 @@ xs_str *html_top_controls(snac *snac, xs_str *s, int settings) | |||
| 557 | 549 | ||
| 558 | L("Post"), | 550 | L("Post"), |
| 559 | 551 | ||
| 560 | settings ? "style=\"display: none\"" : "", | ||
| 561 | L("Operations..."), | 552 | L("Operations..."), |
| 562 | |||
| 563 | snac->actor, | ||
| 564 | L("Follow"), L("(by URL or user@host)"), | 553 | L("Follow"), L("(by URL or user@host)"), |
| 565 | |||
| 566 | snac->actor, | ||
| 567 | L("Boost"), L("(by URL)"), | 554 | L("Boost"), L("(by URL)"), |
| 568 | 555 | ||
| 569 | !settings ? "style=\"display: none\"" : "", | 556 | L("User Settings..."), |
| 570 | |||
| 571 | L("User Settings"), | ||
| 572 | snac->actor, | ||
| 573 | L("Display name"), | 557 | L("Display name"), |
| 574 | es1, | 558 | es1, |
| 575 | L("Avatar"), | 559 | L("Avatar"), |
| @@ -673,13 +657,13 @@ xs_str *html_entry_controls(snac *snac, xs_str *os, const xs_dict *msg, const ch | |||
| 673 | 657 | ||
| 674 | { | 658 | { |
| 675 | xs *s1 = xs_fmt( | 659 | xs *s1 = xs_fmt( |
| 676 | "<form autocomplete=\"off\" method=\"post\" action=\"%s/admin/action\">\n" | 660 | "<form autocomplete=\"off\" method=\"post\" action=\"admin/action\">\n" |
| 677 | "<input type=\"hidden\" name=\"id\" value=\"%s\">\n" | 661 | "<input type=\"hidden\" name=\"id\" value=\"%s\">\n" |
| 678 | "<input type=\"hidden\" name=\"actor\" value=\"%s\">\n" | 662 | "<input type=\"hidden\" name=\"actor\" value=\"%s\">\n" |
| 679 | "<input type=\"hidden\" name=\"redir\" value=\"%s_entry\">\n" | 663 | "<input type=\"hidden\" name=\"redir\" value=\"%s_entry\">\n" |
| 680 | "\n", | 664 | "\n", |
| 681 | 665 | ||
| 682 | snac->actor, id, actor, md5 | 666 | id, actor, md5 |
| 683 | ); | 667 | ); |
| 684 | 668 | ||
| 685 | s = xs_str_cat(s, s1); | 669 | s = xs_str_cat(s, s1); |
| @@ -733,7 +717,7 @@ xs_str *html_entry_controls(snac *snac, xs_str *os, const xs_dict *msg, const ch | |||
| 733 | xs *s1 = xs_fmt( | 717 | xs *s1 = xs_fmt( |
| 734 | "<p><details><summary>%s</summary>\n" | 718 | "<p><details><summary>%s</summary>\n" |
| 735 | "<p><div class=\"snac-note\" id=\"%s_edit\">\n" | 719 | "<p><div class=\"snac-note\" id=\"%s_edit\">\n" |
| 736 | "<form autocomplete=\"off\" method=\"post\" action=\"%s/admin/note\" " | 720 | "<form autocomplete=\"off\" method=\"post\" action=\"admin/note\" " |
| 737 | "enctype=\"multipart/form-data\" id=\"%s_edit_form\">\n" | 721 | "enctype=\"multipart/form-data\" id=\"%s_edit_form\">\n" |
| 738 | "<textarea class=\"snac-textarea\" name=\"content\" " | 722 | "<textarea class=\"snac-textarea\" name=\"content\" " |
| 739 | "rows=\"4\" wrap=\"virtual\" required=\"required\">%s</textarea>\n" | 723 | "rows=\"4\" wrap=\"virtual\" required=\"required\">%s</textarea>\n" |
| @@ -756,7 +740,7 @@ xs_str *html_entry_controls(snac *snac, xs_str *os, const xs_dict *msg, const ch | |||
| 756 | 740 | ||
| 757 | L("Edit..."), | 741 | L("Edit..."), |
| 758 | md5, | 742 | md5, |
| 759 | snac->actor, md5, | 743 | md5, |
| 760 | prev_src, | 744 | prev_src, |
| 761 | id, | 745 | id, |
| 762 | L("Sensitive content"), | 746 | L("Sensitive content"), |
| @@ -784,7 +768,7 @@ xs_str *html_entry_controls(snac *snac, xs_str *os, const xs_dict *msg, const ch | |||
| 784 | xs *s1 = xs_fmt( | 768 | xs *s1 = xs_fmt( |
| 785 | "<p><details><summary>%s</summary>\n" | 769 | "<p><details><summary>%s</summary>\n" |
| 786 | "<p><div class=\"snac-note\" id=\"%s_reply\">\n" | 770 | "<p><div class=\"snac-note\" id=\"%s_reply\">\n" |
| 787 | "<form autocomplete=\"off\" method=\"post\" action=\"%s/admin/note\" " | 771 | "<form autocomplete=\"off\" method=\"post\" action=\"admin/note\" " |
| 788 | "enctype=\"multipart/form-data\" id=\"%s_reply_form\">\n" | 772 | "enctype=\"multipart/form-data\" id=\"%s_reply_form\">\n" |
| 789 | "<textarea class=\"snac-textarea\" name=\"content\" " | 773 | "<textarea class=\"snac-textarea\" name=\"content\" " |
| 790 | "rows=\"4\" wrap=\"virtual\" required=\"required\">%s</textarea>\n" | 774 | "rows=\"4\" wrap=\"virtual\" required=\"required\">%s</textarea>\n" |
| @@ -807,7 +791,7 @@ xs_str *html_entry_controls(snac *snac, xs_str *os, const xs_dict *msg, const ch | |||
| 807 | 791 | ||
| 808 | L("Reply..."), | 792 | L("Reply..."), |
| 809 | md5, | 793 | md5, |
| 810 | snac->actor, md5, | 794 | md5, |
| 811 | ct, | 795 | ct, |
| 812 | id, | 796 | id, |
| 813 | L("Sensitive content"), | 797 | L("Sensitive content"), |
| @@ -1102,10 +1086,10 @@ xs_str *html_entry(snac *snac, xs_str *os, const xs_dict *msg, int local, | |||
| 1102 | /* poll still active */ | 1086 | /* poll still active */ |
| 1103 | xs *s1 = xs_fmt("<div class=\"snac-poll-form\">\n" | 1087 | xs *s1 = xs_fmt("<div class=\"snac-poll-form\">\n" |
| 1104 | "<form autocomplete=\"off\" " | 1088 | "<form autocomplete=\"off\" " |
| 1105 | "method=\"post\" action=\"%s/admin/vote\">\n" | 1089 | "method=\"post\" action=\"admin/vote\">\n" |
| 1106 | "<input type=\"hidden\" name=\"actor\" value= \"%s\">\n" | 1090 | "<input type=\"hidden\" name=\"actor\" value= \"%s\">\n" |
| 1107 | "<input type=\"hidden\" name=\"irt\" value=\"%s\">\n", | 1091 | "<input type=\"hidden\" name=\"irt\" value=\"%s\">\n", |
| 1108 | snac->actor, actor, id); | 1092 | actor, id); |
| 1109 | 1093 | ||
| 1110 | while (xs_list_iter(&p, &v)) { | 1094 | while (xs_list_iter(&p, &v)) { |
| 1111 | const char *name = xs_dict_get(v, "name"); | 1095 | const char *name = xs_dict_get(v, "name"); |
| @@ -1386,7 +1370,7 @@ xs_str *html_timeline(snac *snac, const xs_list *list, int local, int skip, int | |||
| 1386 | s = html_user_header(snac, s, local); | 1370 | s = html_user_header(snac, s, local); |
| 1387 | 1371 | ||
| 1388 | if (!local) | 1372 | if (!local) |
| 1389 | s = html_top_controls(snac, s, list == NULL ? 1 : 0); | 1373 | s = html_top_controls(snac, s); |
| 1390 | 1374 | ||
| 1391 | s = xs_str_cat(s, "<a name=\"snac-posts\"></a>\n"); | 1375 | s = xs_str_cat(s, "<a name=\"snac-posts\"></a>\n"); |
| 1392 | s = xs_str_cat(s, "<div class=\"snac-posts\">\n"); | 1376 | s = xs_str_cat(s, "<div class=\"snac-posts\">\n"); |
| @@ -1495,11 +1479,11 @@ d_char *html_people_list(snac *snac, d_char *os, d_char *list, const char *heade | |||
| 1495 | s = xs_str_cat(s, "<div class=\"snac-controls\">\n"); | 1479 | s = xs_str_cat(s, "<div class=\"snac-controls\">\n"); |
| 1496 | 1480 | ||
| 1497 | xs *s1 = xs_fmt( | 1481 | xs *s1 = xs_fmt( |
| 1498 | "<p><form autocomplete=\"off\" method=\"post\" action=\"%s/admin/action\">\n" | 1482 | "<p><form autocomplete=\"off\" method=\"post\" action=\"admin/action\">\n" |
| 1499 | "<input type=\"hidden\" name=\"actor\" value=\"%s\">\n" | 1483 | "<input type=\"hidden\" name=\"actor\" value=\"%s\">\n" |
| 1500 | "<input type=\"hidden\" name=\"actor-form\" value=\"yes\">\n", | 1484 | "<input type=\"hidden\" name=\"actor-form\" value=\"yes\">\n", |
| 1501 | 1485 | ||
| 1502 | snac->actor, actor_id | 1486 | actor_id |
| 1503 | ); | 1487 | ); |
| 1504 | s = xs_str_cat(s, s1); | 1488 | s = xs_str_cat(s, s1); |
| 1505 | 1489 | ||
| @@ -1523,7 +1507,7 @@ d_char *html_people_list(snac *snac, d_char *os, d_char *list, const char *heade | |||
| 1523 | xs *s2 = xs_fmt( | 1507 | xs *s2 = xs_fmt( |
| 1524 | "<p><details><summary>%s</summary>\n" | 1508 | "<p><details><summary>%s</summary>\n" |
| 1525 | "<p><div class=\"snac-note\" id=\"%s_%s_dm\">\n" | 1509 | "<p><div class=\"snac-note\" id=\"%s_%s_dm\">\n" |
| 1526 | "<form autocomplete=\"off\" method=\"post\" action=\"%s/admin/note\" " | 1510 | "<form autocomplete=\"off\" method=\"post\" action=\"admin/note\" " |
| 1527 | "enctype=\"multipart/form-data\" id=\"%s_reply_form\">\n" | 1511 | "enctype=\"multipart/form-data\" id=\"%s_reply_form\">\n" |
| 1528 | "<textarea class=\"snac-textarea\" name=\"content\" " | 1512 | "<textarea class=\"snac-textarea\" name=\"content\" " |
| 1529 | "rows=\"4\" wrap=\"virtual\" required=\"required\"></textarea>\n" | 1513 | "rows=\"4\" wrap=\"virtual\" required=\"required\"></textarea>\n" |
| @@ -1535,7 +1519,7 @@ d_char *html_people_list(snac *snac, d_char *os, d_char *list, const char *heade | |||
| 1535 | 1519 | ||
| 1536 | L("Direct Message..."), | 1520 | L("Direct Message..."), |
| 1537 | md5, t, | 1521 | md5, t, |
| 1538 | snac->actor, md5, | 1522 | md5, |
| 1539 | actor_id, | 1523 | actor_id, |
| 1540 | L("Post") | 1524 | L("Post") |
| 1541 | ); | 1525 | ); |
| @@ -1586,9 +1570,9 @@ xs_str *html_notifications(snac *snac) | |||
| 1586 | 1570 | ||
| 1587 | xs *s1 = xs_fmt( | 1571 | xs *s1 = xs_fmt( |
| 1588 | "<form autocomplete=\"off\" " | 1572 | "<form autocomplete=\"off\" " |
| 1589 | "method=\"post\" action=\"%s/admin/clear-notifications\" id=\"clear\">\n" | 1573 | "method=\"post\" action=\"admin/clear-notifications\" id=\"clear\">\n" |
| 1590 | "<input type=\"submit\" class=\"snac-btn-like\" value=\"%s\">\n" | 1574 | "<input type=\"submit\" class=\"snac-btn-like\" value=\"%s\">\n" |
| 1591 | "</form><p>\n", snac->actor, L("Clear all")); | 1575 | "</form><p>\n", L("Clear all")); |
| 1592 | s = xs_str_cat(s, s1); | 1576 | s = xs_str_cat(s, s1); |
| 1593 | 1577 | ||
| 1594 | while (xs_list_iter(&p, &v)) { | 1578 | while (xs_list_iter(&p, &v)) { |
| @@ -1823,18 +1807,6 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 1823 | } | 1807 | } |
| 1824 | } | 1808 | } |
| 1825 | else | 1809 | else |
| 1826 | if (strcmp(p_path, "settings") == 0) { /** user settings **/ | ||
| 1827 | if (!login(&snac, req)) { | ||
| 1828 | *body = xs_dup(uid); | ||
| 1829 | status = 401; | ||
| 1830 | } | ||
| 1831 | else { | ||
| 1832 | *body = html_timeline(&snac, NULL, 0, 0, 0, 0); | ||
| 1833 | *b_size = strlen(*body); | ||
| 1834 | status = 200; | ||
| 1835 | } | ||
| 1836 | } | ||
| 1837 | else | ||
| 1838 | if (strcmp(p_path, "notifications") == 0) { /** the list of notifications **/ | 1810 | if (strcmp(p_path, "notifications") == 0) { /** the list of notifications **/ |
| 1839 | if (!login(&snac, req)) { | 1811 | if (!login(&snac, req)) { |
| 1840 | *body = xs_dup(uid); | 1812 | *body = xs_dup(uid); |
| @@ -2400,9 +2372,9 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 2400 | char *redir = xs_dict_get(p_vars, "redir"); | 2372 | char *redir = xs_dict_get(p_vars, "redir"); |
| 2401 | 2373 | ||
| 2402 | if (xs_is_null(redir)) | 2374 | if (xs_is_null(redir)) |
| 2403 | redir = "snac-posts"; | 2375 | redir = ""; |
| 2404 | 2376 | ||
| 2405 | *body = xs_fmt("%s/admin#%s", snac.actor, redir); | 2377 | *body = xs_fmt("../admin#%s", redir); |
| 2406 | *b_size = strlen(*body); | 2378 | *b_size = strlen(*body); |
| 2407 | } | 2379 | } |
| 2408 | 2380 | ||
| @@ -91,7 +91,7 @@ int server_get_handler(xs_dict *req, char *q_path, | |||
| 91 | if (user_open(&snac, uid)) { | 91 | if (user_open(&snac, uid)) { |
| 92 | xs *u = xs_fmt( | 92 | xs *u = xs_fmt( |
| 93 | "<li><a href=\"%s\">@%s@%s (%s)</a></li>\n", | 93 | "<li><a href=\"%s\">@%s@%s (%s)</a></li>\n", |
| 94 | snac.actor, uid, host, | 94 | uid, uid, host, |
| 95 | xs_dict_get(snac.config, "name")); | 95 | xs_dict_get(snac.config, "name")); |
| 96 | 96 | ||
| 97 | ul = xs_str_cat(ul, u); | 97 | ul = xs_str_cat(ul, u); |