天天看點

HTTP協定header中Content-Disposition中文檔案名亂碼

chromium 儲存檔案名亂碼 content-disposition

 從跟蹤代碼來看,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存成檔案為: 

HTTP協定header中Content-Disposition中文檔案名亂碼

而“中文”的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,

HTTP協定header中Content-Disposition中文檔案名亂碼

 2

HTTP協定header中Content-Disposition中文檔案名亂碼

 3

HTTP協定header中Content-Disposition中文檔案名亂碼

 4,這裡把原始編碼做轉換,會調用 HttpContentDisposition::HttpContentDisposition的parse函數,調用到

bool DecodeWord(const std::string& encoded_word

HTTP協定header中Content-Disposition中文檔案名亂碼

 5,開始遠端調用:

HTTP協定header中Content-Disposition中文檔案名亂碼

 6posttask到

HTTP協定header中Content-Disposition中文檔案名亂碼

 7,轉到delegate_->StartDownloadItem

HTTP協定header中Content-Disposition中文檔案名亂碼

 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,這裡還是沒轉碼的值:

HTTP協定header中Content-Disposition中文檔案名亂碼

 從跟蹤代碼來看,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:

HTTP協定header中Content-Disposition中文檔案名亂碼

 它是這裡上層調用的:

HTTP協定header中Content-Disposition中文檔案名亂碼

 最後我們看到在做gin::ConvertToV8轉碼時,對String會用UTF8編碼:

HTTP協定header中Content-Disposition中文檔案名亂碼
// 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