textgen.c
上传用户:lyxiangda
上传日期:2007-01-12
资源大小:3042k
文件大小:36k
源码类别:

CA认证

开发平台:

WINDOWS

  1. /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
  2. /*
  3.  * The contents of this file are subject to the Mozilla Public
  4.  * License Version 1.1 (the "License"); you may not use this file
  5.  * except in compliance with the License. You may obtain a copy of
  6.  * the License at http://www.mozilla.org/MPL/
  7.  * 
  8.  * Software distributed under the License is distributed on an "AS
  9.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  10.  * implied. See the License for the specific language governing
  11.  * rights and limitations under the License.
  12.  * 
  13.  * The Original Code is the Netscape security libraries.
  14.  * 
  15.  * The Initial Developer of the Original Code is Netscape
  16.  * Communications Corporation.  Portions created by Netscape are 
  17.  * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
  18.  * Rights Reserved.
  19.  * 
  20.  * Contributor(s):
  21.  * 
  22.  * Alternatively, the contents of this file may be used under the
  23.  * terms of the GNU General Public License Version 2 or later (the
  24.  * "GPL"), in which case the provisions of the GPL are applicable 
  25.  * instead of those above.  If you wish to allow use of your 
  26.  * version of this file only under the terms of the GPL and not to
  27.  * allow others to use your version of this file under the MPL,
  28.  * indicate your decision by deleting the provisions above and
  29.  * replace them with the notice and other provisions required by
  30.  * the GPL.  If you do not delete the provisions above, a recipient
  31.  * may use your version of this file under either the MPL or the
  32.  * GPL.
  33.  */
  34. #include "prmon.h"
  35. #include "nlsutil.h"
  36. #include "serv.h" /* for SSM_DEBUG */
  37. #include "ssmerrs.h"
  38. #include "resource.h"
  39. #include "kgenctxt.h"
  40. #include "textgen.h"
  41. #include "minihttp.h"
  42. #include "certlist.h"
  43. #include "ssldlgs.h"
  44. #include "oldfunc.h"
  45. #include "pkcs11ui.h"
  46. #include "signtextres.h"
  47. #include "prmem.h"
  48. #include "certres.h"
  49. #include "advisor.h"
  50. #include "nlslayer.h"
  51. typedef enum
  52. {
  53.     TEXTGEN_PUNCT_LEFTBRACE = (int) 0,
  54.     TEXTGEN_PUNCT_RIGHTBRACE,
  55.     TEXTGEN_PUNCT_SINGLEQUOTE,
  56.     TEXTGEN_PUNCT_COMMA,
  57.     TEXTGEN_PUNCT_DOLLAR,
  58.     TEXTGEN_PUNCT_SPACE,
  59.     TEXTGEN_PUNCT_PERCENT,
  60.     TEXTGEN_PUNCT_MAX_INDEX
  61. } PunctIndex;
  62. static char *punct_ch = "{}',$ %"; /* make this match the enum above */
  63. typedef struct KeywordHandlerEntry
  64. {
  65.     char *keyword;
  66.     KeywordHandlerFunc func;
  67. } KeywordHandlerEntry;
  68. static SSMCollection *keyword_handlers = NULL;
  69. /* Forward declarations */
  70. SSMStatus SSM_GetAndExpandTextKeyedByString(SSMTextGenContext *cx,
  71.                                             const char *key, 
  72.                                             char **result);
  73. /* password keyword handler */
  74. SSMStatus SSM_ReSetPasswordKeywordHandler(SSMTextGenContext * cx);
  75. SSMStatus SSM_ShowFollowupKeywordHandler(SSMTextGenContext * cx);
  76. SSMStatus SSM_PasswordPrefKeywordHandler(SSMTextGenContext * cx);
  77. /* cert renewal keyword handler */
  78. SSMStatus SSM_RenewalCertInfoHandler(SSMTextGenContext* cx);
  79. /* Given a string and offset of a left brace, find the matching right brace. */
  80. static int
  81. SSMTextGen_FindRightBrace(char *str)
  82. {
  83.     int i, startOff = 0;
  84.     int result = -1, len;
  85.     char *raw;
  86.     if (!punct_ch[0] || !str || (startOff < 0))
  87.         return -1; /* didn't initialize earlier, or bad params  */
  88.     /* Get the length of the source string */
  89.     len = PL_strlen(str);
  90.     raw = str;
  91.     
  92.     /* Walk along the string until we find either a left or right brace. */
  93.     for(i=startOff+1;(i < len) && (result < 0);i++)
  94.     {
  95.         if (raw[i] == punct_ch[TEXTGEN_PUNCT_LEFTBRACE])
  96.         {
  97.             /* Another left brace. Recurse. 
  98.                Assigning back to i is ok, because we'll increment
  99.                before the next check (and avoid double-counting the
  100.                terminating right brace on which i sits after 
  101.                this call). */
  102.             i += SSMTextGen_FindRightBrace(&str[i]);
  103.         }
  104.         else if (raw[i] == punct_ch[TEXTGEN_PUNCT_RIGHTBRACE])
  105.         {
  106.             /* Found the end. Return now. */
  107.             result = i;
  108.             break;
  109.         }
  110.     }
  111.     return result;
  112. }
  113. static SSMStatus
  114. SSMTextGen_DequotifyString(char *str)
  115. {
  116.     if (str == NULL) {
  117.         return SSM_FAILURE;
  118.     }
  119.     while ((str = PL_strchr(str, ''')) != NULL) {
  120.         if (str[1] == ''') {
  121.             memmove(str, &str[1], PL_strlen(&str[1])+1); 
  122.         }
  123.         str++;
  124.     }
  125.     return SSM_SUCCESS;
  126. }
  127. PRBool
  128. SSMTextGen_StringContainsFormatParams(char *str)
  129. {
  130.     while ((str = PL_strchr(str, punct_ch[TEXTGEN_PUNCT_PERCENT])) != NULL) {
  131.         if (isdigit(str[1])) {
  132.             return PR_TRUE;
  133.         }
  134.         str++;
  135.     }
  136.     return PR_FALSE;
  137. }
  138. static PRInt32
  139. SSMTextGen_CountCommas(char *str)
  140. {
  141.     PRInt32 result = 0;
  142.     
  143.     while ((str = PL_strchr(str,punct_ch[TEXTGEN_PUNCT_COMMA])) != NULL) { 
  144.         result++;
  145.         str++;
  146.     }
  147.     return result;
  148. }
  149. /* Show a stack frame. */
  150. void
  151. SSMTextGen_Show(SSMTextGenContext *cx)
  152. {
  153.     char *temp_ch = NULL;
  154.     temp_ch = cx->m_keyword;
  155.     SSM_DEBUG("{%s", temp_ch);
  156.     if (cx->m_params && (SSM_Count(cx->m_params) > 0))
  157.     {
  158.         char *param = (char *) SSM_At(cx->m_params, 0);
  159.         int i = 0;
  160.         while(param)
  161.         {
  162.             temp_ch = param;
  163.             printf("%c%s", ((i==0)? '/':','), temp_ch);
  164.             param = (char *) SSM_At(cx->m_params, ++i);
  165.         }
  166.     }
  167.     printf("}n");
  168. }
  169. /* Trace back a text gen context. */
  170. void
  171. SSMTextGen_DoTraceback(SSMTextGenContext *cx)
  172. {
  173.     
  174.     if (!cx) 
  175.         return;
  176.     /* Depth first traceback */
  177.     if ((cx->m_caller) && (cx->m_caller != cx))
  178.         SSMTextGen_DoTraceback(cx->m_caller);
  179.     SSMTextGen_Show(cx);
  180. }
  181. void
  182. SSMTextGen_Traceback(char *reason, SSMTextGenContext *cx)
  183. {
  184.     if (reason)
  185.         SSM_DEBUG("ERROR - %sn", reason);
  186.     SSM_DEBUG("Traceback:n");
  187.     if (!cx)
  188.         SSM_DEBUG("(None available)n");
  189.     else
  190.         SSMTextGen_DoTraceback(cx);
  191.     SSM_DEBUG("-- End of traceback --n");
  192. }
  193. /* Create/destroy a textgen context. */
  194. SSMStatus
  195. SSMTextGen_NewContext(SSMTextGenContext *caller, /* can be NULL */
  196.                       HTTPRequest *req,
  197.                       char *keyword,
  198.                       char **params,
  199.                       SSMTextGenContext **result)
  200. {
  201.     SSMStatus rv = SSM_SUCCESS;
  202.     SSMTextGenContext *cx = (SSMTextGenContext *) PR_CALLOC(sizeof(SSMTextGenContext));
  203.     if (!cx)
  204.         goto loser;
  205.     /* Create the collection within the context. */
  206.     cx->m_params = SSM_NewCollection();
  207.     if (!cx->m_params)
  208.         goto loser;
  209.     cx->m_caller = caller;
  210.     cx->m_request = req;
  211.     if (keyword)
  212.         cx->m_keyword = PL_strdup(keyword);
  213.     else
  214.         cx->m_keyword = PL_strdup("");
  215.     if (cx->m_keyword == NULL)
  216.         goto loser;
  217.     cx->m_result = PL_strdup("");
  218.     if (params)
  219.     {
  220.         char *p;
  221.         PRIntn i;
  222.         for(i=0; params[i] != NULL; i++)
  223.         {
  224.             p = params[i];
  225.             if (p)
  226.                 SSM_Enqueue(cx->m_params, SSM_PRIORITY_NORMAL, p);
  227.         }
  228.     }
  229.     goto done;
  230.  loser:
  231.     if (rv == SSM_SUCCESS)
  232.         rv = SSM_FAILURE;
  233.     if (cx)
  234.     {
  235.         if (cx->m_params)
  236.         {
  237.             /* Couldn't finish filling out the params. The parameter
  238.                strings are still owned by the caller at this point, so
  239.                clear out the collection before destroying the context. */
  240.             char *tmp;
  241.             SSMStatus trv;
  242.             /* Deallocate the parameters individually, then
  243.                destroy the parameter collection. */
  244.             trv = SSM_Dequeue(cx->m_params, SSM_PRIORITY_NORMAL, 
  245.                               (void **) &tmp, PR_FALSE);
  246.             while ((trv == PR_SUCCESS) && tmp)
  247.             {
  248.                 trv = SSM_Dequeue(cx->m_params, SSM_PRIORITY_NORMAL, 
  249.                                   (void **) &tmp, PR_FALSE);
  250.             }
  251.         }
  252.         SSMTextGen_DestroyContext(cx);
  253.         cx = NULL;
  254.     }
  255.  done:
  256.     *result = cx;
  257.     return rv;
  258. }
  259. SSMStatus
  260. SSMTextGen_NewTopLevelContext(HTTPRequest *req,
  261.                               SSMTextGenContext **result)
  262. {
  263.     SSMTextGenContext *cx = NULL;
  264.     SSMStatus rv = SSM_SUCCESS;
  265.     
  266.     rv = SSMTextGen_NewContext(NULL, req, NULL, NULL, &cx);
  267.     *result = cx;
  268.     return rv;
  269. }
  270. static SSMTextGenContext *
  271. SSMTextGen_PushStack(SSMTextGenContext *cx, 
  272.                      char *key,
  273.                      char **params)
  274. {
  275.     SSMTextGenContext *newcx = NULL;
  276.     SSMStatus rv;
  277.     
  278.     rv = SSMTextGen_NewContext(cx, cx->m_request, key, params, &newcx);
  279.     if (rv != SSM_SUCCESS)
  280.         SSMTextGen_Traceback("Couldn't push textgen context stack", cx);
  281. #if 0
  282.     if (newcx)
  283.     {
  284.         SSM_DEBUG("New stack frame: ");
  285.         SSMTextGen_Show(newcx);
  286.     }
  287. #endif
  288.     return newcx;
  289. }
  290. void
  291. SSMTextGen_DestroyContext(SSMTextGenContext *cx)
  292. {
  293.     if (cx->m_params)
  294.     {
  295.         char *tmp;
  296.         SSMStatus rv;
  297.         /* Deallocate the parameters individually, then
  298.            destroy the parameter collection. */
  299.         rv = SSM_Dequeue(cx->m_params, SSM_PRIORITY_NORMAL, 
  300.                          (void **) &tmp, PR_FALSE);
  301.         while ((rv == PR_SUCCESS) && tmp)
  302.         {
  303.             PR_Free(tmp);
  304.             rv = SSM_Dequeue(cx->m_params, SSM_PRIORITY_NORMAL, 
  305.                              (void **) &tmp, PR_FALSE);
  306.         }
  307.         SSM_DestroyCollection(cx->m_params);
  308.     }
  309.     if (cx->m_keyword)
  310.         PR_Free(cx->m_keyword);
  311.     if (cx->m_result)
  312.         PR_Free(cx->m_result);
  313.     PR_Free(cx);
  314. }
  315. SSMControlConnection *
  316. SSMTextGen_GetControlConnection(SSMTextGenContext *cx)
  317. {
  318.     if (cx && cx->m_request)
  319.         return cx->m_request->ctrlconn;
  320.     else
  321.         return NULL;
  322. }
  323. SSMResource *
  324. SSMTextGen_GetTargetObject(SSMTextGenContext *cx)
  325. {
  326.     SSMResource *target = NULL;
  327.     if (cx && cx->m_request && cx->m_request->target) {
  328.         target = cx->m_request->target;
  329.     } else {
  330.         SSMControlConnection *ctrl;
  331.         ctrl = SSMTextGen_GetControlConnection(cx);
  332.         target = &(ctrl->super.super);
  333.     }
  334.     return target;
  335. }
  336. /* Allocate/deallocate an array of UTF8 Strings. */
  337. static void
  338. SSMTextGen_DeleteStringPtrArray(char **array)
  339. {
  340.     PRInt32 i;
  341.     
  342.     if (array)
  343.     {
  344.         for(i=0; array[i] != NULL; i++)
  345.         {
  346.             PR_Free(array[i]);
  347.             array[i] = NULL;
  348.         }
  349.         PR_Free(array);
  350.     }
  351. }
  352. /* Given a comma-delimited UnicodeString, split the first
  353.    string off and put the remainder of the fields into an array. */
  354. static SSMStatus
  355. SSMTextGen_SplitKeywordParams(const char *orig,
  356.                               char **keywdResult,
  357.                               char ***paramResult)
  358. {
  359.     char **params = NULL;
  360.     char *keywd;
  361.     char *space, *cursor, *comma;
  362.     PRInt32 argLen;
  363.     SSMStatus rv = SSM_SUCCESS;
  364.     PRInt32 i;
  365.     PRInt32 numParams;
  366.     /* in case we fail */
  367.     *keywdResult = NULL;
  368.     *paramResult = NULL;
  369.     if (!orig)
  370.     {
  371.         rv = PR_INVALID_ARGUMENT_ERROR;
  372.         goto loser;
  373.     }
  374.     /* Get the keyword out first. */
  375.     /* If we have parameters, copy them off. */
  376.     space = PL_strchr(orig, punct_ch[TEXTGEN_PUNCT_SPACE]);
  377.     if (space != NULL) {
  378.         char ch;
  379.         ch = space[0];
  380.         space[0] = '';
  381.         keywd = PL_strdup(orig);
  382.         space[0] = ch;
  383.     } else {
  384.         int len, i;
  385.         len = PL_strlen(orig);
  386.         keywd = PL_strdup(orig);
  387.         for (i=len; i>=0 && isspace(keywd[i]); i--) {
  388.             keywd = '';
  389.         }
  390.     }
  391.     /* Now get the parameters. */
  392.     if (space != NULL) {
  393.         cursor = space+1;
  394.         numParams = SSMTextGen_CountCommas(cursor)+2;
  395.         params = SSM_ZNEW_ARRAY(char*, numParams);
  396.         for (i=0; i<(numParams-1) && cursor != NULL; i++) {
  397.             comma = PL_strchr(cursor, punct_ch[TEXTGEN_PUNCT_COMMA]);
  398.             if (comma != NULL) {
  399.                 argLen = comma-cursor;
  400.             } else {
  401.                 argLen = PL_strlen(cursor);
  402.             }
  403.             params[i] = SSM_NEW_ARRAY(char, (argLen+1));
  404.             if (params[i] == NULL) {
  405.                 goto loser;
  406.             }
  407.             PL_strncpy(params[i], cursor, argLen);
  408.             params[i][argLen] = '';
  409.             cursor = (comma == NULL) ? NULL : comma+1;
  410.         }
  411.     }
  412.     goto done;
  413.  loser:
  414.     if (rv != SSM_SUCCESS)
  415.         rv = SSM_FAILURE;
  416.     if (keywd)
  417.     {
  418.         PR_Free(keywd);
  419.         keywd = NULL;
  420.     }
  421.     if (params)
  422.     {
  423.         SSMTextGen_DeleteStringPtrArray(params);
  424.         params = NULL;
  425.     }
  426.  done:
  427.     *keywdResult = keywd;
  428.     *paramResult = params;
  429.     return rv;
  430. }
  431. /* Keyword handler routines */
  432. static SSMStatus
  433. SSM_KeywordHandlerInitialize(void)
  434. {
  435.     keyword_handlers = SSM_NewCollection();
  436.     return SSM_SUCCESS;
  437. }
  438. SSMStatus
  439. SSM_RegisterKeywordHandler(char *keyword,
  440.                            KeywordHandlerFunc func)
  441. {
  442.     SSMStatus rv = SSM_SUCCESS;
  443.     KeywordHandlerEntry *entry = NULL;
  444.     if ((!keyword_handlers) || (!keyword) || (!func))
  445.         goto loser;
  446.     entry = (KeywordHandlerEntry *) PR_CALLOC(sizeof(KeywordHandlerEntry));
  447.     if (!entry)
  448.         goto loser;
  449.     entry->keyword = keyword;
  450.     entry->func = func;
  451.     SSM_Enqueue(keyword_handlers, SSM_PRIORITY_NORMAL, entry);
  452.     goto done;
  453.  loser:
  454.     if (rv == SSM_SUCCESS) rv = SSM_FAILURE;
  455.     if (entry)
  456.         PR_Free(entry);
  457.  done:
  458.     return rv;
  459. }
  460. SSMStatus
  461. SSMTextGen_DeregisterKeywordHandler(KeywordHandlerFunc *func)
  462. {
  463.     KeywordHandlerEntry *e, *found = NULL;
  464.     PRIntn i;
  465.     PRIntn numEntries = SSM_Count(keyword_handlers);
  466.     SSMStatus rv = SSM_SUCCESS;
  467.     for(i=0;i<numEntries;i++)
  468.     {
  469.         e = (KeywordHandlerEntry *) SSM_At(keyword_handlers, i);
  470.         if ((e != NULL) && (e->func == (KeywordHandlerFunc) func))
  471.         {
  472.             found = e;
  473.             break;
  474.         }
  475.     }
  476.     if (found)
  477.     {
  478.         rv = SSM_Remove(keyword_handlers, found);
  479.         if (rv == SSM_SUCCESS)
  480.         {
  481.             /* Deallocate (found) since we no longer need it. */
  482.             PR_Free(found);
  483.         }
  484.     }
  485.     else
  486.         rv = SSM_FAILURE;
  487.     return rv;
  488. }
  489. static SSMStatus
  490. SSMTextGen_FindKeywordHandler(char *key, 
  491.                               KeywordHandlerFunc *func)
  492. {
  493.     KeywordHandlerEntry *e, *found = NULL;
  494.     char *key_ch = key;
  495.     PRIntn i;
  496.     PRIntn numEntries = SSM_Count(keyword_handlers);
  497.     *func = NULL; /* in case we fail */
  498.     if (key_ch)
  499.     {
  500.         for(i=0;i<numEntries;i++)
  501.         {
  502.             e = (KeywordHandlerEntry *) SSM_At(keyword_handlers, i);
  503.             if (!PL_strcmp(e->keyword, key_ch))
  504.             {
  505.                 found = e;
  506.                 break;
  507.             }
  508.         }
  509.     }
  510.     if (found)
  511.         *func = found->func;
  512.     return (*func ? SSM_SUCCESS : SSM_FAILURE);
  513. }
  514. /*
  515.   Given a numbered keyword argument in (arg), return the appropriate
  516.   argument from the textgen context.
  517.  */
  518. static SSMStatus
  519. SSMTextGen_ReplaceArgument(SSMTextGenContext *cx,
  520.                            char *keywd, char **dest)
  521. {
  522.     SSMStatus rv = SSM_SUCCESS;
  523.     char *arg = NULL, *param = NULL, *raw;
  524.     PRInt32 argNum = -1;
  525.     /* Is the first char a $? If not, bail. */
  526.     raw = keywd;
  527.     if ((!raw) || (raw[0] != punct_ch[TEXTGEN_PUNCT_DOLLAR]))
  528.         goto loser;
  529.     /* 
  530.        If we're here, this means that we think we have a numeric parameter. 
  531.        Copy the keyword, lop off the first char, and convert to an arg number.
  532.     */
  533.     arg = keywd+1;
  534.     argNum = SSMTextGen_atoi(arg);
  535.     if (argNum < 0)
  536.         goto loser;
  537.     param = (char *) SSM_At(cx->m_params, argNum-1);
  538.     if (!param)
  539.     {
  540.         SSM_DEBUG("ERROR: Wanted argument %d when only %d args present.n", 
  541.                   argNum, SSM_Count(cx->m_params));
  542.         SSMTextGen_Traceback(NULL, cx);
  543.         goto loser;
  544.     }
  545.     *dest = PL_strdup(param);
  546.     
  547.     goto done;
  548.  loser:
  549.     if (rv == SSM_SUCCESS) rv = SSM_FAILURE;
  550.     if (*dest)
  551.     {
  552.         PR_Free(*dest);
  553.         *dest = NULL;
  554.     }
  555.  done:
  556.     return rv;
  557. }
  558. /*
  559.     Given a Cartman keyword (presumably found within a string),
  560.     replace it with appropriate content.
  561. */
  562. static SSMStatus
  563. SSMTextGen_ProcessKeyword(SSMTextGenContext *cx, 
  564.                           char *src, char **dest)
  565. {
  566.     SSMStatus rv = SSM_SUCCESS;
  567.     SSMTextGenContext *newcx = NULL;
  568.     char *keywd = NULL;
  569.     char **params = NULL;
  570.     KeywordHandlerFunc func;
  571.     *dest = NULL; /* in case we fail */
  572.     if (PL_strchr(src, 'n')) {
  573.         /* We've got some text with newlines in it.  Keywords in properties
  574.          * files aren't allowed to have newlines in them.
  575.          */
  576.         goto loser;
  577.     }
  578.     /* Split out into keyword and parameters. */
  579.     rv = SSMTextGen_SplitKeywordParams(src, &keywd, &params);
  580.     if (rv != SSM_SUCCESS)
  581.         goto loser;
  582. /*     SSM_DebugUTF8String("SSMTextGen_ProcessKeyword - orig src", src); */
  583. /*     SSM_DebugUTF8String("SSMTextGen_ProcessKeyword - keywd", keywd); */
  584. /*     if (cx->m_params) */
  585. /*     { */
  586. /*     char buf[256]; */
  587. /*     int i = 0; */
  588. /*        UnicodeStringPtr param = NULL; */
  589. /*         for(param = SSM_At(cx->m_params, 0), i = 0; param != NULL; i++) */
  590. /*         { */
  591. /*             param = SSM_At(cx->m_params, i); */
  592. /*             if (param) */
  593. /*             { */
  594. /*                 PR_snprintf(buf, 256, "SSMTextGen_ProcessKeyword - param[%d]", i+1); */
  595. /*                 SSM_DebugUTF8String(buf, param); */
  596. /*             } */
  597. /*         } */
  598. /*     } */
  599.     /* If this is a numbered parameter, replace it with
  600.        what we have stored in the textgen context. */
  601.     rv = SSMTextGen_ReplaceArgument(cx, keywd, dest);
  602.     if (rv == SSM_SUCCESS)
  603.         goto done;
  604.     /* Push the textgen stack. */
  605.     newcx = SSMTextGen_PushStack(cx, keywd, params);
  606.     if (!newcx)
  607.         goto loser;
  608.     /* SSMTextGen_PushStack puts the args of params into a 
  609.      * collection.  So we can now free the array since the 
  610.      * individual pointers are stored in a collection
  611.      */
  612.     PR_Free(params);
  613.     params = NULL;
  614.     /* Look for a keyword handler. */
  615.     rv = SSMTextGen_FindKeywordHandler(keywd, &func);
  616.     if ((rv == SSM_SUCCESS) && (func))
  617.     {
  618.         /* Run the keyword handler. */
  619.         rv = (*func)(newcx);
  620.         if (rv != SSM_SUCCESS)
  621.         {
  622.             SSMTextGen_Traceback("Keyword handler returned error %d", newcx);
  623.             goto loser;
  624.         }
  625.         *dest = newcx->m_result;
  626.         newcx->m_result = NULL; /* so the memory doesn't get deallocated now */
  627.         goto done;
  628.     }
  629.     /* Treat (keywd) as the name of a string in the properties
  630.        file. Extract the value, expand it, and return it (if there
  631.        is something available). (This pushes the textgen stack.) */
  632.     rv = SSM_GetAndExpandTextKeyedByString(newcx, keywd, dest);
  633.     if (rv == SSM_SUCCESS) 
  634.         goto done;
  635.  loser:
  636.     if (rv == SSM_SUCCESS) rv = SSM_FAILURE;
  637.     if (*dest)
  638.     {
  639.         PR_Free(*dest);
  640.         *dest = NULL;
  641.     }
  642.  done:
  643.     if (keywd)
  644.         PR_Free(keywd);
  645.     if (newcx)
  646.         SSMTextGen_DestroyContext(newcx);
  647. /*     if (params && !newcx) */
  648. /*         SSMTextGen_DeleteStringPtrArray(params); */
  649.     return rv;
  650. }
  651. typedef struct SSMTextGenResultStr {
  652.     char *result;
  653.     int allocSize, curSize;
  654. } SSMTextGenResult;
  655. SSMStatus
  656. ssmtextgen_init_segresult(SSMTextGenResult *result, int origLen)
  657. {
  658.     memset (result, 0, sizeof(SSMTextGenResult));
  659.     result->allocSize = (int)(origLen*1.5);
  660.     result->result    = SSM_NEW_ARRAY(char, result->allocSize+1);
  661.     if (result->result == NULL) {
  662.         return SSM_FAILURE;
  663.     }
  664.     result->result[0] = '';
  665.     result->curSize   = 0;
  666.     return SSM_SUCCESS;
  667. }
  668. SSMStatus
  669. ssmtextgen_add_segment(SSMTextGenResult *result, char *segment)
  670. {
  671.     int segLen;
  672.     segLen = PL_strlen(segment);
  673.     if ((result->curSize+segLen) > result->allocSize) {
  674.         char *newString;
  675.         int newLen, defReallocLen, newSegReallocLen;
  676.         defReallocLen = result->allocSize*2;
  677.         newSegReallocLen = segLen + result->curSize;
  678.         newLen = (defReallocLen > newSegReallocLen) ? defReallocLen : 
  679.                                                       newSegReallocLen * 2;
  680.         newString = (char *) PR_Realloc(result->result, newLen);
  681.         if (newString == NULL) {
  682.             return SSM_FAILURE;
  683.         }
  684.         result->result    = newString;
  685.         result->allocSize = newLen;
  686.     }
  687.     memcpy(&result->result[result->curSize], segment, segLen);
  688.     result->curSize += segLen;
  689.     result->result[result->curSize] = '';
  690.     return SSM_SUCCESS;
  691. }
  692. /* 
  693.    Perform substitutions for non-numeric parameters in (str). Parameters
  694.    in the text are surrounded by curly braces.
  695. */
  696. SSMStatus
  697. SSMTextGen_SubstituteString(SSMTextGenContext *cx, 
  698.                             char *str, char **result)
  699. {
  700.     SSMStatus rv = SSM_SUCCESS;
  701.     int len, fragLen, rightBraceIndex;
  702.     char *repl1 = NULL, *repl2 = NULL;
  703.     char *leftBrace, *rightBrace;
  704.     char *tmp, *raw;
  705.     SSMTextGenResult segResult;
  706.     /* in case we fail */
  707.     *result = NULL;
  708.     if ((!str) || (!result))
  709.         return SSM_FAILURE;
  710.     /* Get the length of the source string, and a ptr into it */
  711.     raw = str;
  712.     len = PL_strlen(raw);
  713.     if (ssmtextgen_init_segresult(&segResult, len) != SSM_SUCCESS) {
  714.         goto loser;
  715.     }
  716.     /* Scan the string for the next keyword, if any. */
  717.     while (1)
  718.     {
  719.         /* First look for the left brace */
  720.         leftBrace = PL_strchr(raw, punct_ch[TEXTGEN_PUNCT_LEFTBRACE]);
  721.         if (leftBrace == NULL) {
  722.             if (ssmtextgen_add_segment(&segResult, raw) != 
  723.                 SSM_SUCCESS) {
  724.                 goto loser;
  725.             }
  726.             break;
  727.         }
  728.         /* Look for the corresponding right brace. */
  729.         rightBraceIndex = SSMTextGen_FindRightBrace(leftBrace);
  730.         if (rightBraceIndex >= len) {
  731.             /* No corresponding right brace, should ignore this one. */
  732.             /* We can stop processing here. */
  733.             if (ssmtextgen_add_segment(&segResult, raw) != 
  734.                 SSM_SUCCESS) {
  735.                 goto loser;
  736.             }
  737.             break;
  738.         }
  739.         /* Process the keyword. */
  740.         /* Get all of the text between the braces and expand it. */
  741.         rightBrace = &leftBrace[rightBraceIndex];
  742.         fragLen = rightBraceIndex;
  743.         tmp = SSM_NEW_ARRAY(char, fragLen);
  744.         if (tmp == NULL) {
  745.             goto loser;
  746.         }
  747.         memcpy(tmp, leftBrace+1, fragLen-1);
  748.         tmp[fragLen-1] = '';
  749.         rv = SSMTextGen_SubstituteString(cx, tmp, &repl1);
  750.         PR_Free(tmp);
  751.         tmp = NULL;
  752.         if (rv != SSM_SUCCESS) {
  753.             goto loser;        
  754.         }
  755.         rv = SSMTextGen_ProcessKeyword(cx, repl1, &repl2);
  756.         if (rv != SSM_SUCCESS) {
  757.             char ch;
  758.             SSMStatus rv1, rv2;
  759.             /* The text between the braces couldn't be replaced, so
  760.              * insert everything starting with raw up to and including
  761.              * the left brace, then insert repl1 which is what the substring
  762.              * was substituted as, then place the closing right bracket.
  763.              */
  764.             ch = leftBrace[1];
  765.             leftBrace[1] = '';
  766.             rv1 = ssmtextgen_add_segment(&segResult, raw);
  767.             leftBrace[1] = ch;
  768.             rv2 = ssmtextgen_add_segment(&segResult, repl1);
  769.             if (rv1 != SSM_SUCCESS || rv2 != SSM_SUCCESS ||
  770.                 ssmtextgen_add_segment(&segResult, "}") != SSM_SUCCESS) {
  771.                 goto loser;
  772.             }
  773.         } else {
  774.             /* We processed a keyword, so take the string before the left
  775.              * brace, make it the next segment, then make the response
  776.              * from SSMTextGen_ProcessKeyword the segment after that.
  777.              */
  778.             SSMStatus rv1, rv2;
  779.             leftBrace[0] = '';
  780.             rv1 = ssmtextgen_add_segment(&segResult, raw);
  781.             rv2 = ssmtextgen_add_segment(&segResult, repl2);
  782.             PR_Free(repl2);
  783.             repl2 = NULL;
  784.             leftBrace[0] = punct_ch[TEXTGEN_PUNCT_LEFTBRACE];
  785.             if (rv1 != SSM_SUCCESS || rv2 != SSM_SUCCESS) {
  786.                 goto loser;
  787.             }
  788.         }
  789.         if (repl1 != NULL) {
  790.             PR_Free(repl1);
  791.             repl1 = NULL;
  792.         }
  793.         if (repl2 != NULL) {
  794.             PR_Free(repl2);
  795.             repl2 = NULL;
  796.         }
  797.         raw = &rightBrace[1];
  798.     }
  799.     *result = segResult.result;
  800.     rv = SSM_SUCCESS;
  801.     goto done;
  802.  loser:
  803.     if (rv == SSM_SUCCESS) rv = SSM_FAILURE;
  804.     /* delete/NULL result if we ran into a problem */
  805.     if (*result)
  806.     {
  807.         PR_Free(*result);
  808.         *result = NULL;
  809.     }
  810.  done:
  811.     /* deallocate working strings */
  812.     return rv;
  813. }
  814. /* Given a key and a context, cycle through resource bundles until
  815.    we find a match for the desired key. */
  816. SSMStatus
  817. SSM_FindUTF8StringInBundles(SSMTextGenContext *cx,
  818.                                const char *key, 
  819.                                char **utf8Result)
  820. {
  821.     SSMStatus rv = SSM_FAILURE;
  822.     char *utf8;
  823.     /* Attempt to get the string. */
  824.     utf8 = nlsGetUTF8String(key);
  825.     if (utf8) {
  826.         rv = SSMTextGen_DequotifyString(utf8); /* found it */
  827.         *utf8Result = utf8;
  828.     }
  829.     return rv;
  830. }
  831. /* Get a UnicodeString from the resource database and expand it. */
  832. SSMStatus
  833. SSM_GetAndExpandText(SSMTextGenContext *cx, 
  834.                      const char *key, 
  835.                      char **result)
  836. {
  837.     char *replText = NULL;
  838.     char *origText=NULL;
  839.     SSMStatus rv = SSM_SUCCESS;
  840.     if (!key || !result)
  841.         goto loser;
  842.     PR_FREEIF(*result);
  843.     *result = NULL; /* in case we fail */
  844.     if (cx != NULL && PL_strcmp(cx->m_keyword, key)) {
  845.         PR_Free(cx->m_keyword);
  846.         cx->m_keyword = PL_strdup(key);
  847.     }
  848.     /* Get the text from the database. */
  849.     SSM_DEBUG("Requesting string <%s> from the text database.n", key);
  850.     /*nrv = NLS_PropertyResourceBundleGetString(nls_bndl, 
  851.                                               key,
  852.                                               origText);*/
  853.     rv = SSM_FindUTF8StringInBundles(cx, key, &origText);
  854.     if (rv != SSM_SUCCESS)
  855.     {
  856.         if (cx != NULL)
  857.             SSM_HTTPReportSpecificError(cx->m_request, 
  858.                                         "SSM_GetAndExpandText: SSM error %d "
  859.                                         "getting property string '%s'.", 
  860.                                         rv, key);
  861.         goto loser;
  862.     }
  863.     /* Expand/replace keywords in the string. */
  864.     rv = SSMTextGen_SubstituteString(cx, origText, &replText);
  865.     /*     SSM_DebugUTF8String("SSM_GetAndExpandText - expanded text",  */
  866.     /*                            replText); */
  867.     if ((rv != SSM_SUCCESS) || (!replText))
  868.         goto loser;
  869.     *result = replText;
  870.     replText = NULL; /* So we don't free it*/
  871.     goto done;
  872.  loser:
  873.     if (rv == SSM_SUCCESS)
  874.         rv = SSM_FAILURE;
  875.     /* ### mwelch Need to attempt to return a blank string in case some
  876.        errors happen (such as: we can't find the string/locale not supported).*/
  877.     if (*result) {
  878.         PR_Free(*result);
  879.         *result = NULL;
  880.     }
  881.  done:
  882.     /* Dispose of intermediate strings if applicable. */
  883.     if (origText)
  884.         PR_Free(origText);
  885.     if (replText)
  886.         PR_Free(replText);
  887.     return rv;
  888. }
  889. /* Get a string from the resource database using a Unicode string
  890.    as key. Then, expand the string. */
  891. SSMStatus
  892. SSM_GetAndExpandTextKeyedByString(SSMTextGenContext *cx,
  893.                                   const char *key, 
  894.                                   char **result)
  895. {
  896.     SSMStatus rv = SSM_SUCCESS;
  897.     char *text=NULL;
  898.     *result = NULL; /* in case we fail */
  899.     /* Use the ascii equivalent of (key) as the actual key. */
  900.     if (!key)
  901.         goto loser;
  902.     /* Now, call SSM_GetAndExpandText above using the ASCII key. */
  903.     rv = SSM_GetAndExpandText(cx, key, &text);
  904.     if (rv != SSM_SUCCESS)
  905.         goto loser;
  906.     *result = text;
  907.     text = NULL;
  908.     goto done;
  909.  loser:
  910.     if (rv == SSM_SUCCESS) 
  911.         rv = SSM_FAILURE;
  912.     if (text != NULL) 
  913.         PR_Free(text);
  914.     *result = NULL;
  915.  done:
  916.     return rv;
  917. }
  918. /* Get a numeric parameter from the properties file. */
  919. SSMStatus
  920. SSM_GetNumber(SSMTextGenContext *cx, char *key, PRInt32 *param)
  921. {
  922.     char *text = NULL;
  923.     SSMStatus rv = SSM_SUCCESS;
  924.     *param = 0; /* in case we fail */
  925.     /* Get expanded text from the database. */
  926.     rv = SSM_GetAndExpandText(cx, key, &text);
  927.     if ((rv != SSM_SUCCESS) || (!text))
  928.         goto loser;
  929.     *param = SSMTextGen_atoi(text);
  930.     goto done;
  931.  loser:
  932.     if (rv == SSM_SUCCESS)
  933.         rv = SSM_FAILURE;
  934.  done:
  935.     if (text)
  936.         PR_Free(text);
  937.     return rv;
  938. }
  939. /* 
  940.    Top level routine called by non-NLS-using parts of Cartman. 
  941.    Retrieves a string, expands it, then formats it according to the
  942.    _Print method of the target object. 
  943. */
  944. SSMStatus
  945. SSM_GetUTF8Text(SSMTextGenContext *cx,
  946.                 const char *key, 
  947.                 char **resultStr)
  948. {
  949.     char *expandedText = NULL, *fmtResult = NULL;
  950.     SSMStatus rv = SSM_SUCCESS;
  951.     SSMResource *targetObj;
  952.     *resultStr = NULL; /* in case we fail */
  953.     /* Get expanded text from the database. */
  954.     rv = SSM_GetAndExpandText(cx, key, &expandedText);
  955.     if ((rv != SSM_SUCCESS) || (!expandedText))
  956.         goto loser;
  957.     targetObj = SSMTextGen_GetTargetObject(cx);
  958.     if (SSMTextGen_StringContainsFormatParams(expandedText))
  959.     {
  960.         if (targetObj != NULL)
  961.             rv = SSM_MessageFormatResource(targetObj, expandedText, 
  962.    cx->m_request->numParams-2, 
  963.    &(cx->m_request->paramValues[2]), 
  964.                                            &fmtResult);
  965.         else
  966.         {
  967.             PR_ASSERT(!"No target object for formatting");
  968.             rv = SSM_FAILURE;
  969.         }
  970.     } else {
  971.         fmtResult = expandedText;
  972.     }
  973.     if (rv != SSM_SUCCESS)
  974.         goto loser;
  975.     *resultStr = fmtResult;
  976.              
  977.     if (*resultStr == NULL) {
  978.         goto loser;
  979.     }
  980.     goto done;
  981.  loser:
  982.     if (rv != SSM_SUCCESS) rv = SSM_FAILURE;
  983.     if (*resultStr != NULL) {
  984.         PR_Free(*resultStr);
  985.     }
  986.     *resultStr = NULL;
  987.  done:
  988.     if ((expandedText) && (*resultStr != expandedText))
  989.         PR_Free(expandedText);
  990.     if ((fmtResult) && (*resultStr != fmtResult))
  991.         PR_Free(fmtResult);
  992.     return rv;
  993. }
  994. void
  995. TestNLS(void)
  996. {
  997.     char *ustr;
  998.     SSMTextGenContext *cx;
  999.     SSMStatus rv;
  1000.     rv = SSMTextGen_NewTopLevelContext(NULL, &cx);
  1001.     SSM_GetUTF8Text(cx, "testnls", &ustr);
  1002.     SSM_DebugUTF8String("Expanded testnls", ustr);
  1003.     PR_FREEIF(ustr);
  1004.     SSM_GetUTF8Text(cx, "top1_content", &ustr);
  1005.     SSM_DebugUTF8String("Expanded top1", ustr);
  1006.     PR_FREEIF(ustr);
  1007.     SSM_GetUTF8Text(cx, "left3-1_content", &ustr);
  1008.     SSM_DebugUTF8String("Expanded left3-1", ustr);
  1009.     PR_FREEIF(ustr);
  1010.     SSMTextGen_DestroyContext(cx);
  1011. }
  1012. /* 
  1013.    Hello World keyword handler example.
  1014.    This handler replaces all instances of the keyword "{hello}"
  1015.    with "Hello, World!" in the text being processed. Look for
  1016.    "Hello World" in minihttp.c for content handler examples.
  1017. */
  1018. SSMStatus
  1019. SSM_HelloKeywordHandler(SSMTextGenContext *cx)
  1020. {
  1021.     SSMStatus rv = SSM_SUCCESS; /* generic rv */
  1022.     cx->m_result = PL_strdup("Hello, World");
  1023.     /* All done. */
  1024.     return rv;
  1025. }
  1026. SSMStatus
  1027. wrap_test(SSMTextGenContext *cx)
  1028. {
  1029.     SSMStatus rv = SSM_SUCCESS;
  1030.     char *p, *pattern, *temp = NULL, *fmt;
  1031.     int i;
  1032.     pattern = PL_strdup("test(%1$s) ");
  1033.     /* Zero out the result. */
  1034.     SSMTextGen_UTF8StringClear(&cx->m_result);
  1035.     /* Wrap the parameters inside "test()", and concatenate them onto the 
  1036.        result. */
  1037.     for(i=0;i<SSM_Count(cx->m_params);i++)
  1038.     {
  1039.         p = (char *) SSM_At(cx->m_params, i);
  1040.         temp = PR_smprintf(pattern, p);
  1041.         if (temp == NULL) {
  1042.             goto loser;
  1043.         }
  1044.         fmt = PR_smprintf("%s%s", cx->m_result, temp);
  1045.         PR_smprintf_free(temp);
  1046.         if (fmt == NULL) {
  1047.             goto loser;
  1048.         }
  1049.         PR_Free(cx->m_result);
  1050.         cx->m_result = fmt;
  1051.     }
  1052.     goto done;
  1053.  loser:
  1054.     if (rv == SSM_SUCCESS)
  1055.         rv = SSM_FAILURE;
  1056.     SSMTextGen_UTF8StringClear(&cx->m_result);
  1057.  done:
  1058.     PR_FREEIF(temp);
  1059.     return rv;
  1060. }
  1061. void SSM_InitNLS(char *dataDirectory)
  1062. {
  1063.     SSMStatus rv = SSM_SUCCESS;
  1064.     /* Initialize hash table which contains keyword handlers. */
  1065.     rv = SSM_KeywordHandlerInitialize();
  1066.     if (rv != SSM_SUCCESS)
  1067.         goto loser;
  1068.     /* Create keyword handlers */
  1069.     SSM_RegisterKeywordHandler("_certlist", SSM_CertListKeywordHandler);
  1070.     SSM_RegisterKeywordHandler("_server_cert_info", 
  1071.                                SSM_ServerCertKeywordHandler);
  1072.     SSM_RegisterKeywordHandler("view_cert_info", 
  1073.                                SSM_ViewCertInfoKeywordHandler);
  1074.     SSM_RegisterKeywordHandler("tokenList", SSMTokenUI_KeywordHandler);
  1075.     SSM_RegisterKeywordHandler("_client_auth_certList", 
  1076.        SSM_ClientAuthCertListKeywordHandler);
  1077.     SSM_RegisterKeywordHandler("_get_current_time", 
  1078.                                SSM_CurrentTimeKeywordHandler);
  1079.     SSM_RegisterKeywordHandler("_server_cert_domain_info",
  1080.                                SSM_ServerAuthDomainNameKeywordHandler);
  1081.     SSM_RegisterKeywordHandler("_verify_server_cert",
  1082.                                SSM_VerifyServerCertKeywordHandler);
  1083.     SSM_RegisterKeywordHandler("_fipsmode", SSM_PKCS11FIPSModeKeywordHandler);
  1084.     SSM_RegisterKeywordHandler("_pk11modules",
  1085.                                SSM_PKCS11ModulesKeywordHandler);
  1086.     SSM_RegisterKeywordHandler("_pk11slots",
  1087.                                SSM_PKCS11SlotsKeywordHandler);
  1088.     SSM_RegisterKeywordHandler("_signtext_certList",
  1089.                     SSM_SignTextCertListKeywordHandler);
  1090.     SSM_RegisterKeywordHandler("_ca_cert_info", SSM_CACertKeywordHandler);
  1091.     SSM_RegisterKeywordHandler("_ca_policy_info", SSM_CAPolicyKeywordHandler);
  1092.     SSM_RegisterKeywordHandler("verify_cert", SSM_VerifyCertKeywordHandler);
  1093.     SSM_RegisterKeywordHandler("choose_list", SSM_SelectCertKeywordHandler);
  1094.     SSM_RegisterKeywordHandler("edit_cert", SSM_EditCertKeywordHandler);
  1095.     SSM_RegisterKeywordHandler("sa_selected_item", SSMSecurityAdvisorContext_sa_selected_item);
  1096.     SSM_RegisterKeywordHandler("set_or_reset_password", SSM_ReSetPasswordKeywordHandler);
  1097.     SSM_RegisterKeywordHandler("show_result", SSM_ShowFollowupKeywordHandler); 
  1098.     SSM_RegisterKeywordHandler("get_pref_list", SSMSecurityAdvisorContext_GetPrefListKeywordHandler);
  1099.     SSM_RegisterKeywordHandler("_ocsp_options", 
  1100.                                SSM_OCSPOptionsKeywordHandler);
  1101.     SSM_RegisterKeywordHandler("_default_ocsp_responder",
  1102.                                SSM_OCSPDefaultResponderKeywordHandler);
  1103.     SSM_RegisterKeywordHandler("password_lifetime_pref", SSM_PasswordPrefKeywordHandler);
  1104.     SSM_RegisterKeywordHandler("delete_cert_help", SSM_DeleteCertHelpKeywordHandler);
  1105.     SSM_RegisterKeywordHandler("delete_cert_warning", SSM_DeleteCertWarnKeywordHandler);
  1106.     SSM_RegisterKeywordHandler("get_new_cert_url", SSM_ObtainNewCertSite);
  1107.     SSM_RegisterKeywordHandler("ldap_server_list", SSM_LDAPServerListKeywordHandler);
  1108. #if 0
  1109.     SSM_RegisterKeywordHandler("_java_principals", SSM_JavaPrincipalsKeywordHandler);
  1110. #endif
  1111. #if 0
  1112.     SSM_RegisterKeywordHandler("testwrap", wrap_test);
  1113.     SSM_RegisterKeywordHandler("hello", SSM_HelloKeywordHandler);
  1114. #endif
  1115.     SSM_RegisterKeywordHandler("free_target", 
  1116.                                SSM_FreeTarget);
  1117.     SSM_RegisterKeywordHandler("pkcs12_incompatibility_warning",
  1118.                                SSM_WarnPKCS12Incompatibility);
  1119.     SSM_RegisterKeywordHandler("pass_var",
  1120.                                SSM_PassVariable);
  1121.     SSM_RegisterKeywordHandler("_ocsp_responder_list",
  1122.                                SSM_OCSPResponderList);
  1123.     SSM_RegisterKeywordHandler("_renewal_cert_info",
  1124.                                SSM_RenewalCertInfoHandler);
  1125.     SSM_RegisterKeywordHandler("_emailAddresses",
  1126.                                SSM_FillTextWithEmails);
  1127.     SSM_RegisterKeywordHandler("_certIssuerWindowName",
  1128.                                SSM_MakeUniqueNameForIssuerWindow);
  1129.     SSM_RegisterKeywordHandler("_windowOffset",
  1130.                                SSM_GetWindowOffset);
  1131.     SSM_RegisterKeywordHandler("_crlButton",
  1132.                                SSM_DisplayCRLButton);
  1133.     SSM_RegisterKeywordHandler("_crlList",
  1134.                                SSM_ListCRLs);
  1135.     SSM_RegisterKeywordHandler("_smime_tab",
  1136.                                SSM_LayoutSMIMETab);
  1137.     SSM_RegisterKeywordHandler("_java_js_tab",
  1138.                                SSM_LayoutJavaJSTab);
  1139.     SSM_RegisterKeywordHandler("_addOthersCerts",
  1140.                                SSM_LayoutOthersTab);
  1141. #if 0
  1142.     TestNLS();
  1143. #endif
  1144.     return;
  1145.  loser:
  1146.     SSM_DEBUG("NLS initialization failed. UI will be broken.n");
  1147.     /* Run our little test. */
  1148.     /*SSM_TestNLS();*/
  1149. }
  1150. SSMStatus
  1151. SSM_PassVariable(SSMTextGenContext *cx)
  1152. {
  1153.     SSMStatus rv;
  1154.     char *variable, *value;
  1155.     variable = (char *) SSM_At(cx->m_params, 0);
  1156.     rv = SSM_HTTPParamValue(cx->m_request, variable, &value);
  1157.     PR_FREEIF(cx->m_result);
  1158.     if (rv == SSM_SUCCESS) {
  1159.         cx->m_result = PR_smprintf("&%s=%s", variable, value);
  1160.     } else {
  1161.         cx->m_result = PL_strdup("");
  1162.     }
  1163.     return SSM_SUCCESS;
  1164. }