zl程序教程

您现在的位置是:首页 >  其他

当前栏目

HTTP协议header中Content-Disposition中文文件名乱码

中文HTTP协议 乱码 文件名 header content
2023-09-14 09:08:24 时间

 从跟踪代码来看,content-disposition存放的是http response的raw header。直到在HttpContentDisposition类的filename_成员才会存放转换了的编码。

这个转换编码的猜测流程:asc,utf,有指定编码,按指定;否则按系统的字符集。

 

参考:

https://blog.csdn.net/lc11535/article/details/100013653

https://blog.csdn.net/weixin_34366546/article/details/85839548?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param

在线字符编码转换:http://www.mytju.com/classcode/tools/encode_gb2312.asp

https://blog.csdn.net/iteye_14084/article/details/81554791

https://www.cnblogs.com/batsing/p/charset.html

https://zhidao.baidu.com/question/89668249.html

 

比如:“中文” 字符得编码:

Unicode中为:4E2D 6587  

GBK(gb2312 gb18030)中为:D6D0 CEC4

这是在内存中存放形式。chrome内部统一用Unicode在内存存放,所以会有一张gbk到unicode得对照表,将“中”的 gbk D6D0 转换为 unicode的4E2D。

当需要把“中文”这两字保存到文件,或者网络传输时,直接保存需要两个字节,这样会浪费保存英文的存储空间,因为英文只需要一个字节。所以这时就有个编码的需求。一般都是用utf8。英文直接还是用一个字节;中文就要用3个字节。utf8,utf16,utf32都是对unicode的编码存储。

“中文”的utf8存成文件为: 

 

而“中文”的GBK存储时,直接就是按编码存储。

gb2312:

规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了

gbk:只要高位是1开始,即大于127;不再管地位。这样增加了2万汉字。

GB18030:加入了少数民族的字。

Unicode出现:所有字符都占两位

wchar_t * p = L"Hello!" ;//占10个字节

没有一种简单的算术方法可以把文本内容从UNICODE编码和另一种编码进行转换,这种转换必须通过查表来进行。如unicode 到gbk。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表

UNICODE("Universal Multiple-Octet Coded Character Set",简称 UCS, 俗称 "UNICODE"。) 是用两个字节来表示为一个字符,组合出65535不同的字符,这大概已经可以覆盖世界上所有文化的符号。

如果还不够也没有关系,ISO已经准备了UCS-4方案,说简单了就是四个字节来表示一个字符,这样我们就可以组合出21亿个不同的字符出来(最高位有其他用途)。

面向网络传输规范出现:UTF(UCS Transfer Format)顾名思义,UTF8就是每次8个位传输数据,而UTF16就是每次16个位,只不过为了传输时的可靠性,从UNICODE到UTF时并不是直接的对应,而是要过一些算法和规则来转换。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。

INTEL低位先发送"FEFF",否则高位,utf的文件头都有这个标志。

打开文件时 EF BB BF, 表示UTF-8

Unicode

UTF-8 

0000 - 007F 0xxxxxxx
0080 - 07FF 110xxxxx 10xxxxxx

0800 - FFFF

1110xxxx 10xxxxxx 10xxxxxx

记事本的编码默认是ANSI, 如果你在ANSI的编码输入汉字,那么他实际就是GB系列的编码方式,在这种编码下,"联通"的内码是:

  c1 1100 0001

  aa 1010 1010

  cd 1100 1101

  a8 1010 1000

 

windows的记事本保存时选择编码格式:

里面有四个选项:ANSI,Unicode,Unicode big endian 和 UTF-8。

1)ANSI是默认的编码方式。对于英文文件是ASCII编码,对于简体中文文件是GB2312编码(只针对Windows简体中文版,如果是繁体中文版会采用Big5码)。

2)Unicode编码指的是UCS-2编码方式,即直接用两个字节存入字符的Unicode码。这个选项用的little endian格式。

3)Unicode big endian编码与上一个选项相对应。我在下一节会解释little endian和big endian的涵义。

4)UTF-8编码,也就是上一节谈到的编码方法。

选择完”编码方式“后,点击”保存“按钮,文件的编码方式就立刻转换好了。

Unicode码可以采用UCS-2格式直接存储。以汉字”严“为例,Unicode码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。

Unicode规范中定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做”零宽度非换行空格“(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。这正好是两个字节,而且FF比FE大1。

如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。

这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论,
吃鸡蛋时究竟是从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。为了这件事情,前后爆发了六次战争,
一个皇帝送了命,另一个皇帝丢了王位。 因此,第一个字节在前,就是”大头方式“(Big endian),第二个字节在前就是”小头方式“(Little endian)。

实例:

打开”记事本“程序Notepad.exe,新建一个文本文件,内容就是一个”严“字,依次采用ANSI,Unicode,Unicode big endian 和 UTF-8编码方式保存。

然后,用文本编辑软件UltraEdit中的”十六进制功能“,观察该文件的内部编码方式。

1)ANSI:文件的编码就是两个字节“D1 CF”,这正是“严”的GB2312编码,这也暗示GB2312是采用大头方式存储的。

