strlen/Memcpy_s/strncasecmp

strlen

声明:size_t strlen(const char *str)
举例:

#include <stdio.h>
#include <string.h>

int main ()
{
   char str[50];
   int len;

   strcpy(str, "This is runoob.com");

   len = strlen(str);
   printf("|%s| 的长度是 |%d|\n", str, len);
   
   return(0);
}
让我们编译并运行上面的程序,这将产生以下结果:

|This is runoob.com| 的长度是 |18|

Memcpy_s

void *dest,
size_t numberOfElements,
const void *src,
size_t count
);
第一个参数为目标内存地址,第二个参数为目标内存缓冲大小,第三个参数为源内存地址,第四个为源内存缓冲的大小。返回值是一个错误码。

为什么这个返回值是错误码呢?因为这个版本中加入了基本的错误检测。如果源缓冲大小为0,即count为0,函数返回0,什么也不做。此函数没有对目标指针为NULL的情况,不做检查,所以你自己要注意检查。如果指针有值,但是是无效值,函数也没办法检查是否是有效内存,只是会搜集这些信息,在程序崩溃时提供调试需要的信息。

strncasecmp

举例:

#include <string.h>
main(){
    char *a = "aBcDeF";
    char *b = "AbCdEf";
    if(!strncasecmp(a, b, 3))    // 注意此处存在符号“!”
    {
        printf("%s =%s\n", a, b);
    }
}

定义
int strncasecmp(const char *s1, const char *s2, size_t n);

描述
strncasecmp()用来比较参数s1 和s2 字符串前n个字符,比较时会自动忽略大小写的差异。
若参数s1 和s2 字符串相同则返回0。s1 若大于s2 则返回大于0 的值,s1 若小于s2 则返回小于0 的值。

