root/lib/common/output_html.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. html_free_priv
  2. html_init
  3. add_error_node
  4. html_finish
  5. html_reset
  6. html_subprocess_output
  7. html_version
  8. G_GNUC_PRINTF
  9. G_GNUC_PRINTF
  10. html_output_xml
  11. G_GNUC_PRINTF
  12. G_GNUC_PRINTF
  13. html_increment_list
  14. html_end_list
  15. html_is_quiet
  16. html_spacer
  17. html_progress
  18. pcmk__mk_html_output
  19. pcmk__output_create_html_node
  20. pcmk__html_add_header

   1 /*
   2  * Copyright 2019-2022 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 
  12 #include <ctype.h>
  13 #include <libxml/HTMLtree.h>
  14 #include <stdarg.h>
  15 #include <stdlib.h>
  16 #include <stdio.h>
  17 
  18 #include <crm/common/cmdline_internal.h>
  19 #include <crm/common/xml.h>
  20 
  21 static const char *stylesheet_default =
  22     ".bold { font-weight: bold }\n"
  23 
  24     ".online { color: green }\n"
  25     ".offline { color: red }\n"
  26     ".maint { color: blue }\n"
  27     ".standby { color: blue }\n"
  28     ".health_red { color: red }\n"
  29     ".health_yellow { color: GoldenRod }\n"
  30 
  31     ".rsc-failed { color: red }\n"
  32     ".rsc-failure-ignored { color: DarkGreen }\n"
  33     ".rsc-managed { color: blue }\n"
  34     ".rsc-multiple { color: orange }\n"
  35     ".rsc-ok { color: green }\n"
  36 
  37     ".warning { color: red; font-weight: bold }";
  38 
  39 static gboolean cgi_output = FALSE;
  40 static char *stylesheet_link = NULL;
  41 static char *title = NULL;
  42 static GSList *extra_headers = NULL;
  43 
  44 GOptionEntry pcmk__html_output_entries[] = {
  45     { "html-cgi", 0, 0, G_OPTION_ARG_NONE, &cgi_output,
  46       "Add CGI headers (requires --output-as=html)",
  47       NULL },
  48 
  49     { "html-stylesheet", 0, 0, G_OPTION_ARG_STRING, &stylesheet_link,
  50       "Link to an external stylesheet (requires --output-as=html)",
  51       "URI" },
  52 
  53     { "html-title", 0, 0, G_OPTION_ARG_STRING, &title,
  54       "Specify a page title (requires --output-as=html)",
  55       "TITLE" },
  56 
  57     { NULL }
  58 };
  59 
  60 /* The first several elements of this struct must be the same as the first
  61  * several elements of private_data_s in lib/common/output_xml.c.  This
  62  * struct gets passed to a bunch of the pcmk__output_xml_* functions which
  63  * assume an XML private_data_s.  Keeping them laid out the same means this
  64  * still works.
  65  */
  66 typedef struct private_data_s {
  67     /* Begin members that must match the XML version */
  68     xmlNode *root;
  69     GQueue *parent_q;
  70     GSList *errors;
  71     /* End members that must match the XML version */
  72 } private_data_t;
  73 
  74 static void
  75 html_free_priv(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
  76     private_data_t *priv = NULL;
  77 
  78     if (out == NULL || out->priv == NULL) {
  79         return;
  80     }
  81 
  82     priv = out->priv;
  83 
  84     xmlFreeNode(priv->root);
  85     g_queue_free(priv->parent_q);
  86     g_slist_free(priv->errors);
  87     free(priv);
  88     out->priv = NULL;
  89 }
  90 
  91 static bool
  92 html_init(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
  93     private_data_t *priv = NULL;
  94 
  95     CRM_ASSERT(out != NULL);
  96 
  97     /* If html_init was previously called on this output struct, just return. */
  98     if (out->priv != NULL) {
  99         return true;
 100     } else {
 101         out->priv = calloc(1, sizeof(private_data_t));
 102         if (out->priv == NULL) {
 103             return false;
 104         }
 105 
 106         priv = out->priv;
 107     }
 108 
 109     priv->parent_q = g_queue_new();
 110 
 111     priv->root = create_xml_node(NULL, "html");
 112     xmlCreateIntSubset(priv->root->doc, (pcmkXmlStr) "html", NULL, NULL);
 113 
 114     crm_xml_add(priv->root, "lang", "en");
 115     g_queue_push_tail(priv->parent_q, priv->root);
 116     priv->errors = NULL;
 117 
 118     pcmk__output_xml_create_parent(out, "body", NULL);
 119 
 120     return true;
 121 }
 122 
 123 static void
 124 add_error_node(gpointer data, gpointer user_data) {
     /* [previous][next][first][last][top][bottom][index][help] */
 125     char *str = (char *) data;
 126     pcmk__output_t *out = (pcmk__output_t *) user_data;
 127     out->list_item(out, NULL, "%s", str);
 128 }
 129 
 130 static void
 131 html_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
     /* [previous][next][first][last][top][bottom][index][help] */
 132     private_data_t *priv = NULL;
 133     htmlNodePtr head_node = NULL;
 134     htmlNodePtr charset_node = NULL;
 135 
 136     CRM_ASSERT(out != NULL);
 137 
 138     priv = out->priv;
 139 
 140     /* If root is NULL, html_init failed and we are being called from pcmk__output_free
 141      * in the pcmk__output_new path.
 142      */
 143     if (priv == NULL || priv->root == NULL) {
 144         return;
 145     }
 146 
 147     if (cgi_output && print) {
 148         fprintf(out->dest, "Content-Type: text/html\n\n");
 149     }
 150 
 151     /* Add the head node last - it's not needed earlier because it doesn't contain
 152      * anything else that the user could add, and we want it done last to pick up
 153      * any options that may have been given.
 154      */
 155     head_node = xmlNewNode(NULL, (pcmkXmlStr) "head");
 156 
 157     if (title != NULL ) {
 158         pcmk_create_xml_text_node(head_node, "title", title);
 159     } else if (out->request != NULL) {
 160         pcmk_create_xml_text_node(head_node, "title", out->request);
 161     }
 162 
 163     charset_node = create_xml_node(head_node, "meta");
 164     crm_xml_add(charset_node, "charset", "utf-8");
 165 
 166     /* Add any extra header nodes the caller might have created. */
 167     for (int i = 0; i < g_slist_length(extra_headers); i++) {
 168         xmlAddChild(head_node, xmlCopyNode(g_slist_nth_data(extra_headers, i), 1));
 169     }
 170 
 171     /* Stylesheets are included two different ways.  The first is via a built-in
 172      * default (see the stylesheet_default const above).  The second is via the
 173      * html-stylesheet option, and this should obviously be a link to a
 174      * stylesheet.  The second can override the first.  At least one should be
 175      * given.
 176      */
 177     pcmk_create_xml_text_node(head_node, "style", stylesheet_default);
 178 
 179     if (stylesheet_link != NULL) {
 180         htmlNodePtr link_node = create_xml_node(head_node, "link");
 181         pcmk__xe_set_props(link_node, "rel", "stylesheet",
 182                            "href", stylesheet_link,
 183                            NULL);
 184     }
 185 
 186     xmlAddPrevSibling(priv->root->children, head_node);
 187 
 188     if (g_slist_length(priv->errors) > 0) {
 189         out->begin_list(out, "Errors", NULL, NULL);
 190         g_slist_foreach(priv->errors, add_error_node, (gpointer) out);
 191         out->end_list(out);
 192     }
 193 
 194     if (print) {
 195         htmlDocDump(out->dest, priv->root->doc);
 196     }
 197 
 198     if (copy_dest != NULL) {
 199         *copy_dest = copy_xml(priv->root);
 200     }
 201 
 202     g_slist_free_full(extra_headers, (GDestroyNotify) xmlFreeNode);
 203     extra_headers = NULL;
 204 }
 205 
 206 static void
 207 html_reset(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 208     CRM_ASSERT(out != NULL);
 209 
 210     out->dest = freopen(NULL, "w", out->dest);
 211     CRM_ASSERT(out->dest != NULL);
 212 
 213     html_free_priv(out);
 214     html_init(out);
 215 }
 216 
 217 static void
 218 html_subprocess_output(pcmk__output_t *out, int exit_status,
     /* [previous][next][first][last][top][bottom][index][help] */
 219                        const char *proc_stdout, const char *proc_stderr) {
 220     char *rc_buf = NULL;
 221 
 222     CRM_ASSERT(out != NULL);
 223 
 224     rc_buf = crm_strdup_printf("Return code: %d", exit_status);
 225 
 226     pcmk__output_create_xml_text_node(out, "h2", "Command Output");
 227     pcmk__output_create_html_node(out, "div", NULL, NULL, rc_buf);
 228 
 229     if (proc_stdout != NULL) {
 230         pcmk__output_create_html_node(out, "div", NULL, NULL, "Stdout");
 231         pcmk__output_create_html_node(out, "div", NULL, "output", proc_stdout);
 232     }
 233     if (proc_stderr != NULL) {
 234         pcmk__output_create_html_node(out, "div", NULL, NULL, "Stderr");
 235         pcmk__output_create_html_node(out, "div", NULL, "output", proc_stderr);
 236     }
 237 
 238     free(rc_buf);
 239 }
 240 
 241 static void
 242 html_version(pcmk__output_t *out, bool extended) {
     /* [previous][next][first][last][top][bottom][index][help] */
 243     CRM_ASSERT(out != NULL);
 244 
 245     pcmk__output_create_xml_text_node(out, "h2", "Version Information");
 246     pcmk__output_create_html_node(out, "div", NULL, NULL, "Program: Pacemaker");
 247     pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Version: %s", PACEMAKER_VERSION));
 248     pcmk__output_create_html_node(out, "div", NULL, NULL,
 249                                   "Author: Andrew Beekhof and "
 250                                   "the Pacemaker project contributors");
 251     pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Build: %s", BUILD_VERSION));
 252     pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Features: %s", CRM_FEATURES));
 253 }
 254 
 255 G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
 256 static void
 257 html_err(pcmk__output_t *out, const char *format, ...) {
 258     private_data_t *priv = NULL;
 259     int len = 0;
 260     char *buf = NULL;
 261     va_list ap;
 262 
 263     CRM_ASSERT(out != NULL && out->priv != NULL);
 264     priv = out->priv;
 265 
 266     va_start(ap, format);
 267     len = vasprintf(&buf, format, ap);
 268     CRM_ASSERT(len >= 0);
 269     va_end(ap);
 270 
 271     priv->errors = g_slist_append(priv->errors, buf);
 272 }
 273 
 274 G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
 275 static int
 276 html_info(pcmk__output_t *out, const char *format, ...) {
 277     return pcmk_rc_no_output;
 278 }
 279 
 280 static void
 281 html_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
     /* [previous][next][first][last][top][bottom][index][help] */
 282     htmlNodePtr node = NULL;
 283 
 284     CRM_ASSERT(out != NULL);
 285 
 286     node = pcmk__output_create_html_node(out, "pre", NULL, NULL, buf);
 287     crm_xml_add(node, "lang", "xml");
 288 }
 289 
 290 G_GNUC_PRINTF(4, 5)
     /* [previous][next][first][last][top][bottom][index][help] */
 291 static void
 292 html_begin_list(pcmk__output_t *out, const char *singular_noun,
 293                 const char *plural_noun, const char *format, ...) {
 294     int q_len = 0;
 295     private_data_t *priv = NULL;
 296     xmlNodePtr node = NULL;
 297 
 298     CRM_ASSERT(out != NULL && out->priv != NULL);
 299     priv = out->priv;
 300 
 301     /* If we are already in a list (the queue depth is always at least
 302      * one because of the <html> element), first create a <li> element
 303      * to hold the <h2> and the new list.
 304      */
 305     q_len = g_queue_get_length(priv->parent_q);
 306     if (q_len > 2) {
 307         pcmk__output_xml_create_parent(out, "li", NULL);
 308     }
 309 
 310     if (format != NULL) {
 311         va_list ap;
 312         char *buf = NULL;
 313         int len;
 314 
 315         va_start(ap, format);
 316         len = vasprintf(&buf, format, ap);
 317         va_end(ap);
 318         CRM_ASSERT(len >= 0);
 319 
 320         if (q_len > 2) {
 321             pcmk__output_create_xml_text_node(out, "h3", buf);
 322         } else {
 323             pcmk__output_create_xml_text_node(out, "h2", buf);
 324         }
 325 
 326         free(buf);
 327     }
 328 
 329     node = pcmk__output_xml_create_parent(out, "ul", NULL);
 330     g_queue_push_tail(priv->parent_q, node);
 331 }
 332 
 333 G_GNUC_PRINTF(3, 4)
     /* [previous][next][first][last][top][bottom][index][help] */
 334 static void
 335 html_list_item(pcmk__output_t *out, const char *name, const char *format, ...) {
 336     htmlNodePtr item_node = NULL;
 337     va_list ap;
 338     char *buf = NULL;
 339     int len;
 340 
 341     CRM_ASSERT(out != NULL);
 342 
 343     va_start(ap, format);
 344     len = vasprintf(&buf, format, ap);
 345     CRM_ASSERT(len >= 0);
 346     va_end(ap);
 347 
 348     item_node = pcmk__output_create_xml_text_node(out, "li", buf);
 349     free(buf);
 350 
 351     if (name != NULL) {
 352         crm_xml_add(item_node, "class", name);
 353     }
 354 }
 355 
 356 static void
 357 html_increment_list(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 358     /* This function intentially left blank */
 359 }
 360 
 361 static void
 362 html_end_list(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 363     private_data_t *priv = NULL;
 364 
 365     CRM_ASSERT(out != NULL && out->priv != NULL);
 366     priv = out->priv;
 367 
 368     /* Remove the <ul> tag. */
 369     g_queue_pop_tail(priv->parent_q);
 370     pcmk__output_xml_pop_parent(out);
 371 
 372     /* Remove the <li> created for nested lists. */
 373     if (g_queue_get_length(priv->parent_q) > 2) {
 374         pcmk__output_xml_pop_parent(out);
 375     }
 376 }
 377 
 378 static bool
 379 html_is_quiet(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 380     return false;
 381 }
 382 
 383 static void
 384 html_spacer(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 385     CRM_ASSERT(out != NULL);
 386     pcmk__output_create_xml_node(out, "br", NULL);
 387 }
 388 
 389 static void
 390 html_progress(pcmk__output_t *out, bool end) {
     /* [previous][next][first][last][top][bottom][index][help] */
 391     /* This function intentially left blank */
 392 }
 393 
 394 pcmk__output_t *
 395 pcmk__mk_html_output(char **argv) {
     /* [previous][next][first][last][top][bottom][index][help] */
 396     pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
 397 
 398     if (retval == NULL) {
 399         return NULL;
 400     }
 401 
 402     retval->fmt_name = "html";
 403     retval->request = pcmk__quote_cmdline(argv);
 404 
 405     retval->init = html_init;
 406     retval->free_priv = html_free_priv;
 407     retval->finish = html_finish;
 408     retval->reset = html_reset;
 409 
 410     retval->register_message = pcmk__register_message;
 411     retval->message = pcmk__call_message;
 412 
 413     retval->subprocess_output = html_subprocess_output;
 414     retval->version = html_version;
 415     retval->info = html_info;
 416     retval->err = html_err;
 417     retval->output_xml = html_output_xml;
 418 
 419     retval->begin_list = html_begin_list;
 420     retval->list_item = html_list_item;
 421     retval->increment_list = html_increment_list;
 422     retval->end_list = html_end_list;
 423 
 424     retval->is_quiet = html_is_quiet;
 425     retval->spacer = html_spacer;
 426     retval->progress = html_progress;
 427     retval->prompt = pcmk__text_prompt;
 428 
 429     return retval;
 430 }
 431 
 432 xmlNodePtr
 433 pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id,
     /* [previous][next][first][last][top][bottom][index][help] */
 434                               const char *class_name, const char *text) {
 435     htmlNodePtr node = NULL;
 436 
 437     CRM_ASSERT(out != NULL);
 438     CRM_CHECK(pcmk__str_eq(out->fmt_name, "html", pcmk__str_none), return NULL);
 439 
 440     node = pcmk__output_create_xml_text_node(out, element_name, text);
 441 
 442     if (class_name != NULL) {
 443         crm_xml_add(node, "class", class_name);
 444     }
 445 
 446     if (id != NULL) {
 447         crm_xml_add(node, "id", id);
 448     }
 449 
 450     return node;
 451 }
 452 
 453 void
 454 pcmk__html_add_header(const char *name, ...) {
     /* [previous][next][first][last][top][bottom][index][help] */
 455     htmlNodePtr header_node;
 456     va_list ap;
 457 
 458     va_start(ap, name);
 459 
 460     header_node = xmlNewNode(NULL, (pcmkXmlStr) name);
 461     while (1) {
 462         char *key = va_arg(ap, char *);
 463         char *value;
 464 
 465         if (key == NULL) {
 466             break;
 467         }
 468 
 469         value = va_arg(ap, char *);
 470         crm_xml_add(header_node, key, value);
 471     }
 472 
 473     extra_headers = g_slist_append(extra_headers, header_node);
 474 
 475     va_end(ap);
 476 }

/* [previous][next][first][last][top][bottom][index][help] */