2)Unicode:编码是四个字节“FF FE 25 4E”,其中“FF FE”表明是小头方式存储,真正的编码是4E25。

3)Unicode big endian:编码是四个字节“FE FF 4E 25”,其中“FE FF”表明是大头方式存储。

4)UTF-8:编码是六个字节“EF BB BF E4 B8 A5”,
前三个字节“EF BB BF”表示这是UTF-8编码,
后三个“E4B8A5”就是“严”的具体编码,它的存储顺序与编码顺序是一致的。

 

Unicode和utf16是一一对应的。他俩都是2个字节。只是传输英文会浪费一倍空间。

 

打开文件 FE FF 表示UTF-16.

"汉"对应的unicode是6C49

UTF-16表示的话就是01101100   01001001(共16 bit,两个字节).UTF-16不需要用啥字符来做标志,所以两字节也就是2的16次能表示65536个字符.

而UTF-8由于里面有额外的标志信息,所有一个字节只能表示2的7次方128个字符,两个字节只能表示2的11次方2048个字符.而三个字节能表示2的16次方,65536个字符.

由于"汉"的编码27721大于2048了所有两个字节还不够,只能用三个字节来表示.

所有要用1110xxxx 10xxxxxx 10xxxxxx这种格式.把27721对应的二进制从左到右(little endia 从右到左)填充XXX符号.

Unicode版本2

    前面说的都是unicode的第一个版本.但65536显然不算太多的数字,用它来表示常用的字符是没一点问题.足够了,但如果加上很多特殊的就也不够了.于是从1996年开始又来了第二个版本.用四个字节表示所有字符.这样就出现了UTF-8,UTF16,UTF-32.原理和之前肯定是完全一样的,UTF-32就是把所有的字符都用32bit也就是4个字节来表示.然后UTF-8,UTF-16就视情况而定了.UTF-8可以选择1至8个字节中的任一个来表示.而UTF-16只能是选两字节或四字节..由于unicode版本2的原理完全是一样的,就不多说了.

前面说了要知道具体是哪种编码方式,需要判断文本开头的标志,下面是所有编码对应的开头标志

EF BB BF    UTF-8
FE FF     UTF-16/UCS-2, little endian
FF FE     UTF-16/UCS-2, big endian
FF FE 00 00  UTF-32/UCS-4, little endian.
00 00 FE FF  UTF-32/UCS-4, big-endian.

其中的UCS就是前面说的ISO制定的标准,和Unicode是完全一样的,只不过名字不一样.ucs-2对应utf-16,ucs-4对应UTF-32.UTF-8是没有对应的UCS

UTF-16 并不是一个完美的选择,它存在几个方面的问题: 

  1. UTF-16 能表示的字符数有 6 万多,看起来很多,但是实际上目前 Unicode 5.0 收录的字符已经达到 99024 个字符,早已超过 UTF-16 的存储范围;这直接导致 UTF-16 地位颇为尴尬——如果谁还在想着只要使用 UTF-16 就可以高枕无忧的话,恐怕要失望了
  2. UTF-16 存在大小端字节序问题,这个问题在进行信息交换时特别突出——如果字节序未协商好,将导致乱码;如果协商好,但是双方一个采用大端一个采用小端,则必然有一方要进行大小端转换,性能损失不可避免(大小端问题其实不像看起来那么简单,有时会涉及硬件、操作系统、上层软件多个层次,可能会进行多次转换)
  3. 另外,容错性低有时候也是一大问题——局部的字节错误,特别是丢失或增加可能导致所有后续字符全部错乱,错乱后要想恢复,可能很简单,也可能会非常困难。(这一点在日常生活里大家感觉似乎无关紧要,但是在很多特殊环境下却是巨大的缺陷)

目前支撑我们继续使用 UTF-16 的理由主要是考虑到它是双字节的,在计算字符串长度、执行索引操作时速度很快。当然这些优点 UTF-32 都具有,但很多人毕竟还是觉得 UTF-32 太占空间了。 

反过来 UTF-8 也不完美,也存在一些问题: 

  1. 文化上的不平衡——对于欧美地区一些以英语为母语的国家 UTF-8 简直是太棒了,因为它和 ASCII 一样,一个字符只占一个字节,没有任何额外的存储负担;但是对于中日韩等国家来说,UTF-8 实在是太冗余,一个字符竟然要占用 3 个字节,存储和传输的效率不但没有提升,反而下降了。所以欧美人民常常毫不犹豫的采用 UTF-8,而我们却老是要犹豫一会儿
  2. 变长字节表示带来的效率问题——大家对 UTF-8 疑虑重重的一个问题就是在于其因为是变长字节表示,因此无论是计算字符数,还是执行索引操作效率都不高。为了解决这个问题,常常会考虑把 UTF-8 先转换为 UTF-16 或者 UTF-32 后再操作,操作完毕后再转换回去。而这显然是一种性能负担。



