wifidog HTTP Lighttpd1.4.20源码分析之buffer.c(h)--------字符串内存管理(2)

一些工具性的函数:
1、int LI_ltostr(char *buf, long val);
将长整型val转化成字符串,并存入buf中。

Code
int LI_ltostr(char *buf, long val) 
{
    char swap;
    char *end;
    int len = 1;
    //val为负数,加一个负号,然后转化成正数
    if (val < 0) 
    {
        len++;
        *(buf++) = '-';
        val = -val;
    }
    end = buf;
    /*
          这里val必须设置为大于9,并在循环外在做一次转换
            (*(end) = '0' + val)!
             因为如果val设置为大于0,当val为0时,将不进入循环,那么循
           环后面直接在buf中
     * 追加'\0'。这样0就被转化成了空串!!
     * 这里val转化后的字符串是逆序存放在buf中的,在后面要反转,
     * 以得到正确的顺序。
     */
    while (val > 9) 
    {
        *(end++) = '0' + (val % 10);
        val = val / 10;
    }
    *(end) = '0' + val;
    *(end + 1) = '\0';
    len += end - buf;
    //将字符串反转,
    while (buf < end) 
    {
        swap = *end;
        *end = *buf;
        *buf = swap;
        buf++;
        end--;
    }
    return len;
}

2、char hex2int(unsigned char c);
  converts hex char (0-9, A-Z, a-z) to decimal.returns 0xFF on
invalid input. 将16进制的字符转化成对应的数字,非法输入返回0xFF。忽略c的大小写。
3、char int2hex(char i);
  将i转化成对应的16进制形式
4、int light_isdigit(int c);
  c是否是数字。0-9
5、int light_isxdigit(int c);
  c是否是十六进制的数字0-9 a-f
6、int light_isalpha(int c);
  c是否是字母。
7、int light_isalnum(int c);
  c是否是字母或数字。

  以上几个函数在处理大小写的时候,都使用了c |= 32;将c转换成小写的形式,无论c原来是大写还是小写。原理在函数buffer_caseless_compare中讲解过。
8、int buffer_to_lower(buffer * b);
  将b中的数据转换成小写。
9、int buffer_to_upper(buffer * b);
  将b中的数据转换成大写。
  以上两个函数没有在buffer.c中定义。

  下面的几个宏定义一些方便的操作。

Code
#define BUFFER_APPEND_STRING_CONST(x, y) \
    buffer_append_string_len(x, y, sizeof(y) - 1)

#define BUFFER_COPY_STRING_CONST(x, y) \
    buffer_copy_string_len(x, y, sizeof(y) - 1)
//在buffer中追加一个‘/’,如果最后一个字符是‘/’,则不追加。
#define BUFFER_APPEND_SLASH(x) \
    if (x->used > 1 && x->ptr[x->used - 2] != '/') 
{ BUFFER_APPEND_STRING_CONST(x, "/"); }

#define CONST_STR_LEN(x)  x, x ? sizeof(x) - 1 : 0
#define CONST_BUF_LEN(x)  x->ptr, x->used ? x->used - 1 : 0

#define SEGFAULT() 
do { 
fprintf(stderr, "%s.%d: aborted\n", __FILE__, __LINE__); abort(); 
} while(0)
#define

以下的函数操作涉及到编码问题。在lighttpd中,使用到的编码有六种。具体的类型定义在下面的结构体中。

