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

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