当然,UTF-8 的优点也不能忘了: 

  1. 字符空间足够大,未来 Unicode 新标准收录更多字符,UTF-8 也能妥妥的兼容,因此不会再出现 UTF-16 那样的尴尬
  2. 不存在大小端字节序问题,信息交换时非常便捷
  3. 容错性高,局部的字节错误(丢失、增加、改变)不会导致连锁性的错误,因为 UTF-8 的字符边界很容易检测出来,这是一个巨大的优点(正是为了实现这一点,咱们中日韩人民不得不忍受 3 字节 1 个字符的苦日子)


那么到底该如何选择呢? 

因为无论是 UTF-8 和 UTF-16/32 都各有优缺点,因此 选择的时候应当立足于实际的应用场景。例如在我的习惯中,存储在磁盘上或进行网络交换时都会采用 UTF-8,而在程序内部进行处理时则转换为 UTF-16/32。对于大多数简单的程序来说,这样做既可以保证信息交换时容易实现相互兼容,同时在内部处理时会比较简单,性能也还算不错。(基本上只要你的程序不是 I/O 密集型的都可以这么干,当然这只是我粗浅的认识范围内的经验,很可能会被无情的反驳) 

稍微再展开那么一点点…… 

在一些特殊的领域,字符编码的选择会成为一个很关键的问题。特别是一些高性能网络处理程序里更是如此。这时采用一些特殊的设计技巧,可以缓解性能和字符集选择之间的矛盾。例如对于内容检测/过滤系统,需要面对任何可能的字符编码,这时如果还采用把各种不同的编码都转换为同一种编码后再处理的方案,那么性能下降将会很显著。而如果采用多字符编码支持的有限状态机方案,则既能够无需转换编码,同时又能够以极高的性能进行处理。当然如何从规则列表生成有限状态机,如何使得有限状态机支持多编码,以及这将带来哪些限制,已经又成了另外的问题了。

 


 

转换编码:

     /** 中文字符串转UTF-8与GBK码示例   
             */  
         public static void tttt() throws Exception {  
        String old = "手机银行";  
          
        //中文转换成UTF-8编码(16进制字符串)  
        StringBuffer utf8Str = new StringBuffer();  
        byte[] utf8Decode = old.getBytes("utf-8");  
        for (byte b : utf8Decode) {  
            utf8Str.append(Integer.toHexString(b & 0xFF));  
        }  
//      utf8Str.toString()=====e6898be69cbae993b6e8a18c  
//      System.out.println("UTF-8字符串e6898be69cbae993b6e8a18c转换成中文值======" + new String(utf8Decode, "utf-8"));//-------手机银行  
          
          
        //中文转换成GBK码(16进制字符串)  
        StringBuffer gbkStr = new StringBuffer();  
        byte[] gbkDecode = old.getBytes("gbk");  
        for (byte b : gbkDecode) {  
            gbkStr.append(Integer.toHexString(b & 0xFF));  
        }  
//      gbkStr.toString()=====cad6bbfad2f8d0d0  
//      System.out.println("GBK字符串cad6bbfad2f8d0d0转换成中文值======" + new String(gbkDecode, "gbk"));//----------手机银行  
          
          
        //16进制字符串转换成中文  
        byte[] bb = HexString2Bytes(gbkStr.toString());  
        bb = HexString2Bytes("CAD6BBFAD2F8D0D0000000000000000000000000");  
        byte[] cc = hexToByte("CAD6BBFAD2F8D0D0000000000000000000000000", 20);  
        String aa = new String(bb, "gbk");  
        System.out.println("aa====" + aa);  
    }  

/**  
 * 把16进制字符串转换成字节数组  
 * @param hexstr  
 * @return  
 */  
public static byte[] HexString2Bytes(String hexstr) {  
    byte[] b = new byte[hexstr.length() / 2];  
    int j = 0;  
    for (int i = 0; i < b.length; i++) {  
        char c0 = hexstr.charAt(j++);  
        char c1 = hexstr.charAt(j++);  
        b[i] = (byte) ((parse(c0) << 4) | parse(c1));  
    }  
    return b;  
}  
  
private static int parse(char c) {  
    if (c >= 'a')  
        return (c - 'a' + 10) & 0x0f;  
    if (c >= 'A')  
        return (c - 'A' + 10) & 0x0f;  
    return (c - '0') & 0x0f;  
}  

   


 

处理content-disposition http header里面。 

 