Code
typedef enum 
{
    ENCODING_UNSET,
    ENCODING_REL_URI,                /* for coding a rel-uri (/withspace/and%percent) nicely as part of a href */
    ENCODING_REL_URI_PART,        /* same as ENC_REL_URL plus coding / too as %2F */
    ENCODING_HTML,                /* & becomes &amp; and so on */
    ENCODING_MINIMAL_XML,        /* minimal encoding for xml */
    ENCODING_HEX,                    /* encode string as hex */
    ENCODING_HTTP_HEADER            /* encode \n with \t\n */

于此对应的 buffer.c 源文件中给出了
const char encoded_chars_rel_uri_part[]
const char encoded_chars_rel_uri[]
const char encoded_chars_html[]
const char encoded_chars_minimal_xml[]
const char encoded_chars_hex[]
const char encoded_chars_http_header[]
六个标志数组,数组中值为 1 的元素表示对应下标值大小的字符需要被编码转换,否则不需要转换可以直接使用(即编码前和编码后是同一个值)。例如对于 encoded_chars_rel_uri数组,encoded_chars_rel_uri[32]值为1表示该对应的字符(32对应的是空格,因为空格的十进制值为 32)需要被 uri 编码(被编码为“%20”),而对于值为 0的 encoded_chars_rel_uri[48],其对应的字符就不需要编码(48 对应的是字符‘0’,而字符‘0’并不是特殊字符,因此不用编码。)。对于具体的编码方式,请查阅相关资料。
1、int buffer_append_string_encoded(buffer * b, const char *s,size_t s_len, buffer_encoding_t encoding);
  将字符串s以指定的编码方式存入b中。encoding指定编码的方式。

/**
    * 将字符串s以指定的编码方式存入b中。
    * encoding指定编码的方式。
    */
   int buffer_append_string_encoded(buffer *b, const char *s, size_t s_len, buffer_encoding_t encoding) 
   {
       unsigned char *ds, *d;
       size_t d_len, ndx;
       const char *map = NULL;

      if (!s || !b) return -1;

      //b中存放的不是亦'\0'结尾的字符串。报错。
      if (b->ptr[b->used - 1] != '\0') 
      {
          SEGFAULT();
      }

      if (s_len == 0) return 0;

      //根据编码方式,选择对应的编码数组,就是上面的那六个数组。
      switch(encoding) {
      case ENCODING_REL_URI:
          map = encoded_chars_rel_uri;
          break;
      case ENCODING_REL_URI_PART:
          map = encoded_chars_rel_uri_part;
          break;
      case ENCODING_HTML:
          map = encoded_chars_html;
          break;
      case ENCODING_MINIMAL_XML:
          map = encoded_chars_minimal_xml;
          break;
      case ENCODING_HEX:
          map = encoded_chars_hex;
          break;
      case ENCODING_HTTP_HEADER:
          map = encoded_chars_http_header;
          break;
      case ENCODING_UNSET:
          break;
      }

      assert(map != NULL);

      /* 
       * count to-be-encoded-characters 
       * 计算经过编码转换后的字符串s的长度。
       * 不同的编码方式,对与不同的字符,其转换后的字符长度不同。
       */
      for (ds = (unsigned char *)s, d_len = 0, ndx = 0; ndx < s_len; ds++, ndx++) 
      {
         if (map[*ds]) 
          {
              switch(encoding) 
              {
              case ENCODING_REL_URI:
              case ENCODING_REL_URI_PART:
                  d_len += 3;
                  break;
             case ENCODING_HTML:
             case ENCODING_MINIMAL_XML:
                  d_len += 6;
                  break;
              case ENCODING_HTTP_HEADER:
              case ENCODING_HEX:
                  d_len += 2;
                  break;
              case ENCODING_UNSET:
                  break;
              }
          } 
          else //字符不需要转换 
          {
              d_len ++;
          }
      }

      buffer_prepare_append(b, d_len);

      //下面这个循环就是开始做实际的编码转换工作。
     //ds指向字符串s中的字符。d指向b的数据去存放字符的位置。
      for (ds = (unsigned char *)s, d = (unsigned char *)b->ptr + b->used - 1, d_len = 0, ndx = 0; ndx < s_len; ds++, ndx++) 
      {
         if (map[*ds]) 
          {
              switch(encoding) 
              {
              case ENCODING_REL_URI:             //此编码不需要转换
              case ENCODING_REL_URI_PART:     //将字符ASCII码转化成其对应的十六进制的形式,并在前面加上'%'
                  d[d_len++] = '%';
                  d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F];
                  d[d_len++] = hex_chars[(*ds) & 0x0F];
                  break;
              case ENCODING_HTML:             //不需要转换
              case ENCODING_MINIMAL_XML:         //也是转换成ASCII编码的十六进制形式,前面要加上"&#x",尾随一个';'
                  d[d_len++] = '&';
                  d[d_len++] = '#';
                 d[d_len++] = 'x';
                 d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F];
                 d[d_len++] = hex_chars[(*ds) & 0x0F];
                 d[d_len++] = ';';
                 break;
             case ENCODING_HEX:                 //直接转换成ASCII码对应的十六进制。
                 d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F];
                 d[d_len++] = hex_chars[(*ds) & 0x0F];
                 break;
             case ENCODING_HTTP_HEADER:        //这个处理HTTP头中的换行,统一转换成'\n\t'
                 d[d_len++] = *ds;
                 d[d_len++] = '\t';
                 break;
             case ENCODING_UNSET:
                 break;
             }
        } 
         else 
         {
             d[d_len++] = *ds;
         }
     }     
     /* 
      * terminate buffer and calculate new length 
      * 在新字符串尾部加上一个'\0' 
     */
     b->ptr[b->used + d_len - 1] = '\0';     
     b->used += d_len;         //新的字符串长度。
     return 0;
 }

2、static int buffer_urldecode_internal(buffer *url, int is_query)
  将rul中存放的特殊编码的字符转换成正常的字符。这里的编码是指上面六种编码中的ENCODING_REL_RUL_PART.

