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

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