chrome内部使用utf8来处理。  
if (!base::IsStringASCII(encoded_word)) { // Try UTF-8, referrer_charset and the native OS default charset in turn. if (base::IsStringUTF8(encoded_word)) { *output = encoded_word;//utf8直接返回 } else { base::string16 utf16_output;
//对应未知编码,先转成utf16,在转成utf8输出。字符集charset没有设置,就用系统当前默认的。windows为GBK。windows10可以自己把默认字符集改为UTF8。
if (!referrer_charset.empty() && ConvertToUTF16(encoded_word, referrer_charset.c_str(), &utf16_output)) { *output = base::UTF16ToUTF8(utf16_output); } else { *output = base::WideToUTF8(base::SysNativeMBToWide(encoded_word)); } }

*parse_result_flags |= HttpContentDisposition::HAS_NON_ASCII_STRINGS;

 

electron层:

d:\dev\electron7\src\electron\shell\browser\api\atom_api_download_item.cc

std::string DownloadItem::GetFilename() const {
  return base::UTF16ToUTF8(
      net::GenerateFileName(GetURL(), GetContentDisposition(), std::string(),
                            download_item_->GetSuggestedFilename(),
                            GetMimeType(), "download")
          .LossyDisplayName());
}

std::string DownloadItem::GetContentDisposition() const {
  return download_item_->GetContentDisposition();
}

 

还是把上面的分析disposition跑了一遍,最终到net模块的:

 

base::string16 GetSuggestedFilenameImpl(
    const GURL& url,
    const std::string& content_disposition,
    const std::string& referrer_charset,
    const std::string& suggested_name,
    const std::string& mime_type,
    const std::string& default_name,
    bool should_replace_extension,
    ReplaceIllegalCharactersFunction replace_illegal_characters_function) {
  // TODO: this function to be updated to match the httpbis recommendations.
  // Talk to abarth for the latest news.

  // We don't translate this fallback string, "download". If localization is
  // needed, the caller should provide localized fallback in |default_name|.
  static const base::FilePath::CharType kFinalFallbackName[] =
      FILE_PATH_LITERAL("download");
  std::string filename;  // In UTF-8
  bool overwrite_extension = false;
  bool is_name_from_content_disposition = false;
  // Try to extract a filename from content-disposition first.
  if (!content_disposition.empty()) {
    HttpContentDisposition header(content_disposition, referrer_charset);
    filename = header.filename();
    if (!filename.empty())
      is_name_from_content_disposition = true;
  }

  // Then try to use the suggested name.
  if (filename.empty() && !suggested_name.empty())
    filename = suggested_name;

  // Now try extracting the filename from the URL.  GetFileNameFromURL() only
  // looks at the last component of the URL and doesn't return the hostname as a
  // failover.
  if (filename.empty())
    filename = GetFileNameFromURL(url, referrer_charset, &overwrite_extension);

  // Finally try the URL hostname, but only if there's no default specified in
  // |default_name|.  Some schemes (e.g.: file:, about:, data:) do not have a
  // host name.
  if (filename.empty() && default_name.empty() && url.is_valid() &&
      !url.host().empty()) {
    // TODO(jungshik) : Decode a 'punycoded' IDN hostname. (bug 1264451)
    filename = url.host();
  }

  bool replace_trailing = false;
  base::FilePath::StringType result_str, default_name_str;
#if defined(OS_WIN)
  replace_trailing = true;
  result_str = base::UTF8ToUTF16(filename);
  default_name_str = base::UTF8ToUTF16(default_name);
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
  result_str = filename;
  default_name_str = default_name;
#else
#error Unsupported platform
#endif
  SanitizeGeneratedFileName(&result_str, replace_trailing);
  if (result_str.find_last_not_of(FILE_PATH_LITERAL("-_")) ==
      base::FilePath::StringType::npos) {
    result_str = !default_name_str.empty()
                     ? default_name_str
                     : base::FilePath::StringType(kFinalFallbackName);
    overwrite_extension = false;
  }
  replace_illegal_characters_function(&result_str, '_');
  base::FilePath result(result_str);
  overwrite_extension |= should_replace_extension;
  // extension should not appended to filename derived from
  // content-disposition, if it does not have one.
  // Hence mimetype and overwrite_extension values are not used.
  if (is_name_from_content_disposition)
    GenerateSafeFileName("", false, &result);
  else
    GenerateSafeFileName(mime_type, overwrite_extension, &result);

  base::string16 result16;
  if (!FilePathToString16(result, &result16)) {
    result = base::FilePath(default_name_str);
    if (!FilePathToString16(result, &result16)) {
      result = base::FilePath(kFinalFallbackName);
      FilePathToString16(result, &result16);
    }
  }
  return result16;
}

 

base::FilePath GenerateFileNameImpl(
    const GURL& url,
    const std::string& content_disposition,
    const std::string& referrer_charset,
    const std::string& suggested_name,
    const std::string& mime_type,
    const std::string& default_file_name,
    bool should_replace_extension,
    ReplaceIllegalCharactersFunction replace_illegal_characters_function) {
  base::string16 file_name = GetSuggestedFilenameImpl(
      url, content_disposition, referrer_charset, suggested_name, mime_type,
      default_file_name, should_replace_extension,
      replace_illegal_characters_function);

#if defined(OS_WIN)
  base::FilePath generated_name(file_name);
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
  base::FilePath generated_name(
      base::SysWideToNativeMB(base::UTF16ToWide(file_name)));
#endif

  DCHECK(!generated_name.empty());

  return generated_name;
}

 


 

