joes        2003/01/20 18:36:28

  Added:       src      apreq_cookie.h apreq_cookie.c apreq_env.h
  Log:
  Import rough cookie API.
  
  Revision  Changes    Path
  1.1                  httpd-apreq-2/src/apreq_cookie.h
  
  Index: apreq_cookie.h
  ===================================================================
  #ifndef APREQ_COOKIE_H
  #define APREQ_COOKIE_H
  
  #include "apreq_tables.h"
  
  #ifdef  __cplusplus
  extern "C" {
  #endif 
  
  typedef struct apreq_table_t apreq_jar_t;
  
  typedef enum { NETSCAPE, RFC } apreq_cookie_version_t;
  
  #define APREQ_COOKIE_DEFAULT_VERSION       NETSCAPE
  
  typedef struct apreq_cookie_t {
  
      apreq_cookie_version_t version;
  
      char        *path;
      char        *domain;
      char        *port;
      unsigned     secure;
  
      char        *comment;
      char        *commentURL;
  
      union { 
          long  max_age; 
          const char *expires; 
      } time;
  
      void        *ctx;
  
      apreq_value_t v;           /* "raw" value (extended struct) */
  } apreq_cookie_t;
  
  
  #define apreq_value_to_cookie(ptr) apreq_attr_to_type(apreq_cookie_t, v, ptr)
  #define apreq_cookie_name(c)  ((c)->v.name)
  #define apreq_cookie_value(c) ((c)->v.data)
  
  /**
   * Returns the number of cookies in the jar.
   *
   * @param jar The cookie jar.
   */
  
  int apreq_jar_items(apreq_jar_t *jar);
  #define apreq_jar_items(j) apreq_table_nelts(j)
  
  /**
   * Fetches a cookie from the jar
   *
   * @param jar The cookie jar.
   * @param i   The index of the desired cookie.
   */
  
  apreq_cookie_t *apreq_jar_get(apreq_jar_t *jar, char *name);
  #define apreq_jar_get(j,k) apreq_value_to_cookie(apreq_char_to_value( \
                                apreq_table_get(j,k)))
  
  /**
   * Adds a cookie by pushing it to the bottom of the jar.
   *
   * @param jar The cookie jar.
   * @param c The cookie to add.
   */
  
  void apreq_jar_add(apreq_jar_t *jar, apreq_cookie_t *c);
  #define apreq_jar_add(jar,c)  apreq_table_add(jar, &(c)->v)
  
  /**
   * Parse the incoming "Cookie:" headers into a cookie jar.
   * 
   * @param r The current request_rec.
   * @param data  Optional header string to use in place of r->headers_in.
   * @remark The current source assumes the client will fold all cookies
   * into a single "Cookie:" header.  This may assumption may be removed
   * in a future release.
   */
  
  apreq_jar_t *apreq_jar_parse(void *ctx, const char *data);
  
  /**
   * Returns a new cookie, made from the argument list.
   * Valid argument strings, passed in "attr","value" pairs, are:
   * name, value, version, expires/max-len, domain, port, path,
   * comment, commentURL, secure.
   *
   * @param r The current request.
   */
  apreq_cookie_t *apreq_cookie_make(void *ctx, const apreq_cookie_version_t v,
                                    const char *name, const apr_ssize_t nlen, 
                                    const char *value, const apr_ssize_t vlen);
  
  
  APREQ_DECLARE(apr_status_t) apreq_cookie_attr(apreq_cookie_t *c, 
                                                char *attr, char *val);
  
  /**
   * Returns a string (allocated from the cookie's pool) that
   * represents the cookie as it would appear in a valid "Set-Cookie*" header.
   *
   * @param c The cookie.
   */
  APREQ_DECLARE(const char*) apreq_cookie_as_string(const apreq_cookie_t *c,
                                                    apr_pool_t *p);
  APREQ_DECLARE(int) apreq_cookie_serialize(const apreq_cookie_t *c, 
                                            char *buf, apr_size_t len);
  
  /**
   * Get/set the "expires" string.  For NETSCAPE cookies, this returns 
   * the date (properly formatted) when the cookie is to expire.
   * For RFC cookies, this function returns NULL.
   * 
   * @param c The cookie.
   * @param time_str If NULL, return the current expiry date. Otherwise
   * replace with this value instead.  The time_str should be in a format
   * that apreq_atod() can understand, namely /[+-]?\d+\s*[YMDhms]/.
   */
  APREQ_DECLARE(void) apreq_cookie_expires(apreq_cookie_t *c, 
                                           const char *time_str);
  
  /**
   * Add the cookie to the outgoing "Set-Cookie" headers.
   *
   * @param c The cookie.
   */
  apr_status_t apreq_cookie_bake(apreq_cookie_t *c);
  
  /* XXX: how about baking whole cookie jars, too ??? */
  
  /**
   * Add the cookie to the outgoing "Set-Cookie2" headers.
   *
   * @param c The cookie.
   */
  apr_status_t apreq_cookie_bake2(apreq_cookie_t *c);
  
  #ifdef __cplusplus
   }
  #endif
  
  #endif /*APREQ_COOKIE_H*/
  
  
  
  
  
  1.1                  httpd-apreq-2/src/apreq_cookie.c
  
  Index: apreq_cookie.c
  ===================================================================
  /* ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2000 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Apache" and "Apache Software Foundation" must
   *    not be used to endorse or promote products derived from this
   *    software without prior written permission. For written
   *    permission, please contact [EMAIL PROTECTED]
   *
   * 5. Products derived from this software may not be called "Apache",
   *    nor may "Apache" appear in their name, without prior written
   *    permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * Portions of this software are based upon public domain software
   * originally written at the National Center for Supercomputing Applications,
   * University of Illinois, Urbana-Champaign.
   */
  
  #include "apreq_cookie.h"
  #include "apreq_env.h"
  
  APREQ_DECLARE(int) (apreq_jar_items)(apreq_jar_t *jar)
  {
      return apreq_jar_items(jar);
  }
  
  APREQ_DECLARE(apreq_cookie_t *) (apreq_jar_get)(apreq_jar_t *jar, 
                                                  char *name)
  {
      return apreq_jar_get(jar,name);
  }
  
  APREQ_DECLARE(void) (apreq_jar_add)(apreq_jar_t *jar, 
                                      apreq_cookie_t *c)
  {
      apreq_jar_add(jar,c);
  }
  
  APREQ_DECLARE(void) apreq_cookie_expires(apreq_cookie_t *c, 
                                           const char *time_str)
  {
      if ( c->version == NETSCAPE )
          c->time.expires = apreq_expires(apreq_env_pool(c->ctx), 
                                          time_str,
                                          APREQ_EXPIRES_COOKIE);
      else
          c->time.max_age = apreq_atol(time_str);
  }
  
  APREQ_DECLARE(apr_status_t) apreq_cookie_attr(apreq_cookie_t *c, 
                                                char *attr, char *val)
  {
      if ( attr[0] ==  '-' || attr[0] == '$' )
          ++attr;
  
      switch (apr_tolower(*attr)) {
  
      case 'n': /* name */
          c->v.name = val;
          return APR_SUCCESS;
  
      case 'v': /* version */
          c->version = *val - '0';
          return APR_SUCCESS;
  
      case 'e': case 'm': /* expires, max-age */
          apreq_cookie_expires(c, val);
          return APR_SUCCESS;
  
      case 'd':
          c->domain = val;
          return APR_SUCCESS;
  
      case 'p':
          if (strcasecmp("port", attr)==0) {
              c->port = val;
              return APR_SUCCESS;
          }
          else if (strcasecmp("path", attr)==0) {
              c->path = val;
              return APR_SUCCESS;
          }
          break;
  
      case 'c':
          if (strcasecmp("comment", attr)==0) {
              c->comment = val;
              return APR_SUCCESS;
          } 
          else if (strcasecmp("commentURL", attr)==0) {
              c->commentURL = val;
              return APR_SUCCESS;
          }
          break;
  
      case 's':
          c->secure = ( strcasecmp(val,"off")!=0 && *val != '0' );
          return APR_SUCCESS;
      };
  
      apreq_warn(c->ctx, "unknown cookie attribute: `%s' => `%s'", 
                 attr, val);
  
      return APR_EGENERAL;
  }
  
  APREQ_DECLARE(apreq_cookie_t *) apreq_cookie_make(void *ctx, 
                                    apreq_cookie_version_t version,
                                    const char *name, const apr_ssize_t nlen,
                                    const char *value, const apr_ssize_t vlen)
  {
      apr_pool_t *p = apreq_env_pool(ctx);
      apreq_cookie_t *c = apr_palloc(p, vlen + sizeof *c);
      apreq_value_t *v = &c->v;
  
      c->ctx = ctx;
      v->size = vlen;
      v->name = apr_pstrmemdup(p, name, nlen);
      memcpy(v->data, value, vlen);
      v->data[vlen] = 0;
      
      c->version = version;
  
      /* session cookie is the default */
  
      if (c->version == NETSCAPE)
          c->time.expires = NULL;
      else
          c->time.max_age = -1;
  
      c->path = NULL;
      c->domain = NULL;
      c->port = NULL;
      c->secure = 0; 
  
      c->comment = NULL;
      c->commentURL = NULL;
  
      return c;
  }
  
  static APR_INLINE apr_status_t get_pair(const char **data,
                                          const char **n, apr_ssize_t *nlen,
                                          const char **v, apr_ssize_t *vlen)
  {
      const char *d = *data;
      unsigned char in_quotes = 0;
  
      *n = d;
  
      while( *d != '=' && !apr_isspace(*d) )
          if (*d++ == 0)  /*error: no '=' sign */
              return APR_EGENERAL;
  
      *nlen = d - *n;
  
      do ++d; while ( *d == '=' || apr_isspace(*d) );
  
      *v = d;
  
      for (;;++d) {
          switch (*d) {
  
          case ';': case ',':
              if (in_quotes)
                  break;
              /* else fall through */
          case 0:
              goto pair_result;
  
          case '\\':
              if (d[1])
                  ++d;
              break;
  
          case '"':
              in_quotes = ! in_quotes;
          }
      }
  
   pair_result:
  
      *data = d;
      *vlen = d - *v;
  
      /* shouldn't still be in_quotes */
  
      return in_quotes ? APR_EGENERAL : APR_SUCCESS;
  }
  
  APREQ_DECLARE(apreq_jar_t *) apreq_jar_parse(void *ctx, 
                                               const char *d)
  {
      apr_pool_t *p = apreq_env_pool(ctx);
  
      apreq_cookie_version_t version;
      apreq_jar_t *j = NULL;
      apreq_cookie_t *c;
  
      const char *name, *value; 
      apr_ssize_t nlen, vlen;
  
  
      /* initialize jar */
      
      if (d == NULL) {
          /* use the environment's cookie data */
  
          /* fetch ctx->jar (cached jar) */
          if ( apreq_env_jar(ctx, &j) == APR_SUCCESS && j != NULL)
              return j;
          
          d = apreq_env_cookie(ctx);
          j = apreq_table_make(apreq_env_pool(ctx), APREQ_DEFAULT_NELTS);
  
          /* XXX: potential race condition here 
             between env_jar fetch and env_jar set.  */
  
          apreq_env_jar(ctx,&j);      /* set (cache) ctx->jar */
  
          if (d == NULL)
              return j;
      }
      else {
          j = apreq_table_make(p, APREQ_DEFAULT_NELTS);
      }
  
  #ifdef DEBUG
      apreq_log(APREQ_DEBUG(ctx), "parsing cookie data: %s",d);
  #endif
  
  
      /* parse d */
  
   parse_header:
  
      c = NULL;
      version = NETSCAPE;
  
      while (apr_isspace(*d))
          ++d;
  
      /* XXX cheat: assume "$..." => "$Version" => RFC Cookie header */
  
      if (*d == '$') { 
          version = RFC;
          while (*d && !apr_isspace(*d))
              ++d;
      }
  
      for (;;) {
  
          while (*d == ';' || apr_isspace(*d))
              ++d;
  
          switch (*d) {
  
          case 0:
              return j;
  
          case ',':
              ++d;
              goto parse_header;
  
          case '$':
              if ( c == NULL || version == NETSCAPE ||
                   get_pair(&d, &name, &nlen, &value, &vlen) != APR_SUCCESS )
                  return j; /* XXX: log error? */
  
              apreq_cookie_attr(c, apr_pstrmemdup(p, name, nlen),
                                   apr_pstrmemdup(p, value, vlen));
              break;
  
          default:
              if ( get_pair(&d, &name, &nlen, &value, &vlen) != APR_SUCCESS )
                  return j; /* XXX: log error? */
  
              c = apreq_cookie_make(ctx, version, name, nlen, value, vlen);
              apreq_jar_add(j, c);
  
          }
      }
  
      /* NOT REACHED */
      return j;
  }
  
  #define ADD_ATTR(name) do { strcpy(f,c->name ? "; " #name "=%s" : \
                                      "%.0s"); f+= strlen(f); } while (0)
  
  APREQ_DECLARE(int) apreq_cookie_serialize(const apreq_cookie_t *c, 
                                            char *buf, apr_size_t len)
  {
      /*  The format string must be large enough to accomodate all
       *  of the cookie attributes.  The current attributes sum to 
       *  ~80 characters (w/ 6 padding chars per attr), so anything 
       *  over that should number be fine.
       */
  
      char format[128] = "%s=%s";
      char *f = format + strlen(format);
  
      /* XXX protocol enforcement (for debugging, anyway) ??? */
  
      if (c->v.name == NULL)
          return -1;
      
      if (c->version == NETSCAPE) {
  
          ADD_ATTR(path);
          ADD_ATTR(domain);
  
          strcpy(f, c->time.expires ? "; expires=%s" : "%.0s");
          f += strlen(f);
  
          if (c->secure)
              strcpy(f, "; secure");
  
          return apr_snprintf(buf, len, format, c->v.name, c->v.data,
                             c->path, c->domain, c->time.expires);
      }
  
      /* c->version == RFC */
  
      ADD_ATTR(version);
      ADD_ATTR(path);
      ADD_ATTR(domain);
      ADD_ATTR(port);
      ADD_ATTR(comment);
      ADD_ATTR(commentURL);
  
      /* "%.0s" very hackish, but it should be safe.  max_age is
       * the last va_arg, and we're not actually printing it in 
       * the "%.0s" case. Should check the apr_snprintf implementation
       * just for certainty though.
       */
  
      strcpy(f, c->time.max_age >= 0 ? "; max_age=%ld" : "%.0s");
  
      f += strlen(f);
  
      if (c->secure)
          strcpy(f, "; secure");
  
      return apr_snprintf(buf, len, format, c->v.name, c->v.data,
                          c->version, c->path, c->domain, c->port,
                          c->comment, c->commentURL, c->time.max_age);
  }
  
  #undef ADD_ATTR
  
  APREQ_DECLARE(const char*) apreq_cookie_as_string(const apreq_cookie_t *c,
                                                    apr_pool_t *p)
  {
      char s[4096];
      int n = apreq_cookie_serialize(c, s, 4096);
  
      if ( n > 0 && n < 4096 )
          return apr_pstrmemdup(p, s, n);
      else
          return NULL;
  }
  
  APREQ_DECLARE(apr_status_t) apreq_cookie_bake(apreq_cookie_t *c)
  {
      char s[4096];
      int n = apreq_cookie_serialize(c, s, 4096);
  
      if (n > 0 && n < 4096)
          return apreq_env_set_cookie(c->ctx, s);
      else
          return APR_EGENERAL;     /* cookie is too large */
  }       
  
  APREQ_DECLARE(apr_status_t) apreq_cookie_bake2(apreq_cookie_t *c)
  {
      char s[4096];
      int n;
  
      if (c->version != RFC)
          return APREQ_ERROR;     /* wrong type */
  
      n = apreq_cookie_serialize(c, s, 4096);
  
      if (n > 0 && n < 4096)
          return apreq_env_set_cookie2(c->ctx, s);
      else
          return APR_EGENERAL;     /* cookie is too large */
  }
  
  
  
  
  
  #ifdef NEEDS_A_NEW_HOME
  
  void (apreq_cookie_push)(apreq_cookie_t *c, apreq_value_t *v)
  {
      apreq_cookie_push(c,v);
  }
  
  void apreq_cookie_addn(apreq_cookie_t *c, char *val, int len)
  {
      apreq_value_t *v = (apreq_value_t *)apr_array_push(c->values.data);
      v->data = val;
      v->size = len;
  }
  
  void apreq_cookie_add(apreq_cookie_t *c, char *val, int len)
  {
      apr_array_header_t *a = (apr_array_header_t *)c->values.data;
      apreq_cookie_addn(c, apr_pstrndup(a->pool, val, len), len);
  }
  
  APREQ_dDECODE(apreq_cookie_decode)
  {
      apr_array_header_t *a;
      apreq_value_t *v;
      apr_off_t len;
      char *word;
  
      word = apr_pstrdup(p,key);
      len  = apreq_unescape(word);
      if (len <= 0)       /* key size must be > 0 */
          return NULL;
  
      a = apr_array_make(p, APREQ_DEFAULT_NELTS, sizeof(apreq_value_t));
      v = (apreq_value_t *)apr_array_push(a);
      v->data = word;
      v->size = len;
  
      while ( *val && (word = apr_getword(p, &val, '&')) ) {
          len = apreq_unescape(word);
          if (len < 0)
              return NULL; /* bad escape data */
          v = (apreq_value_t *)apr_array_push(a);
          v->data = word;
          v->size = len;
      }
  
      return a;
  }
  
  static const char c2x_table[] = "0123456789abcdef";
  APREQ_dENCODE(apreq_cookie_encode)
  {
      apr_size_t len = 0;
      char *res, *data;
      int i;
  
      if (s == 0)
          return apr_pcalloc(p,1);
  
      for (i = 0; i < s; ++i)
          len += a[i].size;
  
      res = data = apr_palloc(p, 3*len + a->nelts);
  
      for (i = 0; i < s; ++i) {
          apr_size_t n = a[i].size;
          const unsigned char *s = (const unsigned char *)a[i].data;
  
          while (n--) {
              unsigned c = *s;
              if (apr_isalnum(c))
                  *data++ = c;
              else if (c == ' ') 
                  *data++ = '+';
              else {
  #if APR_CHARSET_EBCDIC
                  c = apr_xlate_conv_byte(ap_hdrs_to_ascii, (unsigned char)c);
  #endif
                  *data++ = '%';
                  *data++ = c2x_table[c >> 4];
                  *data++ = c2x_table[c & 0xf];
              }
              ++s;
          }
          *data++ = (i == 0) ? '=' : '&';
      }
  
      if (s == 1)
          data[0] = 0;    /* no value: name= */
      else
          data[-1] = 0;   /* replace final '&' with '\0' */
  
      return res;
  }
  
  #endif /* NEEDS_A_NEW_HOME */
  
  
  
  1.1                  httpd-apreq-2/src/apreq_env.h
  
  Index: apreq_env.h
  ===================================================================
  #ifndef APREQ_ENV_H
  #define APREQ_ENV_H
  
  #include "apreq_cookie.h"
  
  /*
  #include "apreq_request.h"
  */
  
  #ifdef  __cplusplus
   extern "C" {
  #endif 
  
  /* XXX: Per-process initialization */
  
  struct apreq_request_env {
      int foo;
  /*
      apr_status_t (*init)(void *ctx, apreq_parser_cfg_t *cfg);
      apreq_request_t *(*make)(void *ctx);
      apr_status_t (*parse)(apreq_request_t *req);
  */
  };
  
  struct apreq_cookie_env {
      const char *(*in)(void *ctx);
      const char *(*in2)(void *ctx);
  
      apr_status_t (*jar)(void *ctx, apreq_jar_t **j);
      apr_status_t (*out)(void *ctx, const char *c);
      apr_status_t (*out2)(void *ctx, const char *c);
  };
  
  extern const struct apreq_env {
      struct apreq_request_env r;
      struct apreq_cookie_env  c;
  
      /* uri components (cookies need to know the host/domain and path) */
      const char *(*uri)(void *ctx);
      const char *(*uri_path)(void *ctx);
  
      apr_pool_t *(*pool)(void *ctx);
      void (*log)(const char *file, int line, int level, 
                  apr_status_t status, void *ctx, const char *fmt, ...);
  } APREQ_ENV;
  
  APREQ_DECLARE(void) apreq_log(const char *file, int line, int level, 
                                apr_status_t status, void *ctx, const char 
*fmt, ...);
  
  APREQ_DECLARE(apr_status_t) apreq_env_jar(void *ctx, apreq_jar_t **j);
  APREQ_DECLARE(const char *) apreq_env_cookie(void *ctx);
  APREQ_DECLARE(const char *) apreq_env_cookie2(void *ctx);
  APREQ_DECLARE(apr_status_t) apreq_env_set_cookie(void *ctx, const char *s);
  APREQ_DECLARE(apr_status_t) apreq_env_set_cookie2(void *ctx, const char *s);
  APREQ_DECLARE(apr_status_t) apreq_env_cookie_jar(void *ctx, 
                                                   apreq_jar_t **j);
  
  #define apreq_env_pool(r) APREQ_ENV.pool(r)
  #define apreq_env_cookie(r) APREQ_ENV.c.in(r)
  #define apreq_env_cookie2(r) APREQ_ENV.c.in2(r)
  #define apreq_env_set_cookie(r,s) APREQ_ENV.c.out(r,s)
  #define apreq_env_set_cookie2(r,s) APREQ_ENV.c.out2(r,s)
  
  #define AP_LOG_MARK      __FILE__,__LINE__
  
  #define AP_LOG_EMERG     0       /* system is unusable */
  #define AP_LOG_ALERT     1       /* action must be taken immediately */
  #define AP_LOG_CRIT      2       /* critical conditions */
  #define AP_LOG_ERR       3       /* error conditions */
  #define AP_LOG_WARNING   4       /* warning conditions */
  #define AP_LOG_NOTICE    5       /* normal but significant condition */
  #define AP_LOG_INFO      6       /* informational */
  #define AP_LOG_DEBUG     7       /* debug-level messages */
  
  
  #define APREQ_LOG_DEBUG(r)  AP_LOG_MARK, AP_LOG_DEBUG,0,(r)
  #define APREQ_LOG_ERROR(r)  AP_LOG_MARK, AP_LOG_ERR,0,(r)
  #define APREQ_LOG_WARN(r)   AP_LOG_MARK, AP_LOG_WARNING,0,(r)
  
  APREQ_DECLARE(void) apreq_warn(void *ctx, const char *fmt, ...);
  
  #ifdef __cplusplus
   }
  #endif
  
  #endif
  
  
  

Reply via email to