/* 
   * decodes url-special-chars inplace.
   * replaces non-printable characters with '_'
   * 将rul中存放的特殊编码的字符转换成正常的字符。这里的编码是指上面六种编码中的ENCODING_REL_RUL_PART.
   * 也就是把ASCII码的16进制表示,转换成正常的ASCII码。转换后的结果直接存放在url中。
   *
   * 这个is_query参数的作用仅仅控制是否将字符串中的'+'转换成空格' '。
   */

 static int buffer_urldecode_internal(buffer *url, int is_query) 
 {
     unsigned char high, low;
     const char *src;
     char *dst;

     if (!url || !url->ptr) return -1;

     //源字符串和目的字符串是同一个串。
     src = (const char*) url->ptr;
     dst = (char*) url->ptr;

     while ((*src) != '\0') 
     {
         if (is_query && *src == '+') 
         {
             *dst = ' ';
         } 
         else if (*src == '%') 
         {
             *dst = '%';
             //将后面16进制表示的ASCII码转换成正常的ASCII码。
             high = hex2int(*(src + 1));          //高四位
             if (high != 0xFF)                   //0xFF表示转换出错。
             {
                 low = hex2int(*(src + 2));         //低四位
                 if (low != 0xFF) 
                 {
                     high = (high << 4) | low;      //合并
                     /* map control-characters out  判断是否是控制字符。*/
                    if (high < 32 || high == 127) 
                         high = '_';
                     *dst = high;
                     src += 2;     
                     //这个转换过程中,三个源字符转换成一个目的字符。
                    //虽然是一个字符串,但源字符串遍历的更快,不会冲突。
                 }
             }
         } 
         else 
         {
             *dst = *src;
         }

         dst++;
        src++;
     }

     *dst = '\0';     //新结尾。
     url->used = (dst - url->ptr) + 1;

     return 0;
 }

3、int buffer_path_simplify(buffer * dest, buffer * src);
  删除路径字符串中的"/../","//"和"/./",简化路径,并不是简单的删除。

/* Remove "/../", "//", "/./" parts from path.
    *
    * /blah/..         gets  /
    * /blah/../foo     gets  /foo
    * /abc/./xyz       gets  /abc/xyz
    * /abc//xyz        gets  /abc/xyz
    *
    * NOTE: src and dest can point to the same buffer, in which case,
    *       the operation is performed in-place.
   *
   * 删除路径字符串中的"/../","//"和"/./",简化路径,并不是简单的删除。
   * 对于"/../"在路径中相当与父目录,因此,实际的路径相当于删除"/../"和其前面的一个"/XX/".
   * 如: /home/test/../foo   ->   /home/foo
   * 而"//"和"/./"表示当前目录,简单的将其删去就可以了。
  * NOTE: 源缓冲src和目的缓冲可以指向同一个缓冲,在这种情况下,操作将源缓冲中的数据替换。
   */

  int buffer_path_simplify(buffer *dest, buffer *src)
  {
      int toklen;
      char c, pre1;
      char *start, *slash, *walk, *out;
      unsigned short pre;     //pre两个字节,char一个字节,pre中可以存放两个字符。

     if (src == NULL || src->ptr == NULL || dest == NULL)
          return -1;

      if (src == dest)
          buffer_prepare_append(dest, 1);
      else
          buffer_prepare_copy(dest, src->used + 1);

      walk  = src->ptr;
      start = dest->ptr;
      out   = dest->ptr;
      slash = dest->ptr;


  #if defined(__WIN32) || defined(__CYGWIN__)
      /* 
       * cygwin is treating \ and / the same, so we have to that too
       * cygwin中\和/相同。转化之。
       */

      for (walk = src->ptr; *walk; walk++) 
      {
          if (*walk == '\\') *walk = '/';
      }
      walk = src->ptr;
  #endif
      //过滤掉开始的空格。
      while (*walk == ' ') 
      {
          walk++;
      }

      pre1 = *(walk++);
      c    = *(walk++);
      pre  = pre1;
      if (pre1 != '/')  //路径不是以'/'开始,在目的路径中加上'/'
      {
          pre = ('/' << 8) | pre1; //将prel指向的字符存放在pre的高八位。
         *(out++) = '/';
      }
      *(out++) = pre1;

     if (pre1 == '\0')          //转换结束
      {
          dest->used = (out - start) + 1;
          return 0;
      }

      while (1) 
      {
          if (c == '/' || c == '\0') 
          {
              toklen = out - slash; //slash指向距离c指向的字符前面最近的一个'/'。
              if (toklen == 3 && pre == (('.' << 8) | '.')) // "/../"
             {
                 out = slash;
                 if (out > start) //删除"/../"前面的一层目录"/XX/".
                  {
                      out--;
                     while (out > start && *out != '/') 
                      {
                          out--;
                      }
                  }

                  if (c == '\0')
                      out++;
              } 
              else if (toklen == 1 || pre == (('/' << 8) | '.')) // "//" 和 "/./"
              {
                  out = slash;
                 if (c == '\0')
                      out++;
              }

             slash = out;
         }

         if (c == '\0')
             break;

         pre1 = c;
         pre  = (pre << 8) | pre1; //pre始终存放的是prel指向的字符和其前一个字符。
         c    = *walk;
         *out = pre1;

         out++;
         walk++;
     }

     *out = '\0';
     dest->used = (out - start) + 1;

     return 0;
 }

总得来说,buffer的内容比较简单,其他的函数读者可以自行查看。

本文章由 http://www.wifidog.pro/2015/04/15/wifidog%E8%AE%A4%E8%AF%81lighttpd%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86-2.html 整理编辑,转载请注明出处

标签: wifidog认证, wifidog源码, wifidog分析