http网页跳转时调用:

1,

 2

 

 3

 

 4,这里把原始编码做转换,会调用 HttpContentDisposition::HttpContentDisposition的parse函数,调用到

bool DecodeWord(const std::string& encoded_word

 

 5,开始远程调用:

 

 6posttask到

 

 7,转到delegate_->StartDownloadItem

 

 8,新的download走:

GetNextId(base::BindOnce(&DownloadManagerImpl::CreateNewDownloadItemToStart,
weak_factory_.GetWeakPtr(), std::move(info),
on_started, std::move(callback)));

9,再到

download::DownloadItemImpl* download = CreateActiveItem(id, *info);

10,这里还是没转码的值:

 

 从跟踪代码来看,content-disposition存放的是http response的raw header。

在HttpContentDisposition类的filename_成员才会存放转换了的编码。

 

js里面全部是utf8编码,导致gbk的响应头无法输出????

[17240:0923/170223.551:FATAL:values.cc(210)] Check failed: IsStringUTF8(string_value_).
Backtrace:
        base::debug::CollectStackTrace [0x00007FFC111911E0+48] (D:\dev\electron7\src\base\debug\stack_trace_win.cc:284)
        base::debug::StackTrace::StackTrace [0x00007FFC11191180+80] (D:\dev\electron7\src\base\debug\stack_trace.cc:206)
        base::debug::StackTrace::StackTrace [0x00007FFC11183B58+40] (D:\dev\electron7\src\base\debug\stack_trace.cc:203)
        logging::LogMessage::~LogMessage [0x00007FFC11203D2F+143] (D:\dev\electron7\src\base\logging.cc:629)
        base::Value::Value [0x00007FFC114714E2+290] (D:\dev\electron7\src\base\values.cc:211)
        base::Value::Value [0x00007FFC114719AE+62] (D:\dev\electron7\src\base\values.cc:206)
        std::_Default_allocator_traits<std::allocator<base::Value> >::construct<base::Value,std::basic_string<char,std::char_traits<char>,std::allocator<char> > &> [0x00007FF61263DD08+88] (C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.25.28610\include\xmemory:671)
        std::vector<base::Value,std::allocator<base::Value> >::_Emplace_reallocate<std::basic_string<char,std::char_traits<char>,std::allocator<char> > &> [0x00007FF61263DBB2+386] (C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.25.28610\include\vector:750)
        std::vector<base::Value,std::allocator<base::Value> >::emplace_back<std::basic_string<char,std::char_traits<char>,std::allocator<char> > &> [0x00007FF61263D930+128] (C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.25.28610\include\vector:708)
        electron::api::`anonymous namespace'::HttpResponseHeadersToV8 [0x00007FF61263D3B8+392] (D:\dev\electron7\src\electron\shell\browser\api\atom_api_web_request_ns.cc:115)
        electron::api::`anonymous namespace'::ToDictionary [0x00007FF61263C880+1280] (D:\dev\electron7\src\electron\shell\browser\api\atom_api_web_request_ns.cc:138)
        electron::api::`anonymous namespace'::FillDetails<extensions::WebRequestInfo *,network::ResourceRequest> [0x00007FF61263A7E7+55] (D:\dev\electron7\src\electron\shell\browser\api\atom_api_web_request_ns.cc:180)
        electron::api::WebRequestNS::HandleSimpleEvent<network::ResourceRequest> [0x00007FF61262B581+305] (D:\dev\electron7\src\electron\shell\browser\api\atom_api_web_request_ns.cc:414)
        electron::api::WebRequestNS::OnResponseStarted [0x00007FF61262B430+112] (D:\dev\electron7\src\electron\shell\browser\api\atom_api_web_request_ns.cc:316)
        electron::ProxyingURLLoaderFactory::InProgressRequest::ContinueToResponseStarted [0x00007FF612709917+1079] (D:\dev\electron7\src\electron\shell\browser\net\proxying_url_loader_factory.cc:512)
        electron::ProxyingURLLoaderFactory::InProgressRequest::OnReceiveResponse [0x00007FF612709349+265] (D:\dev\electron7\src\electron\shell\browser\net\proxying_url_loader_factory.cc:208)
        network::mojom::URLLoaderClientStubDispatch::Accept [0x00007FF612BE9A24+980] (D:\dev\electron7\src\out\Testing\gen\services\network\public\mojom\url_loader.mojom.cc:1191)
        network::mojom::URLLoaderClientStub<mojo::RawPtrImplRefTraits<network::mojom::URLLoaderClient> >::Accept [0x00007FF6127111F2+98] (D:\dev\electron7\src\out\Testing\gen\services\network\public\mojom\url_loader.mojom.h:296)
        mojo::InterfaceEndpointClient::HandleValidatedMessage [0x00007FFC343B8EEF+1935] (D:\dev\electron7\src\mojo\public\cpp\bindings\lib\interface_endpoint_client.cc:554)
        mojo::InterfaceEndpointClient::HandleIncomingMessageThunk::Accept [0x00007FFC343B8751+33] (D:\dev\electron7\src\mojo\public\cpp\bindings\lib\interface_endpoint_client.cc:140)
        mojo::FilterChain::Accept [0x00007FFC343B73C9+393] (D:\dev\electron7\src\mojo\public\cpp\bindings\lib\filter_chain.cc:40)
        mojo::InterfaceEndpointClient::HandleIncomingMessage [0x00007FFC343BBCD5+213] (D:\dev\electron7\src\mojo\public\cpp\bindings\lib\interface_endpoint_client.cc:357)
        mojo::internal::MultiplexRouter::ProcessIncomingMessage [0x00007FFC343CE6F2+1666] (D:\dev\electron7\src\mojo\public\cpp\bindings\lib\multiplex_router.cc:876)
        mojo::internal::MultiplexRouter::Accept [0x00007FFC343CDC41+673] (D:\dev\electron7\src\mojo\public\cpp\bindings\lib\multiplex_router.cc:598)
        mojo::FilterChain::Accept [0x00007FFC343B73C9+393] (D:\dev\electron7\src\mojo\public\cpp\bindings\lib\filter_chain.cc:40)
        mojo::Connector::DispatchMessageW [0x00007FFC3439F6F4+1396] (D:\dev\electron7\src\mojo\public\cpp\bindings\lib\connector.cc:513)
        mojo::Connector::ReadAllAvailableMessages [0x00007FFC343A0D53+675] (D:\dev\electron7\src\mojo\public\cpp\bindings\lib\connector.cc:589)
        mojo::Connector::OnHandleReadyInternal [0x00007FFC343A088A+378] (D:\dev\electron7\src\mojo\public\cpp\bindings\lib\connector.cc:424)
        mojo::Connector::OnWatcherHandleReady [0x00007FFC343A06FB+27] (D:\dev\electron7\src\mojo\public\cpp\bindings\lib\connector.cc:384)
        base::internal::FunctorTraits<void (mojo::Connector::*)(unsigned int),void>::Invoke<void (mojo::Connector::*)(unsigned int),mojo::Connector *,unsigned int> [0x00007FFC343A90B5+69] (D:\dev\electron7\src\base\bind_internal.h:499)
        base::internal::InvokeHelper<0,void>::MakeItSo<void (mojo::Connector::*const &)(unsigned int),mojo::Connector *,unsigned int> [0x00007FFC343A8FFD+77] (D:\dev\electron7\src\base\bind_internal.h:599)
        base::internal::Invoker<base::internal::BindState<void (mojo::Connector::*)(unsigned int),base::internal::UnretainedWrapper<mojo::Connector> >,void (unsigned int)>::RunImpl<void (mojo::Connector::*const &)(unsigned int),const std::tuple<base::internal::Un [0x00007FFC343A8F91+113] (D:\dev\electron7\src\base\bind_internal.h:672)
        base::internal::Invoker<base::internal::BindState<void (mojo::Connector::*)(unsigned int),base::internal::UnretainedWrapper<mojo::Connector> >,void (unsigned int)>::Run [0x00007FFC343A8E85+101] (D:\dev\electron7\src\base\bind_internal.h:654)
        base::RepeatingCallback<void (unsigned int)>::Run [0x00007FFC34397DA8+104] (D:\dev\electron7\src\base\callback.h:132)
        mojo::SimpleWatcher::DiscardReadyState [0x00007FFC343A36B0+32] (D:\dev\electron7\src\mojo\public\cpp\system\simple_watcher.h:195)
        base::internal::FunctorTraits<void (*)(const base::RepeatingCallback<void (unsigned int)> &, unsigned int, const mojo::HandleSignalsState &),void>::Invoke<void (*const &)(const base::RepeatingCallback<void (unsigned int)> &, unsigned int, const mojo::Hand [0x00007FFC343A3AD6+102] (D:\dev\electron7\src\base\bind_internal.h:399)
        base::internal::InvokeHelper<0,void>::MakeItSo<void (*const &)(const base::RepeatingCallback<void (unsigned int)> &, unsigned int, const mojo::HandleSignalsState &),const base::RepeatingCallback<void (unsigned int)> &,unsigned int,const mojo::HandleSignal [0x00007FFC343A3A36+102] (D:\dev\electron7\src\base\bind_internal.h:599)
        base::internal::Invoker<base::internal::BindState<void (*)(const base::RepeatingCallback<void (unsigned int)> &, unsigned int, const mojo::HandleSignalsState &),base::RepeatingCallback<void (unsigned int)> >,void (unsigned int, const mojo::HandleSignalsSt [0x00007FFC343A39B6+134] (D:\dev\electron7\src\base\bind_internal.h:672)
        base::internal::Invoker<base::internal::BindState<void (*)(const base::RepeatingCallback<void (unsigned int)> &, unsigned int, const mojo::HandleSignalsState &),base::RepeatingCallback<void (unsigned int)> >,void (unsigned int, const mojo::HandleSignalsSt [0x00007FFC343A3853+131] (D:\dev\electron7\src\base\bind_internal.h:654)
        base::RepeatingCallback<void (unsigned int, const mojo::HandleSignalsState &)>::Run [0x00007FFC34A9C281+129] (D:\dev\electron7\src\base\callback.h:132)
        mojo::SimpleWatcher::OnHandleReady [0x00007FFC34A9BE11+753] (D:\dev\electron7\src\mojo\public\cpp\system\simple_watcher.cc:293)
        base::internal::FunctorTraits<void (mojo::SimpleWatcher::*)(int, unsigned int, const mojo::HandleSignalsState &),void>::Invoke<void (mojo::SimpleWatcher::*)(int, unsigned int, const mojo::HandleSignalsState &),base::WeakPtr<mojo::SimpleWatcher>,int,unsign [0x00007FFC34A9CE37+135] (D:\dev\electron7\src\base\bind_internal.h:499)
        base::internal::InvokeHelper<1,void>::MakeItSo<void (mojo::SimpleWatcher::*)(int, unsigned int, const mojo::HandleSignalsState &),base::WeakPtr<mojo::SimpleWatcher>,int,unsigned int,mojo::HandleSignalsState> [0x00007FFC34A9CC96+166] (D:\dev\electron7\src\base\bind_internal.h:622)
        base::internal::Invoker<base::internal::BindState<void (mojo::SimpleWatcher::*)(int, unsigned int, const mojo::HandleSignalsState &),base::WeakPtr<mojo::SimpleWatcher>,int,unsigned int,mojo::HandleSignalsState>,void ()>::RunImpl<void (mojo::SimpleWatcher: [0x00007FFC34A9CBCA+186] (D:\dev\electron7\src\base\bind_internal.h:672)
        base::internal::Invoker<base::internal::BindState<void (mojo::SimpleWatcher::*)(int, unsigned int, const mojo::HandleSignalsState &),base::WeakPtr<mojo::SimpleWatcher>,int,unsigned int,mojo::HandleSignalsState>,void ()>::RunOnce [0x00007FFC34A9C9CE+78] (D:\dev\electron7\src\base\bind_internal.h:641)
        base::OnceCallback<void ()>::Run [0x00007FFC111726B1+97] (D:\dev\electron7\src\base\callback.h:99)
        base::TaskAnnotator::RunTask [0x00007FFC11341335+1605] (D:\dev\electron7\src\base\task\common\task_annotator.cc:144)
        base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl [0x00007FFC113794AC+1804] (D:\dev\electron7\src\base\task\sequence_manager\thread_controller_with_message_pump_impl.cc:366)
        base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoSomeWork [0x00007FFC11378B2F+191] (D:\dev\electron7\src\base\task\sequence_manager\thread_controller_with_message_pump_impl.cc:221)
        base::MessagePumpForUI::DoRunLoop [0x00007FFC11220449+457] (D:\dev\electron7\src\base\message_loop\message_pump_win.cc:217)
        base::MessagePumpWin::Run [0x00007FFC1121EBA4+292] (D:\dev\electron7\src\base\message_loop\message_pump_win.cc:76)
        base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run [0x00007FFC1137A87E+910] (D:\dev\electron7\src\base\task\sequence_manager\thread_controller_with_message_pump_impl.cc:467)
        base::RunLoop::Run [0x00007FFC112DE335+901] (D:\dev\electron7\src\base\run_loop.cc:156)
        content::BrowserMainLoop::MainMessageLoopRun [0x00007FFBFEC4AADD+157] (D:\dev\electron7\src\content\browser\browser_main_loop.cc:1511)
        content::BrowserMainLoop::RunMainMessageLoopParts [0x00007FFBFEC4A8D0+528] (D:\dev\electron7\src\content\browser\browser_main_loop.cc:1031)
        content::BrowserMainRunnerImpl::Run [0x00007FFBFEC500DF+335] (D:\dev\electron7\src\content\browser\browser_main_runner_impl.cc:149)
        content::BrowserMain [0x00007FFBFEC4357C+284] (D:\dev\electron7\src\content\browser\browser_main.cc:47)
        content::RunBrowserProcessMain [0x00007FFC0145D468+168] (D:\dev\electron7\src\content\app\content_main_runner_impl.cc:556)
        content::ContentMainRunnerImpl::RunServiceManager [0x00007FFC0145EC76+1334] (D:\dev\electron7\src\content\app\content_main_runner_impl.cc:963)
        content::ContentMainRunnerImpl::Run [0x00007FFC0145E656+614] (D:\dev\electron7\src\content\app\content_main_runner_impl.cc:871)
        content::ContentServiceManagerMainDelegate::RunEmbedderProcess [0x00007FFC01459BA7+55] (D:\dev\electron7\src\content\app\content_service_manager_main_delegate.cc:52)
        service_manager::Main [0x00007FFBDA202323+1731] (D:\dev\electron7\src\services\service_manager\embedder\main.cc:423)
        content::ContentMain [0x00007FFC0145D32F+95] (D:\dev\electron7\src\content\app\content_main.cc:20)
        wWinMain [0x00007FF6123C20BC+1244] (D:\dev\electron7\src\electron\shell\app\atom_main.cc:168)
        invoke_main [0x00007FF6150A6B42+50] (d:\agent\_work\4\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:123)
        __scrt_common_main_seh [0x00007FF6150A6C7E+302] (d:\agent\_work\4\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288)
        __scrt_common_main [0x00007FF6150A6CFE+14] (d:\agent\_work\4\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:331)
        wWinMainCRTStartup [0x00007FF6150A6D19+9] (d:\agent\_work\4\s\src\vctools\crt\vcstartup\src\startup\exe_wwinmain.cpp:17)
        BaseThreadInitThunk [0x00007FFC4B4C7BD4+20]
        RtlUserThreadStart [0x00007FFC4C86CE51+33]
Task trace:
Backtrace:
        mojo::SimpleWatcher::Context::Notify [0x00007FFC34A9C590+528] (D:\dev\electron7\src\mojo\public\cpp\system\simple_watcher.cc:120)
IPC message handler context: 0x51C3F41F

 


在electron中,当http服务器返回http header是gbk的content-disposition下载时,文件名保存乱码。在electron调用时注册了onHeadersReceived回调。(写在app.whenReady().then(function xx)里面)

 

//win.webContents.session.
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
                            // logger.debug(`onHeadersReceived: url:${details.url}, details:${JSON.stringify(details)}`);
console.log("========= electron onHeadersReceived ====================")
/*
                            for (const key in details.responseHeaders) {
                                const lowerKey = key.toLowerCase();
                                switch (lowerKey) {
                                    case 'x-frame-options':
                                    case 'content-security-policy':
                                        // logger.debug(`Received header ${key}: ${details.responseHeaders[key]}`);
                                        delete details.responseHeaders[key];
                                        break;
                                }
                            }
*/
//                            console.log(`Response headers: ${JSON.stringify(details.responseHeaders)}`);
                            callback({
                                cancel: false,
                                responseHeaders: details.responseHeaders,
                                statusLine: details.statusLine,
                            });
                        });

    

调查发现是这里会检查我们要做的编码为utf8:

 

 它是这里上层调用的:

 

 最后我们看到在做gin::ConvertToV8转码时,对String会用UTF8编码:

 


 

// Returns the filename determined from the last component of the path portion
// of the URL.  Returns an empty string if the URL doesn't have a path or is
// invalid. If the generated filename is not reliable,
// |should_overwrite_extension| will be set to true, in which case a better
// extension should be determined based on the content type.
std::string GetFileNameFromURL(const GURL& url,
                               const std::string& referrer_charset,
                               bool* should_overwrite_extension) {
  // about: and data: URLs don't have file names, but esp. data: URLs may
  // contain parts that look like ones (i.e., contain a slash).  Therefore we
  // don't attempt to divine a file name out of them.
  if (!url.is_valid() || url.SchemeIs("about") || url.SchemeIs("data"))
    return std::string();

  std::string unescaped_url_filename =
      UnescapeBinaryURLComponent(url.ExtractFileName(), UnescapeRule::NORMAL);

  // The URL's path should be escaped UTF-8, but may not be.
  std::string decoded_filename = unescaped_url_filename;
  if (!base::IsStringUTF8(decoded_filename)) {
    // TODO(jshin): this is probably not robust enough. To be sure, we need
    // encoding detection.
    base::string16 utf16_output;
    if (!referrer_charset.empty() &&
        ConvertToUTF16(unescaped_url_filename, referrer_charset.c_str(),
                       &utf16_output)) {
      decoded_filename = base::UTF16ToUTF8(utf16_output);
    } else {
      decoded_filename =
          base::WideToUTF8(base::SysNativeMBToWide(unescaped_url_filename));
    }
  }
  // If the URL contains a (possibly empty) query, assume it is a generator, and
  // allow the determined extension to be overwritten.
  *should_overwrite_extension = !decoded_filename.empty() && url.has_query();

  return decoded_filename;
}

 

去掉escape字串: D:\dev\electron7\src\net\base\escape.h