[HTTP]_[C/C++]_[解析URL的转义字符百分比字符串]

本文介绍了一个用于将URL中带有%XX格式的字符串转换为正常字符的C++实现。该算法可以将URL编码的字符串解码成原始形式,适用于处理包含特殊字符的文件路径或链接。


场景:

1.有时候获取一个超链接时,或者一个图片src时,里面的地址带有%XX,这样如果当成文件路径处理会不识别。所以要把转义字符解码。

2.它其实就是ASCII码的十六进制表示。

以下是stackoverflow提供的一个实现,很久以前找的,找不到原文了。添加了一些单元测试.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <windows.h>
#include <assert.h>

std::string UrlDecode(std::string &SRC)
{
    std::string ret;
    char ch;
    int ii;
    for (size_t i=0; i<SRC.length(); i++) {
        if (int(SRC[i])==37) {
            sscanf(SRC.substr(i+1,2).c_str(), "%x", &ii);
            ch=static_cast<char>(ii);
            ret+=ch;
            i=i+2;
        } else {
            ret+=SRC[i];
        }
    }
    return (ret);
}


int main(int argc, char *argv[])
{
	printf("Hello, world\n");
	std::string file_path = "C:\\Program%20File\\";
	
	std::string str = UrlDecode(file_path);
	assert(!strcmp(str.c_str(),"C:\\Program File\\"));
	
	return 0;
}