static int build_isp_bin() { char name_buf[1024] = { 0 }; char* isp_buf = calloc(1024 * 1024 * 5, 1); char* tmp_p = isp_buf; char default_isp_name[DEFAULT_ISP_NAME_LEN] = { 0 }; int ext_fw_file_len = 0, ret = 0, i = 0; int isp_all_len = 0; int isp_valid_len = 0; get_partition_size(target_isp_partition, &isp_all_len, &isp_valid_len); if (isp_buf == NULL) return -1; memcpy(gBlockIspHeader.magicNumber, blockHdrMagicNumber, BLOCK_MAGIC_NUMBER_LEN); gBlockIspHeader.num = htons(gBlockIspHeader.num); memcpy(isp_buf, &gBlockIspHeader, sizeof(BLOCK_NEW_HEADER)); isp_buf += sizeof(BLOCK_NEW_HEADER); for (i = 0; i < gExtFwBlockNum; i++) { if (gExtFwBlock[i].add && gExtFwBlock[i].blockType == DEFINE_EX_PACK(CONTENT_TYPE_ISP_CONFIG)) { ret = packExtFw(&gExtFwBlock[i], isp_buf, bin_extfw_path); if (ret) { ERR("packExtFw is not good"); goto out_free_buf; } /* upboot 的 extFw 头部和文件长度累加,需要使用文件对齐后的长度 */ ext_fw_file_len += ALIGN(gExtFwBlock[i].fileInfo.fileSize + sizeof(EXT_FW_HEADER), 4); isp_buf += ALIGN(gExtFwBlock[i].fileInfo.fileSize + sizeof(EXT_FW_HEADER), 4); } else continue; } snprintf(name_buf, sizeof(name_buf), "%s/build_fw_dir/isp_block.bin", bin_extfw_path); writeToBinFile(isp_buf - ext_fw_file_len - sizeof(BLOCK_NEW_HEADER), ext_fw_file_len + sizeof(BLOCK_NEW_HEADER), name_buf); isp_buf = tmp_p; for (i = 0; i < gExtFwBlockNum; i++) { /* 将 default extFw 写入 radio 分区,即往 flash.bin 写入 default isp_config 固件共板时会有多个flash.bin,对应不同的default isp_config*/ if (0 == strncasecmp(gExtFwBlock[i].deviceName, DEFAULT_EXT_FW_NAME, strlen(DEFAULT_EXT_FW_NAME)) && gExtFwBlock[i].blockType == DEFINE_EX_PACK(CONTENT_TYPE_ISP_CONFIG)) { printf("write default isp\n"); if (sizeof(EXT_FW_HEADER) + gExtFwBlock[i].fileInfo.fileSize > isp_all_len) { DBG("isp sector is not enough to write isp file [size 0x%lx]", sizeof(EXT_FW_HEADER) + gExtFwBlock[i].fileInfo.fileSize); ret = -1; goto out_free_buf; } ret = packExtFw(&gExtFwBlock[i], isp_buf, bin_extfw_path); if (ret) { ERR("packExtFw default not good"); goto out_free_buf; } #ifdef FIRMWARE_SUPPORT_MULTI_PRODUCT_BY_SENSOR snprintf(default_isp_name, sizeof(default_isp_name), "%s_isp.bin", gExtFwBlock[i].deviceName); update_fw_multi_name(gExtFwBlock[i].default_isp_name); #else snprintf(default_isp_name, sizeof(default_isp_name), "isp.bin"); #endif isp_buf += gExtFwBlock[i].fileInfo.fileSize + sizeof(EXT_FW_HEADER); snprintf(name_buf, sizeof(name_buf), "%s/build_fw_dir/%s", bin_extfw_path, default_isp_name); writeToBinFile(isp_buf - gExtFwBlock[i].fileInfo.fileSize - sizeof(EXT_FW_HEADER), gExtFwBlock[i].fileInfo.fileSize + sizeof(EXT_FW_HEADER), name_buf); } } out_free_buf: free(tmp_p); return ret; } static int build_mcu_bin() { char name_buf[1024] = { 0 }; char* mcu_buf = calloc(1024 * 1024, 1); char* tmp_p = mcu_buf; int ext_fw_file_len = 0, ret = 0, i = 0; if (mcu_buf == NULL) return -1; memcpy(gBlockMcuHeader.magicNumber, blockHdrMagicNumber, BLOCK_MAGIC_NUMBER_LEN); gBlockMcuHeader.num = htons(gBlockMcuHeader.num); memcpy(mcu_buf, &gBlockMcuHeader, sizeof(BLOCK_NEW_HEADER)); mcu_buf += sizeof(BLOCK_NEW_HEADER); for (i = 0; i < gExtFwBlockNum; i++) { if (gExtFwBlock[i].add && gExtFwBlock[i].blockType == DEFINE_EX_PACK(CONTENT_TYPE_MCU_CONFIG)) { ret = packExtFw(&gExtFwBlock[i], mcu_buf, bin_extfw_path); if (ret) { ERR("packExtFw is not good"); goto out_free_buf; } /* upboot 的 extFw 头部和文件长度累加,需要使用文件对齐后的长度 */ ext_fw_file_len += ALIGN(gExtFwBlock[i].fileInfo.fileSize + sizeof(EXT_FW_HEADER), 4); mcu_buf += ALIGN(gExtFwBlock[i].fileInfo.fileSize + sizeof(EXT_FW_HEADER), 4); } else continue; } snprintf(name_buf, sizeof(name_buf), "%s/build_fw_dir/mcu_block.bin", bin_extfw_path); if (ext_fw_file_len > 0) writeToBinFile(mcu_buf - ext_fw_file_len - sizeof(BLOCK_NEW_HEADER), ext_fw_file_len + sizeof(BLOCK_NEW_HEADER), name_buf); out_free_buf: free(tmp_p); return ret; } static int build_lte_bin() { int ext_fw_file_len = 0, ret = 0, i = 0; char* lte_buf = calloc(1024 * 1024 * 80, 1); char name_buf[1024] = { 0 }; if (lte_buf == NULL) return -1; memcpy(gBlockLteHeader.magicNumber, blockHdrMagicNumber, BLOCK_MAGIC_NUMBER_LEN); gBlockLteHeader.num = htons(gBlockLteHeader.num); memcpy(lte_buf, &gBlockLteHeader, sizeof(BLOCK_NEW_HEADER)); lte_buf += sizeof(BLOCK_NEW_HEADER); for (i = 0; i < gExtFwBlockNum; i++) { if (gExtFwBlock[i].add && gExtFwBlock[i].blockType == DEFINE_EX_PACK(CONTENT_TYPE_LTE_CONFIG)) { ret = packExtFw(&gExtFwBlock[i], lte_buf, bin_extfw_path); if (ret) { ERR("packExtFw is not good"); goto out_free_buf; } /* upboot 的 extFw 头部和文件长度累加,需要使用文件对齐后的长度 */ #ifndef LTE_FW_QF_UPGRAD ext_fw_file_len += ALIGN(gExtFwBlock[i].fileInfo.fileSize + sizeof(EXT_FW_HEADER), 4); lte_buf += ALIGN(gExtFwBlock[i].fileInfo.fileSize + sizeof(EXT_FW_HEADER), 4); #else ext_fw_file_len += ALIGN(gExtFwBlock[i].fileInfo.fileSize + sizeof(EXT_FW_HEADER) + lte_ext_fw_header_expend_len, 4); lte_buf += ALIGN(gExtFwBlock[i].fileInfo.fileSize + sizeof(EXT_FW_HEADER) + lte_ext_fw_header_expend_len, 4); #endif } else continue; } snprintf(name_buf, sizeof(name_buf), "%s/build_fw_dir/lte_block.bin", bin_extfw_path); if (ext_fw_file_len > 0) { writeToBinFile(lte_buf - ext_fw_file_len - sizeof(BLOCK_NEW_HEADER), ext_fw_file_len + sizeof(BLOCK_NEW_HEADER), name_buf); } out_free_buf: free(lte_buf - ext_fw_file_len - sizeof(BLOCK_NEW_HEADER)); return ret; } 分析这三个函数的作用
10-15
该模块目前仅支持WS-UsernameToken认证,现在我要新增digest认证,目前已做的修改如下(onvif_proc_data_srv函数只修改了一半),请你补全剩下的修改(包括onvif_proc_data_srv函数以及其他需要修改的地方) #### 流程梳理 - 初始化认证状态:`digest_authenticated`=0,`wss_authenticated`=0。 - 检查Digest凭据: - 如果有,验证,成功则`digest_authenticated`=1;失败则返回401。 - 如果没有,继续。 - 解析SOAP消息(如果前面返回了,就无需解析)。 - 在解析SOAP之后,检查SOAP头中是否有`WSS`凭据: - 如果有,进行`WSS`验证(调用原有的`soap_usernametoken_auth`等函数): 成功:`wss_authenticated`=1 失败:构造`SOAP Fault`,设置上下文返回类型为`SOAP_FAULT`,然后返回错误。 - 如果没有,`wss_authenticated`保持0。 - 检查是否至少有一个认证通过:if (`digest_authenticated` || `wss_authenticated`) - 如果是,则继续后续处理(调用`soap_serve_request`)。 - 否则,返回401(带`WWW-Authenticate`头)。 **认证组合测试**: | 测试用例 | 预期结果 | | ------------------------ | -------------- | | 有效`Digest` + 无`WSS` | 200 OK | | 有效`Digest` + 有`效WSS` | 200 OK | | 有效`Digest` + 无效`WSS` | 400/SOAP Fault | | 无效`Digest` + 有效`WSS` | 401 | | 无效`Digest` + 无效`WSS` | 401 | | 无`Digest` + 有效`WSS` | 200 OK | | 无`Digest` + 无效`WSS` | 400/SOAP Fault | | 无`Digest` + 无`WSS` | 401 | #### 修改步骤 - 修改CONTEXT结构体,增加一些字段来存储Digest认证相关的信息。 - 对`http_parse.c`中解析HTTP头部的函数,增加对Authorization头的解析 - 实现Digest认证验证函数 - 生成nonce和管理nonce,比如设置nonce的过期时间,防止重放攻击 - 修改`onvif_proc_data_srv`函数(`onvif_srv.c`),在解析SOAP之前,先检查是否有Digest认证头并进行验证。 - 在401响应的HTTP报头添加`WWW-Authenticate`头。 #### HTTP解析流程 在`http_parser.c`中,HTTP报文的解析主要由`http_parser()`函数完成。该函数通过以下步骤将报文内容读入CONTEXT结构体: ##### 1. 初始化CONTEXT结构体 - 分配头部缓冲区(`context->head_buf`),大小为`HTTP_HEAD_BUF_SIZE`。 - 初始化CONTEXT中的各个字段,包括`head_end`(指向缓冲区当前写入位置)、`hbuf_data_end`(指向已接收数据的末尾)等。 - 设置套接字为非阻塞模式。 ##### 2. 解析HTTP头部 - 调用`http_parse_header(context)`函数解析HTTP头部。 - 读取请求行(第一行):调用`http_read_line`读取一行,然后通过`http_parse_method`解析HTTP方法(GET/POST等),`http_parse_url`解析URL(包括路径和查询参数),`http_parse_version`解析HTTP版本。 - 解析请求头字段:循环调用`http_read_line`读取每一行,直到遇到空行(表示头部结束)。每行按`key: value`格式解析,通过`http_parse_key`函数处理。该函数会调用注册的关键字解析器(如`http_parse_host`、`http_parse_content_type`等)将对应的值存入CONTEXT的字段中。 - 例如,Host字段的值会存入`context->host`。 ##### 3. 解析HTTP内容(Body) - 如果有内容(如POST请求),根据`Content-Length`或`Transfer-Encoding`处理: - 对于普通POST请求,调用`http_read_content`函数读取内容到`context->content_buf`。 - 对于文件上传(`multipart/form-data`),会处理`boundary`,并调整内容指针。 - 读取的内容会被存入`context->content_buf`,长度由`context->content_len`记录。 ##### 4. 后续处理 - 根据解析出的URL路径,确定服务组(如`ONVIF`或`HTTPD`),并调用对应的`data_srv`函数进行处理。 - 处理结果会被放入响应缓冲区,通过`http_make_response`或`http_make_file_response`发送。 #### **步骤一:修改CONTEXT结构体(httpd.h)** 在CONTEXT结构体中增加Digest认证相关的字段: ```c #define LEN_USERNAME 256 #define LEN_REALM 256 #define LEN_DIGEST_NONCE 256 #define LEN_URI 256 #define LEN_RESPONSE 256 #define LEN_ALGORITHM 64 #define LEN_QOP 64 #define LEN_NC 32 #define LEN_CNONCE 64 #define LEN_OPAQUE 256 typedef struct _CONTEXT { /* 现有字段... */ /* 新增Digest认证相关字段 */ BOOL digest_authenticated; /* Digest认证是否通过 */ BOOL wss_authenticated; /* Digest认证是否通过 */ char digest_username[LEN_USERNAME]; /* Digest认证用户名 */ char digest_realm[LEN_REALM]; /* Digest认证域 */ char digest_nonce[LEN_DIGEST_NONCE]; /* 服务器生成的nonce */ char digest_uri[LEN_URI]; /* 请求的URI */ char digest_response[LEN_RESPONSE]; /* 客户端计算的响应值 */ char digest_algorithm[LEN_ALGORITHM];/* 算法类型 */ char digest_qop[LEN_QOP]; /* 保护质量 */ char digest_nc[LEN_NC]; /* 随机数计数器 */ char digest_cnonce[LEN_CNONCE]; /* 客户端随机数 */ char digest_opaque[LEN_OPAQUE]; /* 服务器不透明数据 */ /* 其他现有字段... */ } CONTEXT; ``` #### 步骤二:解析Authorization头(`http_parser.c`) **添加Authorization头解析器** 在`http_parser_init()`函数中添加: ```c void http_parser_init() { /* 现有初始化代码... */ /* 添加Authorization头解析器 */ http_key_parser_add("Authorization", http_parse_authorization); /* ... */ } ``` **实现Authorization头解析函数** 参考`http`头格式: ```c GET/cgi-bin/checkout?a=b HTTP/1.1 Authorization: Digest username="Mufasa", realm="realm", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c0", uri="/xxxx/System/Register", response="6629fae49393a05397450978507c4ef1", algorithm=MD5, qop=auth, nc=00000001, cnonce="0a4f113b", /* opaque="5ccc069c403ebaf9f0171e9517f40e41" */ ``` 辅助函数:`trim_whitespace` - 去除键前后的空格,避免因空格导致匹配失败。 - 十六进制字符转数值 ```c /* 移除字符串两端的空格 */ void trim_whitespace(char *str) { char *end; /* 去除前导空格 */ while (isspace((unsigned char)*str)) str++; /* 全空格字符串处理 */ if (*str == 0) return; /* 去除尾部空格 */ end = str + strlen(str) - 1; while (end > str && isspace((unsigned char)*end)) end--; *(end + 1) = '\0'; } /* 十六进制字符转数值 */ int hex_val(char c) { return isdigit(c) ? c - '0' : tolower(c) - 'a' + 10; } ``` **`http_parse_authorization`:** 该函数假设各参数之间使用逗号分隔,且参数值中不包含逗号。 ```c LOCAL S32 http_parse_authorization(CONTEXT *context, char *value) { char *parse_ptr = value; char *token; int error_count = 0; /* 检查Digest认证类型 */ if (strncasecmp(parse_ptr, "Digest ", 7) != 0) { return OK; } parse_ptr += 7; /* 跳过前缀 */ char *saveptr; /* strtok_r上下文指针 */ token = strtok_r(parse_ptr, ",", &saveptr); while (token != NULL) { /* 分割键值对 */ char *eq = strchr(token, '='); if (!eq) { HTTPD_DEBUG("Invalid param format: %s", token); error_count++; token = strtok_r(NULL, ",", &saveptr); continue; } /* 分割键值 */ *eq = '\0'; char *key = token; char *val = eq + 1; /* 移除键值两端的空格 */ trim_whitespace(key); trim_whitespace(val); /* 处理引号包裹的值 */ size_t vlen = strlen(val); if (vlen >= 2 && val[0] == '"' && val[vlen-1] == '"') { val[vlen-1] = '\0'; /* 移除尾部引号 */ memmove(val, val+1, vlen-1); /* 移除头部引号 */ vlen -= 2; /* 更新长度 */ } /* 存储到CONTEXT字段 */ if (strcasecmp(key, "username") == 0) { strncpy(context->digest_username, val, LEN_USERNAME-1); } else if (strcasecmp(key, "realm") == 0) { strncpy(context->digest_realm, val, LEN_REALM-1); } else if (strcasecmp(key, "nonce") == 0) { strncpy(context->digest_nonce, val, LEN_DIGEST_NONCE-1); } else if (strcasecmp(key, "uri") == 0) { /* 规范化URI路径(移除查询参数)*/ char *query_start = strchr(val, '?'); if (query_start) *query_start = '\0'; strncpy(context->digest_uri, val, LEN_URI-1); } else if (strcasecmp(key, "response") == 0) { strncpy(context->digest_response, val, LEN_RESPONSE-1); } else if (strcasecmp(key, "algorithm") == 0) { strncpy(context->digest_algorithm, val, LEN_ALGORITHM-1); } else if (strcasecmp(key, "qop") == 0) { strncpy(context->digest_qop, val, LEN_QOP-1); } else if (strcasecmp(key, "nc") == 0) { strncpy(context->digest_nc, val, LEN_NC-1); } else if (strcasecmp(key, "cnonce") == 0) { strncpy(context->digest_cnonce, val, LEN_CNONCE-1); } else if (strcasecmp(key, "opaque") == 0) { strncpy(context->digest_opaque, val, LEN_OPAQUE-1); } else { HTTPD_DEBUG("Unknown Digest param: %s=%s", key, val); } token = strtok_r(NULL, ",", &saveptr); } /* 检查必需参数是否齐全 */ if (strlen(context->digest_response) == 0) { HTTPD_WARNING("Missing required response field"); return ERROR; } return (error_count > 3) ? ERROR : OK; } ``` #### 步骤三:生成响应Authorization头 `onvif_srv.c`文件,`onvif_make_response(CONTEXT *context)`函数会在状态码非200时调用`http_make_response()`函数发送HTTP错误响应。 ```c #define HTTP_UNAUTHORIZED 401 LOCAL S32 onvif_make_response(CONTEXT *context) { /* 其他字段... */ if (context->code == HTTP_REQ_OK) { if (soap->use_udp == FALSE && soap->xml_buf.start != NULL && soap->xml_buf.start != soap->xml_buf.last) { ONVIF_TRACE("Onvif_make_response return onvif_send_http_rsp_packet."); ret = onvif_send_http_rsp_packet(soap); goto out; } else { ONVIF_TRACE("Onvif_make_response return OK."); ret = OK; goto out; } } ONVIF_TRACE("Onvif_make_response return http_make_response."); ret = http_make_response(context); out: /* 清除content内容 */ http_reset_context(context); return ret; } ``` 在`http_make_response()`函数中,通过`http_send_rsp_header()`设置HTTP响应报文头部。因此,需要修改`http_send_rsp_header()`函数,使其能够在HTTP状态码为401时添加`WWW-Authenticate`头部。 ```c /* Location */ if (HTTP_MOVE_TEMPORARILY == context->code) { // ...原有Location代码... } /* ========== 新增Digest认证头 ========== */ if (HTTP_UNAUTHORIZED == context->code) { /* 必需字段: realm, nonce, qop */ context->digest_realm = “Restricted Area”; /* 生成带时效的nonce */ if (generate_digest_nonce(context) != OK) { return ERROR; } length = snprintf(context->head_end, (context->head_buf_end - context->head_end), "WWW-Authenticate: Digest realm=\"Restricted Area\", nonce=\"%s\", qop=\"auth\"", context->digest_nonce); /* 结束头部 */ length += snprintf(context->head_end + length, (context->head_buf_end - context->head_end - length), "\r\n"); context->head_end += length; context->header_len += length; } /* Access-Control-Allow-Origin */ if (context->origin == TRUE) { /* ...原有代码... */ } ``` #### 步骤四:nonce生成及过期管理: ```c #include <openssl/rand.h> #include <openssl/err.h> #include <time.h> #include <string.h> #define ERROR_CRYPTO_FAIL -1 #define ERROR_BUFFER_OVERFLOW -2 #define OK 0 S32 generate_digest_nonce(CONTEXT *context) { /* 数据源:时间戳+随机数 */ struct { time_t timestamp; /* 精确到秒的时间戳 */ time_t expiration_time; /* 过期时间 */ unsigned char rand_bytes[16]; /* 强随机数 */ } nonce_seed; /* 取当前时间戳 */ nonce_seed.timestamp = time(NULL); nonce_seed.expiration_time = nonce_seed.timestamp + NONCE_LIFETIME; /* 过期时间 */ /* 生成密码学强随机数 (使用OpenSSL RAND_bytes) */ if (RAND_bytes(nonce_seed.rand_bytes, sizeof(nonce_seed.rand_bytes)) != 1) { return ERROR_CRYPTO_FAIL; /* 随机数生成失败 */ } /* 转为十六进制字符串格式 */ char hex_buffer[2 * sizeof(nonce_seed) + 1]; const unsigned char *ptr = (const unsigned char *)&nonce_seed; for (size_t i = 0; i < sizeof(nonce_seed); i++) { snprintf(hex_buffer + 2*i, 3, "%02x", ptr[i]); } hex_buffer[2 * sizeof(nonce_seed)] = '\0'; /* 组合realm和hex_buffer生成nonce:realm:hex_buffer" */ int len = snprintf(context->digest_nonce, LEN_DIGEST_NONCE, "%s:%s", context->digest_realm, hex_buffer); if (len < 0 || len >= LEN_DIGEST_NONCE) { return ERROR_BUFFER_OVERFLOW; } return OK; } /* NONCE验证函数(应在认证流程中调用) */ int validate_nonce(const char *nonce_str) { /* 提取时间结构体部分(跳过realm前缀) */ const char *hex_seed = strchr(nonce_str, ':') + 1; size_t seed_len = strlen(hex_seed)/2; /* 将HEX转回二进制结构 */ struct { time_t timestamp; time_t expiration_time; unsigned char rand_bytes[16]; } seed; for (size_t i = 0; i < seed_len; i++) { sscanf(hex_seed + 2*i, "%2hhx", (unsigned char*)&seed + i); } /* 过期判断 */ time_t current_time = time(NULL); if (current_time > seed.expiration_time) { return 0; /* 过期 */ } return 1; /* 有效 */ } ``` #### 步骤五:修改`onvif_proc_data_srv`函数(`onvif_srv.c`)(待修改) 在`onvif_proc_data_srv`函数中,在解析SOAP之前,先检查是否有Digest认证头,并进行验证。 ```c S32 onvif_proc_data_srv(CONTEXT *context) { /* 现有代码... */ /* 在解析SOAP之前添加Digest认证检查 */ context->digest_authenticated = FALSE; /* 检查是否有Digest认证头 */ if (context->digest_username[0] != '\0') { /* 执行Digest认证 */ if (check_digest_auth(context) == OK) { context->digest_authenticated = TRUE; ONVIF_TRACE("Digest authentication successful"); } else { ONVIF_TRACE("Digest authentication failed"); /* 认证失败,返回401 */ context->code = HTTP_UNAUTHORIZED; return ERROR; } } /* 检查是否至少有一个认证通过 */ if (!context->digest_authenticated && !soap->wss_authenticated) { /* 没有提供任何认证凭据,返回401 */ context->code = HTTP_UNAUTHORIZED; return ERROR; } /* 继续原有处理逻辑... */ ``` #### 步骤六:实现Digest认证验证函数 实现一个函数,用于验证Digest认证的响应是否正确。 ```c S32 check_digest_auth(CONTEXT *context) { if (!context) return -1; /* 验证必要参数存在 */ if (strlen(context->digest_username) == 0 || strlen(context->digest_realm) == 0 || strlen(context->digest_nonce) == 0 || strlen(context->digest_uri) == 0 || strlen(context->digest_response) == 0) { ONVIF_WARN("Missing required Digest auth parameters"); return -1; } /* 获取用户对应的密码 */ char stored_pwd[USER_MANAGEMENT_PASSWD_MAX_STR_LEN + 1] = {0}; if (strcmp(context->digest_username, TP_ROOT_USER) == 0) { /* 管理员账户 */ char *plain_pwd = tpssl_rsa_decrypt(PRI_KEY_FILE_PATH, (U8 *)TP_DEFAULT_PSWD, (U32)strlen(TP_DEFAULT_PSWD)); if (!plain_pwd) return -1; strncpy(stored_pwd, plain_pwd, sizeof(stored_pwd)-1); ONVIF_FREE(plain_pwd); } else { /* 第三方账户 */ THIRD_ACCOUNT_USER_MANAGEMENT third_acct; if (0 == ds_read(THIRD_ACCOUNT_PATH, &third_acct, sizeof(third_acct))) { char *plain_pwd = tpssl_rsa_decrypt(PRI_KEY_FILE_PATH, (U8 *)third_acct.ciphertext, (U32)strlen(third_acct.ciphertext)); if (!plain_pwd) return -1; strncpy(stored_pwd, plain_pwd, sizeof(stored_pwd)-1); ONVIF_FREE(plain_pwd); } else { return -1; } } /* 计算HA1 = MD5(username:realm:password) */ char ha1_input[256]; snprintf(ha1_input, sizeof(ha1_input), "%s:%s:%s", context->digest_username, context->digest_realm, stored_pwd); char ha1[33]; tpssl_md5_hex(ha1_input, ha1); memset(stored_pwd, 0, sizeof(stored_pwd)); /* 立即清除密码 */ /* 计算HA2 = MD5(method:uri) */ char ha2_input[256]; snprintf(ha2_input, sizeof(ha2_input), "%s:%s", http_method_str(context->method), context->digest_uri); char ha2[33]; tpssl_md5_hex(ha2_input, ha2); /* 计算预期响应 = MD5(HA1:nonce:nc:cnonce:qop:HA2) */ char resp_input[512]; snprintf(resp_input, sizeof(resp_input), "%s:%s:%s:%s:%s:%s", ha1, context->digest_nonce, context->digest_nc, context->digest_cnonce, context->digest_qop, ha2); char expected_resp[33]; tpssl_md5_hex(resp_input, expected_resp); /* 验证响应值 */ if (strcasecmp(expected_resp, context->digest_response) == 0) { ONVIF_TRACE("Digest auth success for user: %s", context->digest_username); return 0; /* 认证成功 */ } ONVIF_WARN("Digest auth failed. Expected: %s, Received: %s", expected_resp, context->digest_response); return -1; /* 认证失败 */ } #include <openssl/md5.h> /* 计算字符串的MD5哈希值(十六进制格式) */ void tpssl_md5_hex(const char *input, char *output) { if (!input || !output) return; unsigned char digest[MD5_DIGEST_LENGTH]; MD5_CTX context; MD5_Init(&context); MD5_Update(&context, input, strlen(input)); MD5_Final(digest, &context); for (int i = 0; i < MD5_DIGEST_LENGTH; i++) { sprintf(output + (i * 2), "%02x", digest[i]); } output[32] = '\0'; /* MD5总是32字符 */ } /* 将HTTP方法枚举转换为字符串 */ const char *http_method_str(HTTP_METHOD method) { switch(method) { case HTTP_GET: return "GET"; case HTTP_POST: return "POST"; case HTTP_PUT: return "PUT"; case HTTP_DELETE: return "DELETE"; default: return "UNKNOWN"; } } ```
09-13
#define MAX_PATH_LEN 1024 #define MAX_MATCHES 1024 #define DEBUG_PRINT(fmt, ...) \ do { fprintf(stderr, "[DEBUG] %s:%d: " fmt, __func__, __LINE__, ##__VA_ARGS__); } while (0) // 文件类型枚举 typedef enum { FILE_TYPE_REGULAR, FILE_TYPE_DIRECTORY, FILE_TYPE_VIRTUAL } file_type_t; // 匹配项结构 typedef struct { char *path; file_type_t type; } match_item_t; // 补全状态结构 typedef struct { char base_path[MAX_PATH_LEN]; // 原始基础路径 char current_path[MAX_PATH_LEN]; // 当前解析的路径 char prefix[MAX_PATH_LEN]; // 当前需要补全的前缀 match_item_t matches[MAX_MATCHES]; // 匹配项列表 int match_count; // 匹配项数量 int current_index; // 当前选择的匹配项索引 char last_user_input[MAX_PATH_LEN]; // 用户最后一次输入 char last_returned_match[MAX_PATH_LEN]; // 上一次返回的匹配项 file_type_t last_match_type; // 上一次返回的匹配项类型 int in_single_directory_mode; // 是否处于单目录模式 int is_virtual_path; // 当前路径是否为虚拟路径 } completion_state; static completion_state comp_state = {0}; int compare_matches(const void *a, const void *b) { const match_item_t *ma = (const match_item_t *)a; const match_item_t *mb = (const match_item_t *)b; const char *sa = ma->path; const char *sb = mb->path; char first_a = sa[0]; char first_b = sb[0]; int is_lower_a = islower((unsigned char)first_a); int is_lower_b = islower((unsigned char)first_b); if (is_lower_a && !is_lower_b) return -1; if (!is_lower_a && is_lower_b) return 1; int is_upper_a = isupper((unsigned char)first_a); int is_upper_b = isupper((unsigned char)first_b); if (is_upper_a && !is_upper_b) return -1; if (!is_upper_a && is_upper_b) return 1; return strcasecmp(sa, sb); } // 重置补全状态 void reset_completion_state() { printf("Resetting state (had %d matches)\n", comp_state.match_count); //for (int i = 0; i < comp_state.match_count; i++) { // if (comp_state.matches[i].path) { // XFREE(MTYPE_TMP, comp_state.matches[i].path); // comp_state.matches[i].path = NULL; // } //} comp_state.match_count = 0; comp_state.current_index = 0; comp_state.last_returned_match[0] = '\0'; comp_state.last_match_type = FILE_TYPE_REGULAR; comp_state.in_single_directory_mode = 0; comp_state.is_virtual_path = 0; } // 虚拟路径映射 const char *map_virtual_path(const char *virt) { static char real_path[MAX_PATH_LEN]; if (strncmp(virt, "flash:/", 7) == 0) { snprintf(real_path, sizeof(real_path), "/mnt/switch/%s", virt + 7); return real_path; } // 对于普通路径,也复制(如果需要) strncpy(real_path, virt, sizeof(real_path) - 1); real_path[sizeof(real_path) - 1] = '\0'; return real_path; } // 解析输入文本 void parse_input_text(const char *text) { //assert(text != NULL); // 如果输入为空 if (text[0] == '\0') { comp_state.current_path[0] = '\0'; comp_state.prefix[0] = '\0'; return; } // 查找最后一个 '/' const char *slash = strrchr(text, '/'); if (slash == NULL) { // 无 '/' → 整个是前缀,路径为空 comp_state.current_path[0] = '\0'; strncpy(comp_state.prefix, text, MAX_PATH_LEN - 1); comp_state.prefix[MAX_PATH_LEN - 1] = '\0'; } else { // 有 '/' → 路径是到 '/' 为止,前缀是后面部分 size_t path_len = (slash - text) + 1; // 包括 '/' size_t copy_len = path_len > MAX_PATH_LEN - 1 ? MAX_PATH_LEN - 1 : path_len; memcpy(comp_state.current_path, text, copy_len); comp_state.current_path[copy_len] = '\0'; const char *prefix_start = slash + 1; size_t prefix_len = strlen(prefix_start); if (prefix_len >= MAX_PATH_LEN) prefix_len = MAX_PATH_LEN - 1; memcpy(comp_state.prefix, prefix_start, prefix_len); comp_state.prefix[prefix_len] = '\0'; } printf("[PARSE] text='%s' → path='%s', prefix='%s'\n", text, comp_state.current_path, comp_state.prefix); } // 添加匹配项 void add_match(const char *path, file_type_t type) { if (comp_state.match_count >= MAX_MATCHES) { printf("Match list full, skipping '%s'\n", path); return; } comp_state.matches[comp_state.match_count].path = XSTRDUP(MTYPE_TMP, path); comp_state.matches[comp_state.match_count].type = type; comp_state.match_count++; printf("Added match: %s (type: %d)\n", path, type); } // 生成匹配项 void generate_matches(void) { printf("Generating matches for path='%s' prefix='%s'\n", comp_state.current_path, comp_state.prefix); // 重置匹配计数 comp_state.match_count = 0; // 添加虚拟路径选项 (仅当输入以'f'开头) if (comp_state.prefix[0] == 'f' || comp_state.prefix[0] == 'F') { add_match("flash:/", FILE_TYPE_VIRTUAL); printf("Added virtual path option: flash:/\n"); } // 获取实际扫描路径 const char *scan_path = comp_state.current_path; if (strncmp(scan_path, "flash:/", 7) == 0) { scan_path = map_virtual_path(scan_path); // 确保 map_virtual_path 返回 "/mnt/switch/..." } // 处理空路径情况 if (*scan_path == '\0') { scan_path = "."; } // 尝试打开目录 DIR *dir = opendir(scan_path); if (!dir) { printf("Failed to open dir '%s'\n", scan_path); return; } struct dirent *entry; while ((entry = readdir(dir)) != NULL) { const char *name = entry->d_name; // 跳过隐藏文件和特殊目录 if (name[0] == '.' && (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)) { continue; } // 前缀匹配 if (comp_state.prefix[0] != '\0' && strncasecmp(name, comp_state.prefix, strlen(comp_state.prefix)) != 0) { continue; } // 构建完整路径 char full_path[MAX_PATH_LEN] = {0}; snprintf(full_path, sizeof(full_path), "%s%s", comp_state.current_path, name); // 构建真实路径用于 stat() char check_path[MAX_PATH_LEN] = {0}; if (strncmp(comp_state.current_path, "flash:/", 7) == 0) { snprintf(check_path, sizeof(check_path), "/mnt/switch/%s/%s", comp_state.current_path + 7, name); } else { snprintf(check_path, sizeof(check_path), "%s/%s", scan_path, name); } struct stat st; if (stat(check_path, &st) == 0) { if (S_ISDIR(st.st_mode)) { // 目录:添加 '/' snprintf(full_path, sizeof(full_path), "%s%s/", comp_state.current_path, name); add_match(full_path, FILE_TYPE_DIRECTORY); } else { add_match(full_path, FILE_TYPE_REGULAR); } } else { add_match(full_path, FILE_TYPE_REGULAR); printf("Failed to stat '%s', assuming regular file\n", check_path); } } closedir(dir); printf("Total matches generated: %d\n", comp_state.match_count); } // 检测输入变化 int detect_input_change(const char *text) { printf("[DEBUG] detect_input_change: last_input='%s', last_match='%s', text='%s'\n", comp_state.last_user_input, comp_state.last_returned_match, text); // 情况1: 首次调用 if (comp_state.last_user_input[0] == '\0') { printf("→ First call: input changed\n"); return 1; } // 情况2: 用户在上一个补全项后继续输入(如 etc/ → etc/s) if (comp_state.last_returned_match[0] != '\0') { size_t match_len = strlen(comp_state.last_returned_match); if (match_len > 0 && match_len < MAX_PATH_LEN && strncmp(text, comp_state.last_returned_match, match_len) == 0 && strlen(text) > match_len) { printf("→ Append after match: '%s' → '%s'\n", comp_state.last_returned_match, text); return 1; } } // 情况3: 输入变短(用户删除字符) if (strlen(text) < strlen(comp_state.last_user_input)) { printf("→ Input shortened: '%s' → '%s'\n", comp_state.last_user_input, text); return 1; } // 情况4: 输入前缀不一致(全新输入) size_t last_len = strlen(comp_state.last_user_input); if (last_len > 0 && strncmp(text, comp_state.last_user_input, last_len) != 0) { printf("→ New input prefix: '%s' vs '%s'\n", text, comp_state.last_user_input); return 1; } // 默认:无显著变化 → 循环模式 printf("→ No change detected (cycling mode)\n"); return 0; } // 处理单目录模式 void handle_single_directory_mode(void) { if (comp_state.match_count != 1) return; if (comp_state.matches[0].type != FILE_TYPE_DIRECTORY) return; const char *full = comp_state.matches[0].path; const char *slash = strrchr(full, '/'); if (!slash) return; const char *dirname = slash + 1; if (strlen(dirname) == 0) return; char new_path[MAX_PATH_LEN]; snprintf(new_path, sizeof(new_path), "%s%s/", comp_state.current_path, dirname); strncpy(comp_state.current_path, new_path, MAX_PATH_LEN - 1); comp_state.prefix[0] = '\0'; reset_completion_state(); generate_matches(); // ← 必须成功生成子文件 if (comp_state.match_count == 0) { printf("→ No files in directory: '%s'\n", comp_state.current_path); // 至少添加一个占位符,避免后续崩溃 add_match(new_path, FILE_TYPE_DIRECTORY); // 保持当前目录 return; } comp_state.current_index = 0; printf("→ Auto-expanded to directory: '%s' with %d items\n", comp_state.current_path, comp_state.match_count); } // 主补全函数 char *filename_completion_function(const char *text, int state) { printf("\n=== COMPLETION CALL: text='%s' state=%d ===\n", text, state); // 检测输入文本是否变化 int input_changed = detect_input_change(text); // 更新用户输入记录 if (input_changed) { strncpy(comp_state.last_user_input, text, MAX_PATH_LEN-1); comp_state.last_user_input[MAX_PATH_LEN-1] = '\0'; } // 处理不同模式 if (input_changed) { printf("Input changed: '%s' -> '%s'\n", comp_state.last_returned_match, text); // 重置状态 reset_completion_state(); // 解析输入 parse_input_text(text); // 生成匹配项 generate_matches(); // 如果唯一目录,自动展开并加载其内容 if (comp_state.match_count == 1 && comp_state.matches[0].type == FILE_TYPE_DIRECTORY) { handle_single_directory_mode(); // ← 已经 generate_matches() // 注意:此时 match_count 是子目录内容数量 } comp_state.current_index = 0; } // 处理目录追加模式 else if (comp_state.last_match_type == FILE_TYPE_DIRECTORY && comp_state.last_returned_match[0] != '\0' && strstr(text, comp_state.last_returned_match) == text && strlen(text) > strlen(comp_state.last_returned_match)) { printf("Directory append mode: '%s' + '%s'\n", comp_state.last_returned_match, text + strlen(comp_state.last_returned_match)); // 设置当前路径为上次返回的匹配项 strncpy(comp_state.current_path, comp_state.last_returned_match, MAX_PATH_LEN-1); // 提取新前缀 size_t prefix_start = strlen(comp_state.last_returned_match); size_t prefix_size = strlen(text) - prefix_start; if (prefix_size >= MAX_PATH_LEN) prefix_size = MAX_PATH_LEN - 1; strncpy(comp_state.prefix, text + prefix_start, prefix_size); comp_state.prefix[prefix_size] = '\0'; // 重新生成匹配项 reset_completion_state(); generate_matches(); comp_state.current_index = 0; } // 处理循环模式 else { if (comp_state.match_count == 0) { return NULL; } comp_state.current_index = (comp_state.current_index + 1) % comp_state.match_count; printf("Cycling to index %d\n", comp_state.current_index); } // 返回当前匹配项 if (comp_state.match_count == 0) { return NULL; } printf("→ comp_state.match_count = %d, comp_state.current_index = %d\n", comp_state.match_count, comp_state.current_index); const char *path = comp_state.matches[comp_state.current_index].path; char *ret = XSTRDUP(MTYPE_TMP, path); // ✅ 关键:只要返回了补全项,就更新 last_returned_match // 并且,如果这是自动补全(唯一匹配),也要更新 last_user_input! strncpy(comp_state.last_returned_match, ret, MAX_PATH_LEN - 1); comp_state.last_returned_match[MAX_PATH_LEN - 1] = '\0'; // ✅ 新增:如果 match_count == 1,说明自动补全,用户“输入”变成了补全结果 if (input_changed && comp_state.match_count == 1) { strncpy(comp_state.last_user_input, ret, MAX_PATH_LEN - 1); comp_state.last_user_input[MAX_PATH_LEN - 1] = '\0'; printf("→ Auto-completed. Updating last_user_input to: '%s'\n", comp_state.last_user_input); } return ret; } 现在是这样实现的,看看哪里还有问题?
08-14
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值