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_create
  21. pcmk__html_add_header

   1 /*
   2  * Copyright 2019-2024 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     "." PCMK__VALUE_BOLD " { font-weight: bold }\n"
  23 
  24     "." PCMK_VALUE_ONLINE " { color: green }\n"
  25     "." PCMK_VALUE_OFFLINE " { color: red }\n"
  26     "." PCMK__VALUE_MAINT " { color: blue }\n"
  27     "." PCMK_VALUE_STANDBY " { color: blue }\n"
  28     "." PCMK__VALUE_HEALTH_RED " { color: red }\n"
  29     "." PCMK__VALUE_HEALTH_YELLOW " { color: GoldenRod }\n"
  30 
  31     "." PCMK__VALUE_RSC_FAILED " { color: red }\n"
  32     "." PCMK__VALUE_RSC_FAILURE_IGNORED " { color: DarkGreen }\n"
  33     "." PCMK__VALUE_RSC_MANAGED " { color: blue }\n"
  34     "." PCMK__VALUE_RSC_MULTIPLE " { color: orange }\n"
  35     "." PCMK__VALUE_RSC_OK " { color: green }\n"
  36 
  37     "." PCMK__VALUE_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     free_xml(priv->root);
  85     /* The elements of parent_q are xmlNodes that are a part of the
  86      * priv->root document, so the above line already frees them.  Don't
  87      * call g_queue_free_full here.
  88      */
  89     g_queue_free(priv->parent_q);
  90     g_slist_free_full(priv->errors, free);
  91     free(priv);
  92     out->priv = NULL;
  93 }
  94 
  95 static bool
  96 html_init(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
  97     private_data_t *priv = NULL;
  98 
  99     pcmk__assert(out != NULL);
 100 
 101     /* If html_init was previously called on this output struct, just return. */
 102     if (out->priv != NULL) {
 103         return true;
 104     } else {
 105         out->priv = calloc(1, sizeof(private_data_t));
 106         if (out->priv == NULL) {
 107             return false;
 108         }
 109 
 110         priv = out->priv;
 111     }
 112 
 113     priv->parent_q = g_queue_new();
 114 
 115     priv->root = pcmk__xe_create(NULL, "html");
 116     xmlCreateIntSubset(priv->root->doc, (pcmkXmlStr) "html", NULL, NULL);
 117 
 118     crm_xml_add(priv->root, PCMK_XA_LANG, PCMK__VALUE_EN);
 119     g_queue_push_tail(priv->parent_q, priv->root);
 120     priv->errors = NULL;
 121 
 122     pcmk__output_xml_create_parent(out, "body", NULL);
 123 
 124     return true;
 125 }
 126 
 127 static void
 128 add_error_node(gpointer data, gpointer user_data) {
     /* [previous][next][first][last][top][bottom][index][help] */
 129     char *str = (char *) data;
 130     pcmk__output_t *out = (pcmk__output_t *) user_data;
 131     out->list_item(out, NULL, "%s", str);
 132 }
 133 
 134 static void
 135 html_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
     /* [previous][next][first][last][top][bottom][index][help] */
 136     private_data_t *priv = NULL;
 137     htmlNodePtr head_node = NULL;
 138     htmlNodePtr charset_node = NULL;
 139     xmlNode *child_node = NULL;
 140 
 141     pcmk__assert(out != NULL);
 142 
 143     priv = out->priv;
 144 
 145     /* If root is NULL, html_init failed and we are being called from pcmk__output_free
 146      * in the pcmk__output_new path.
 147      */
 148     if (priv == NULL || priv->root == NULL) {
 149         return;
 150     }
 151 
 152     if (cgi_output && print) {
 153         fprintf(out->dest, "Content-Type: text/html\n\n");
 154     }
 155 
 156     /* Add the head node last - it's not needed earlier because it doesn't contain
 157      * anything else that the user could add, and we want it done last to pick up
 158      * any options that may have been given.
 159      */
 160     head_node = pcmk__xe_create(priv->root, "head");
 161     xmlAddPrevSibling(priv->root->children, head_node);
 162 
 163     if (title != NULL ) {
 164         child_node = pcmk__xe_create(head_node, "title");
 165         pcmk__xe_set_content(child_node, "%s", title);
 166     } else if (out->request != NULL) {
 167         child_node = pcmk__xe_create(head_node, "title");
 168         pcmk__xe_set_content(child_node, "%s", out->request);
 169     }
 170 
 171     charset_node = pcmk__xe_create(head_node, PCMK__XE_META);
 172     crm_xml_add(charset_node, "charset", "utf-8");
 173 
 174     /* Add any extra header nodes the caller might have created. */
 175     for (GSList *iter = extra_headers; iter != NULL; iter = iter->next) {
 176         pcmk__xml_copy(head_node, (xmlNode *) iter->data);
 177     }
 178 
 179     /* Stylesheets are included two different ways.  The first is via a built-in
 180      * default (see the stylesheet_default const above).  The second is via the
 181      * html-stylesheet option, and this should obviously be a link to a
 182      * stylesheet.  The second can override the first.  At least one should be
 183      * given.
 184      */
 185     child_node = pcmk__xe_create(head_node, "style");
 186     pcmk__xe_set_content(child_node, "%s", stylesheet_default);
 187 
 188     if (stylesheet_link != NULL) {
 189         htmlNodePtr link_node = pcmk__xe_create(head_node, "link");
 190         pcmk__xe_set_props(link_node, "rel", "stylesheet",
 191                            "href", stylesheet_link,
 192                            NULL);
 193     }
 194 
 195     if (g_slist_length(priv->errors) > 0) {
 196         out->begin_list(out, "Errors", NULL, NULL);
 197         g_slist_foreach(priv->errors, add_error_node, (gpointer) out);
 198         out->end_list(out);
 199     }
 200 
 201     if (print) {
 202         htmlDocDump(out->dest, priv->root->doc);
 203     }
 204 
 205     if (copy_dest != NULL) {
 206         *copy_dest = pcmk__xml_copy(NULL, priv->root);
 207     }
 208 
 209     g_slist_free_full(extra_headers, (GDestroyNotify) free_xml);
 210     extra_headers = NULL;
 211 }
 212 
 213 static void
 214 html_reset(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 215     pcmk__assert(out != NULL);
 216 
 217     out->dest = freopen(NULL, "w", out->dest);
 218     pcmk__assert(out->dest != NULL);
 219 
 220     html_free_priv(out);
 221     html_init(out);
 222 }
 223 
 224 static void
 225 html_subprocess_output(pcmk__output_t *out, int exit_status,
     /* [previous][next][first][last][top][bottom][index][help] */
 226                        const char *proc_stdout, const char *proc_stderr) {
 227     char *rc_buf = NULL;
 228 
 229     pcmk__assert(out != NULL);
 230 
 231     rc_buf = crm_strdup_printf("Return code: %d", exit_status);
 232 
 233     pcmk__output_create_xml_text_node(out, "h2", "Command Output");
 234     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, rc_buf);
 235 
 236     if (proc_stdout != NULL) {
 237         pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, "Stdout");
 238         pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL,
 239                                       PCMK__VALUE_OUTPUT, proc_stdout);
 240     }
 241     if (proc_stderr != NULL) {
 242         pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, "Stderr");
 243         pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL,
 244                                       PCMK__VALUE_OUTPUT, proc_stderr);
 245     }
 246 
 247     free(rc_buf);
 248 }
 249 
 250 static void
 251 html_version(pcmk__output_t *out, bool extended) {
     /* [previous][next][first][last][top][bottom][index][help] */
 252     pcmk__assert(out != NULL);
 253 
 254     pcmk__output_create_xml_text_node(out, "h2", "Version Information");
 255     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL,
 256                                   "Program: Pacemaker");
 257     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL,
 258                                   "Version: " PACEMAKER_VERSION);
 259     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL,
 260                                   "Author: Andrew Beekhof and "
 261                                   "the Pacemaker project contributors");
 262     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL,
 263                                   "Build: " BUILD_VERSION);
 264     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL,
 265                                   "Features: " CRM_FEATURES);
 266 }
 267 
 268 G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
 269 static void
 270 html_err(pcmk__output_t *out, const char *format, ...) {
 271     private_data_t *priv = NULL;
 272     int len = 0;
 273     char *buf = NULL;
 274     va_list ap;
 275 
 276     pcmk__assert((out != NULL) && (out->priv != NULL));
 277     priv = out->priv;
 278 
 279     va_start(ap, format);
 280     len = vasprintf(&buf, format, ap);
 281     pcmk__assert(len >= 0);
 282     va_end(ap);
 283 
 284     priv->errors = g_slist_append(priv->errors, buf);
 285 }
 286 
 287 G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
 288 static int
 289 html_info(pcmk__output_t *out, const char *format, ...) {
 290     return pcmk_rc_no_output;
 291 }
 292 
 293 static void
 294 html_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
     /* [previous][next][first][last][top][bottom][index][help] */
 295     htmlNodePtr node = NULL;
 296 
 297     pcmk__assert(out != NULL);
 298 
 299     node = pcmk__output_create_html_node(out, "pre", NULL, NULL, buf);
 300     crm_xml_add(node, PCMK_XA_LANG, "xml");
 301 }
 302 
 303 G_GNUC_PRINTF(4, 5)
     /* [previous][next][first][last][top][bottom][index][help] */
 304 static void
 305 html_begin_list(pcmk__output_t *out, const char *singular_noun,
 306                 const char *plural_noun, const char *format, ...) {
 307     int q_len = 0;
 308     private_data_t *priv = NULL;
 309     xmlNodePtr node = NULL;
 310 
 311     pcmk__assert((out != NULL) && (out->priv != NULL));
 312     priv = out->priv;
 313 
 314     /* If we are already in a list (the queue depth is always at least
 315      * one because of the <html> element), first create a <li> element
 316      * to hold the <h2> and the new list.
 317      */
 318     q_len = g_queue_get_length(priv->parent_q);
 319     if (q_len > 2) {
 320         pcmk__output_xml_create_parent(out, "li", NULL);
 321     }
 322 
 323     if (format != NULL) {
 324         va_list ap;
 325         char *buf = NULL;
 326         int len;
 327 
 328         va_start(ap, format);
 329         len = vasprintf(&buf, format, ap);
 330         va_end(ap);
 331         pcmk__assert(len >= 0);
 332 
 333         if (q_len > 2) {
 334             pcmk__output_create_xml_text_node(out, "h3", buf);
 335         } else {
 336             pcmk__output_create_xml_text_node(out, "h2", buf);
 337         }
 338 
 339         free(buf);
 340     }
 341 
 342     node = pcmk__output_xml_create_parent(out, "ul", NULL);
 343     g_queue_push_tail(priv->parent_q, node);
 344 }
 345 
 346 G_GNUC_PRINTF(3, 4)
     /* [previous][next][first][last][top][bottom][index][help] */
 347 static void
 348 html_list_item(pcmk__output_t *out, const char *name, const char *format, ...) {
 349     htmlNodePtr item_node = NULL;
 350     va_list ap;
 351     char *buf = NULL;
 352     int len;
 353 
 354     pcmk__assert(out != NULL);
 355 
 356     va_start(ap, format);
 357     len = vasprintf(&buf, format, ap);
 358     pcmk__assert(len >= 0);
 359     va_end(ap);
 360 
 361     item_node = pcmk__output_create_xml_text_node(out, "li", buf);
 362     free(buf);
 363 
 364     if (name != NULL) {
 365         crm_xml_add(item_node, PCMK_XA_CLASS, name);
 366     }
 367 }
 368 
 369 static void
 370 html_increment_list(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 371     /* This function intentially left blank */
 372 }
 373 
 374 static void
 375 html_end_list(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 376     private_data_t *priv = NULL;
 377 
 378     pcmk__assert((out != NULL) && (out->priv != NULL));
 379     priv = out->priv;
 380 
 381     /* Remove the <ul> tag, but do not free this result - it's still
 382      * part of the document.
 383      */
 384     g_queue_pop_tail(priv->parent_q);
 385     pcmk__output_xml_pop_parent(out);
 386 
 387     /* Remove the <li> created for nested lists. */
 388     if (g_queue_get_length(priv->parent_q) > 2) {
 389         pcmk__output_xml_pop_parent(out);
 390     }
 391 }
 392 
 393 static bool
 394 html_is_quiet(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 395     return false;
 396 }
 397 
 398 static void
 399 html_spacer(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 400     pcmk__assert(out != NULL);
 401     pcmk__output_create_xml_node(out, "br", NULL);
 402 }
 403 
 404 static void
 405 html_progress(pcmk__output_t *out, bool end) {
     /* [previous][next][first][last][top][bottom][index][help] */
 406     /* This function intentially left blank */
 407 }
 408 
 409 pcmk__output_t *
 410 pcmk__mk_html_output(char **argv) {
     /* [previous][next][first][last][top][bottom][index][help] */
 411     pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
 412 
 413     if (retval == NULL) {
 414         return NULL;
 415     }
 416 
 417     retval->fmt_name = "html";
 418     retval->request = pcmk__quote_cmdline(argv);
 419 
 420     retval->init = html_init;
 421     retval->free_priv = html_free_priv;
 422     retval->finish = html_finish;
 423     retval->reset = html_reset;
 424 
 425     retval->register_message = pcmk__register_message;
 426     retval->message = pcmk__call_message;
 427 
 428     retval->subprocess_output = html_subprocess_output;
 429     retval->version = html_version;
 430     retval->info = html_info;
 431     retval->transient = html_info;
 432     retval->err = html_err;
 433     retval->output_xml = html_output_xml;
 434 
 435     retval->begin_list = html_begin_list;
 436     retval->list_item = html_list_item;
 437     retval->increment_list = html_increment_list;
 438     retval->end_list = html_end_list;
 439 
 440     retval->is_quiet = html_is_quiet;
 441     retval->spacer = html_spacer;
 442     retval->progress = html_progress;
 443     retval->prompt = pcmk__text_prompt;
 444 
 445     return retval;
 446 }
 447 
 448 xmlNodePtr
 449 pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id,
     /* [previous][next][first][last][top][bottom][index][help] */
 450                               const char *class_name, const char *text) {
 451     htmlNodePtr node = NULL;
 452 
 453     pcmk__assert(out != NULL);
 454     CRM_CHECK(pcmk__str_eq(out->fmt_name, "html", pcmk__str_none), return NULL);
 455 
 456     node = pcmk__output_create_xml_text_node(out, element_name, text);
 457 
 458     if (class_name != NULL) {
 459         crm_xml_add(node, PCMK_XA_CLASS, class_name);
 460     }
 461 
 462     if (id != NULL) {
 463         crm_xml_add(node, PCMK_XA_ID, id);
 464     }
 465 
 466     return node;
 467 }
 468 
 469 /*!
 470  * \internal
 471  * \brief Create a new HTML element under a given parent with ID and class
 472  *
 473  * \param[in,out] parent  XML element that will be the new element's parent
 474  *                        (\c NULL to create a new XML document with the new
 475  *                        node as root)
 476  * \param[in]     name    Name of new element
 477  * \param[in]     id      CSS ID of new element (can be \c NULL)
 478  * \param[in]     class   CSS class of new element (can be \c NULL)
 479  *
 480  * \return Newly created XML element (guaranteed not to be \c NULL)
 481  */
 482 xmlNode *
 483 pcmk__html_create(xmlNode *parent, const char *name, const char *id,
     /* [previous][next][first][last][top][bottom][index][help] */
 484                   const char *class)
 485 {
 486     xmlNode *node = pcmk__xe_create(parent, name);
 487 
 488     pcmk__xe_set_props(node,
 489                        PCMK_XA_CLASS, class,
 490                        PCMK_XA_ID, id,
 491                        NULL);
 492     return node;
 493 }
 494 
 495 void
 496 pcmk__html_add_header(const char *name, ...) {
     /* [previous][next][first][last][top][bottom][index][help] */
 497     htmlNodePtr header_node;
 498     va_list ap;
 499 
 500     va_start(ap, name);
 501 
 502     header_node = pcmk__xe_create(NULL, name);
 503     while (1) {
 504         char *key = va_arg(ap, char *);
 505         char *value;
 506 
 507         if (key == NULL) {
 508             break;
 509         }
 510 
 511         value = va_arg(ap, char *);
 512         crm_xml_add(header_node, key, value);
 513     }
 514 
 515     extra_headers = g_slist_append(extra_headers, header_node);
 516 
 517     va_end(ap);
 518 }

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