<think>我们需要将这个C结构体用FLOGI(类似cout的日志工具)打印出来。由于FLOGI的具体语法我不清楚(因为它是自定义的,类似于cout,但可能不是完全相同的语法),我将假设FLOGI支持流式输出,并且可以像std::cout一样使用<<操作符输出各种基本类型。 但是,请注意结构体中有数组(如bat_type[9], bat_sn[24]等),这些数组实际上是固定大小的字符数组,我们可以将它们视为字符串(如果它们是以空字符结尾的)或者作为字符数组逐个打印。然而,根据结构体定义,它们并没有被显式声明为以空字符结尾,所以直接当作C字符串输出可能会有风险(如果内容没有'\0',则可能越界)。为了安全起见,我们可以将每个字符数组当作固定长度的数组,并输出为字符串,但需要确保输出正确的长度(即打印数组中的实际内容,直到遇到空字符或者达到数组长度)。 不过,FLOGI可能没有直接支持打印固定长度数组的操作符,所以我们可以将数组转换为std::string_view(C++17)或使用一个循环来输出。但考虑到简洁性,我们可以将每个字符数组包装成一个字符串视图或者构造一个临时字符串,但要注意数组可能没有空终止符,所以我们不能直接使用c_str()。 另一种方法是,我们可以写一个辅助函数来安全地打印固定大小的字符数组,确保即使没有空终止符也不会越界。 然而,由于要求简洁,并且我们假设是在C++环境中(因为提到了类似cout的工具),我们可以使用std::string_view来安全地处理这些字符数组,因为我们知道数组的大小,所以可以构造一个string_view来指定长度,这样输出时只输出数组中的字符(遇到空字符也不会停止,直到达到长度)。但是,如果数组中有中间有空字符,那么输出就会截断(但这不是问题,因为数据本身可能包含空字符,我们不应该忽略)。 但是,如果FLOGI不支持string_view,那么我们可以用循环逐个字符输出,或者将数组内容复制到一个字符串中再输出。不过,这样不够简洁。 我建议:对于字符数组,我们使用一个包装函数,将其转换为一个字符串对象(使用给定的长度),然后输出。但注意,如果数组中有空字符,我们仍然希望完整输出,所以不能直接当作C字符串。 然而,FLOGI可能不支持直接输出二进制数据(包括中间有空字符的字符串)。通常,日志输出时,如果遇到非打印字符,我们可能会希望以十六进制形式输出,或者直接输出整个字符串(但非打印字符可能导致日志混乱)。考虑到这是一个结构体,我们可能需要以可读的方式输出,所以对于字符串字段,如果它们应该是字符串(比如time_zone, bootup_type等),那么它们可能是以空字符结尾的。但是,我们无法确定,所以最安全的方式是输出为十六进制字符串,或者按字符数组输出每个字符(如果是可打印字符则输出,否则输出转义序列)。但这会增加代码复杂性。 考虑到实际需求,通常这些字段都是可打印字符串,并且以空字符结尾。因此,我们可以假设bat_type, bat_sn, time_zone, bootup_type都是以空字符结尾的字符串。这样,我们可以直接将它们当作C字符串输出(因为数组大小足够,通常会有空字符)。但是,如果它们没有被正确终止,那么我们就可能读取越界。所以,为了安全,我们输出时指定最大长度,即数组的大小,然后输出到第一个空字符为止(如果遇到空字符就停止,否则输出全部长度?)但这样我们需要一个安全的方式。 实际上,我们可以这样:构造一个std::string_view,长度为数组大小,然后输出。这样即使没有空字符,也会输出整个数组(但非打印字符会导致日志看起来乱)。或者,我们可以将数组内容转换成十六进制字符串输出,但这会使日志变得很长。 由于问题要求安全且简洁,我建议采用以下策略: 1. 对于明确是字符串的字段(如time_zone, bootup_type),我们假设它们是以空字符结尾的,但为了防止越界,我们使用strnlen来获取实际长度,然后输出(这样确保不会越界,并且输出到第一个空字符为止)。 2. 对于像bat_type和bat_sn,它们可能也是字符串,同样处理。 但是,bat_type只有9个字节,bat_sn有24个字节,如果它们没有空字符,我们最多输出9个或24个字符。 另一种更一致的方法是:为每个字符数组字段,我们使用std::string_view来包装,然后输出整个视图(这样会输出整个数组,包括空字符后面的内容,但如果有空字符,后面的内容也会输出,这可能是我们想要的吗?)。实际上,我们通常只需要输出到第一个空字符为止,因为后面的内容可能是未初始化的。 所以,我决定:对于字符数组,我们使用一个辅助函数来获取一个“安全”的C字符串表示,即如果数组中有空字符,则输出到空字符为止,否则输出整个数组(最多到数组长度)。我们可以写一个函数来返回一个std::string,但这样会有拷贝。为了避免拷贝,我们可以返回一个std::string_view,然后输出这个string_view。 但是,FLOGI可能不支持直接输出string_view。所以,我们可以写一个函数,返回一个指向有效字符串的指针和长度,然后使用FLOGI的write函数(如果有的话)或者循环输出每个字符。 为了简洁,我们假设FLOGI可以输出std::string,那么我们可以将每个字符数组转换为一个std::string,指定长度,这样即使中间有空字符也会完整输出。但这样输出的字符串可能包含不可见字符,日志可读性差。 考虑到日志的可读性,通常我们期望看到的是字符串的内容(如果字段确实是字符串的话)。所以,我们假设这些字段都是可打印字符串,并且以空字符结尾。这样,我们使用C字符串的方式输出,但使用一个安全的函数确保不越界。 我们可以这样:对于字符数组arr,使用`std::string_view(arr, strnlen(arr, sizeof(arr)))`,然后输出这个string_view。这样,如果数组中有空字符,则输出到空字符为止(不会越界),如果没有,则输出整个数组(因为strnlen会在达到sizeof(arr)时停止)。然后,我们输出这个string_view。 但是,由于FLOGI可能不支持string_view,我们可以用循环输出这个string_view的每个字符?或者,如果FLOGI有输出字符数组的函数,那最好。 由于问题中没有指定FLOGI的具体接口,我将假设FLOGI支持<<操作符输出std::string和C风格字符串(const char*)。因此,对于字符数组,我们可以这样: FLOGI << "bat_type: " << std::string(event.bat_type, strnlen(event.bat_type, sizeof(event.bat_type))); 但是,这样每个字段都要写这么长,不够简洁。我们可以定义一个宏或者辅助函数来简化。 然而,问题要求简洁。所以,我们可以写一个辅助函数,将固定大小的字符数组转换为std::string,然后输出。但是,为了避免临时构造std::string(可能带来性能开销,但日志通常可以接受),我们可以使用一个宏或者内联函数。 由于C++17引入了string_view,我们可以使用它来避免内存分配。但是,如果FLOGI不接受string_view,我们可以用循环输出每个字符(这样代码会很长)。 另一种方法是,直接使用FLOGI输出C字符串,因为大多数情况下它们都有终止符。如果担心,我们可以用strnlen计算长度,然后使用write方法(如果FLOGI有类似的方法)输出。 这里,我假设FLOGI有类似于cout的用法,并且支持输出字符指针(当作C字符串)。所以,我们可以直接输出字符数组,因为数组名就是指针。但是,如果数组没有终止符,就会越界读取。所以,我们需要确保安全。 鉴于以上考虑,我决定采用如下方法: 对于每个字符数组字段,我们使用一个包装函数,该函数返回一个std::string(通过复制数组内容,并截断到第一个空字符,或者整个数组如果没有空字符)。这样虽然有一次拷贝,但安全且简单。 但是,我们可以写一个函数来返回一个安全的C字符串表示(通过临时构造一个字符串),然后输出这个字符串。不过,我们可以直接构造一个临时字符串并输出。 然而,为了代码的简洁性,并且考虑到这些数组不会太大,我们可以接受这种拷贝。 具体实现: 我们可以定义一个辅助函数(模板)来将字符数组转换为字符串: template<size_t N> std::string safe_string(const char (&arr)[N]) { return std::string(arr, strnlen(arr, N)); } 但是,注意:结构体中的数组类型是uint8_t,而不是char。所以我们需要先将其转换为char*。不过,uint8_t通常就是unsigned char,我们可以将其视为字符。 另外,有些字段可能不是字符串,而是二进制数据(虽然这里看起来都是字符串),但我们还是按照字符串输出。 因此,我们可以这样: 由于数组类型是uint8_t,我们可以使用reinterpret_cast<const char*>(arr)来转换。 但是,我们可以直接构造字符串:std::string(reinterpret_cast<const char*>(arr), strnlen(reinterpret_cast<const char*>(arr), sizeof(arr))) 这样写很长,所以我们可以用辅助函数。 但是,为了简洁,我们不单独写辅助函数(因为问题要求简洁,并且只在这个结构体中使用),我们可以在输出每个字段时使用临时构造的std::string。 另外,对于非数组字段,直接输出即可。 下面,我们写出整个打印函数: 注意:FLOGI可能是一个全局对象,类似于std::cout。 假设我们已经有一个event_buried_dgs_boot_up_t类型的变量event。 打印代码: 为了避免重复写很长的转换代码,我们可以使用宏来简化,但宏不安全,所以我们写一个内联函数。不过,为了简洁,我们可以在一个函数内完成。 由于结构体中有多个字段,我们逐个输出。 另外,对于数组字段,我们使用上述的安全转换。 但是,对于bat_type和bat_sn,它们分别有9和24个字节,我们也要这样处理。 步骤: 1. 对于非数组字段,直接输出。 2. 对于数组字段,转换为std::string(使用安全长度)然后输出。 代码示例: ```cpp #include <string> #include <cstring> // for strnlen // 辅助函数:将uint8_t数组转换为安全的std::string inline std::string safe_string(const uint8_t* arr, size_t max_len) { // 如果arr为空,返回空字符串 if (arr == nullptr) { return ""; } // 计算实际字符串长度(直到遇到'\0'或者max_len) size_t len = strnlen(reinterpret_cast<const char*>(arr), max_len); return std::string(reinterpret_cast<const char*>(arr), len); } void print_boot_up_event(const event_buried_dgs_boot_up_t& event) { FLOGI << "battery_percent: " << static_cast<int>(event.battery_percent); FLOGI << "bat_num: " << static_cast<int>(event.bat_num); FLOGI << "screen_brightless: " << static_cast<int>(event.screen_brightless); FLOGI << "is_poweroff: " << static_cast<int>(event.is_poweroff); FLOGI << "sd_card_online: " << static_cast<int>(event.sd_card_online); FLOGI << "screen_light_time: " << event.screen_light_time; FLOGI << "bat_type: " << safe_string(event.bat_type, sizeof(event.bat_type)); FLOGI << "bat_sn: " << safe_string(event.bat_sn, sizeof(event.bat_sn)); FLOGI << "time_zone: " << safe_string(event.time_zone, sizeof(event.time_zone)); FLOGI << "bootup_type: " << safe_string(event.bootup_type, sizeof(event.bootup_type)); } ``` 注意: - 对于uint8_t类型的非数组字段,我们转换为int输出,因为如果直接输出,可能会被当作字符(如果FLOGI将uint8_t当做unsigned char,那么输出的是字符,而不是数字)。所以需要显式转换为int。 - safe_string函数将uint8_t数组转换为std::string,使用给定的最大长度(即数组大小),然后通过strnlen获取实际字符串长度(遇到空字符停止,但不超过最大长度),然后构造字符串。 但是,safe_string函数中,我们使用strnlen,它要求数组指向的是一个有效的C风格字符串(即字符数组),即使没有终止符,strnlen也会在max_len处停止,所以是安全的。 然而,如果数组中有嵌入的空字符,那么strnlen会返回第一个空字符的位置,这样后面的内容就不会被输出。如果这些字段可能包含嵌入的空字符,那么我们需要完整输出整个数组(作为二进制数据),但这样日志可读性不好。所以,我们假设这些字段都是字符串,通常不会有嵌入的空字符(除了结尾的终止符)。 另外,对于time_zone和bootup_type,它们的大小是32,我们使用sizeof(event.time_zone)得到32。 这样,我们就安全地将结构体打印出来了。 但是,我们假设FLOGI可以接受std::string并输出其内容。如果FLOGI不支持输出std::string,那么我们可以输出为C字符串,即使用.c_str()。但是,我们已经在safe_string中返回了std::string,所以我们可以这样: FLOGI << "bat_type: " << safe_string(...).c_str(); 不过,通常FLOGI(如果设计类似cout)是支持std::string的,因为C++标准库中cout是支持的。 所以,我们直接输出std::string。 另外,我们也可以重载<<操作符,但这里不展开。 最后,请注意,这个打印函数只适用于这个特定的结构体。 如果FLOGI有更高级的格式化功能(比如打印结构体),那就更好。但这里我们没有,所以手动打印每个字段。 因此,最终代码如上。 注意:需要包含头文件<string>和<cstring>。 另外,在实际使用中,可能还需要考虑线程安全等问题,但这里只是打印日志,通常日志系统会处理线程安全。 如果FLOGI不支持多行,我们可以将整个结构体打印成一行,但这里按照每个字段一行输出,比较清晰。 我们也可以在一行输出,用分隔符。但这取决于需求,这里我们按行输出。 如果希望更紧凑,可以自行调整。 综上所述,我们实现了安全且简洁的打印。 注意:如果结构体中的数组字段包含非ASCII字符(比如中文),那么输出可能乱码,这取决于日志系统的编码,但我们这里不处理。 下面,我将代码整合成一个完整的例子。 </think>以下是使用 FLOGI 日志工具安全打印 `event_buried_dgs_boot_up_t` 结构体的完整实现方案。该方案通过辅助函数确保字符数组的安全输出,并处理非字符字段的格式化: ```cpp #include <string> #include <cstring> // 用于 strnlen // 安全打印字符数组的辅助函数 inline std::string safe_char_array_to_string(const uint8_t* arr, size_t max_len) { // 计算有效字符串长度(遇到空字符或达到最大长度时停止) size_t valid_len = strnlen(reinterpret_cast<const char*>(arr), max_len); return std::string(reinterpret_cast<const char*>(arr), valid_len); } // 打印结构体的主函数 void log_boot_up_event(const event_buried_dgs_boot_up_t& event) { // 直接打印基础类型字段 FLOGI << "Battery Percent: " << static_cast<int>(event.battery_percent); FLOGI << "Battery Number: " << static_cast<int>(event.bat_num); FLOGI << "Screen Brightness: " << static_cast<int>(event.screen_brightless); FLOGI << "Normal Shutdown: " << (event.is_poweroff ? "Yes" : "No"); FLOGI << "SD Card Present: " << (event.sd_card_online ? "Yes" : "No"); FLOGI << "Screen Usage Time: " << event.screen_light_time << " minutes"; // 安全打印字符数组字段 FLOGI << "Battery Type: " << safe_char_array_to_string(event.bat_type, sizeof(event.bat_type)); FLOGI << "Battery SN: " << safe_char_array_to_string(event.bat_sn, sizeof(event.bat_sn)); FLOGI << "Time Zone: " << safe_char_array_to_string(event.time_zone, sizeof(event.time_zone)); FLOGI << "Bootup Type: " << safe_char_array_to_string(event.bootup_type, sizeof(event.bootup_type)); } ``` ### 代码详解: 1. **辅助函数 `safe_char_array_to_string`**: - 使用 `strnlen` 获取有效字符串长度,防止越界访问 - 将 `uint8_t` 数组转换为 `const char*` 类型 - 返回安全的 `std::string` 对象,确保内容完整且无额外内存风险 2. **主函数 `log_boot_up_event`**: - **基础类型处理**: - 使用 `static_cast<int>` 显式转换 `uint8_t` 字段,避免被误解释为 ASCII 字符 - 布尔值转换为可读的 "Yes/No" 字符串 - **字符数组处理**: - 调用辅助函数安全转换固定长度字符数组 - 显式指定数组长度参数(如 `sizeof(event.bat_type)`) - **输出格式**: - 每个字段单独一行,包含清晰的字段说明 - 数值类型添加单位说明(如 minutes) ### 安全特性: 1. **防越界**:对所有字符数组使用 `strnlen` 确定有效长度 2. **类型安全**: - 数值字段显式转换为 `int` 避免二进制解释 - 布尔值转换为人类可读格式 3. **内存安全**:使用 `std::string` 管理临时字符串,避免内存泄漏 ### 使用示例: ```cpp event_buried_dgs_boot_up_t boot_event = { .battery_percent = 85, .bat_num = 2, .screen_brightless = 70, .is_poweroff = 1, .sd_card_online = 1, .screen_light_time = 120, .bat_type = "Li-ion\x00\x12", // 测试含特殊字符的情况 .bat_sn = "SN12345678901234567890", .time_zone = "Asia/Shanghai", .bootup_type = "Cold Boot" }; log_boot_up_event(boot_event); ``` ### 输出示例: ``` Battery Percent: 85 Battery Number: 2 Screen Brightness: 70 Normal Shutdown: Yes SD Card Present: Yes Screen Usage Time: 120 minutes Battery Type: Li-ion Battery SN: SN12345678901234567890 Time Zone: Asia/Shanghai Bootup Type: Cold Boot ``` 此实现确保: - 即使字符数组包含中间空字符(如测试数据中的 `\x00`),也能安全截断 - 所有字段输出为人类可读格式 - 无内存越界风险 - 兼容标准 C++11 及以上环境
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白行峰 (花名)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值