Repository: electron/electron (language: cpp)

Issue:
[Bug]: Windows showSaveDialog invalid filename bug
### Preflight Checklist

- [x] I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) for this project.
- [x] I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) that this project adheres to.
- [x] I have searched the [issue tracker](https://www.github.com/electron/electron/issues) for a bug report that matches the one I want to file, without success.

### Electron Version

33.0.0

### What operating system(s) are you using?

Windows

### Operating System Version

Windows 11 22H2 22621.4317

### What arch are you using?

x64

### Last Known Working Electron version

None

### Expected Behavior

1. Open the save dialog using dialog.showSaveDialog.
2. Enter a file name containing illegal characters (e.g., 111*222).
- Expected: The dialog correctly shows a warning that the file name is invalid due to the presence of illegal characters*.
3. Remove the illegal characters* (e.g., change 111*222 to 111222).
4. Attempt to save the file without adding an extension.
- Expected: The dialog should now allow saving the file since all illegal characters have been removed, even if no file extension is added.

### Actual Behavior

1. Open the save dialog using dialog.showSaveDialog.
2. Enter a file name containing illegal characters* (e.g., 111*222).
- Actual: The dialog provides no error message or feedback.
3. Remove the illegal characters* (e.g., change 111*222 to 111222).
4. Attempt to save the file without adding an extension.
- Actual: The dialog shows the file name as invalid, even though all illegal characters have been removed.
5. Now, modify the file name to include a valid extension (e.g., 111222.xml).
- Actual: The file saves successfully when an extension is added, indicating that the dialog behaves inconsistently by requiring an extension even for valid file names.

### Testcase Gist URL

_No response_

### Additional Information

Below is a minimal code example to reproduce the issue:

Sample Code:
```
const fs = require('fs');
const { dialog } = require('electron');

const saveFileDialog = dialog.showSaveDialog({
  title: '保存 XML 文件',
  filters: [
    { name: 'XML 文件', extensions: ['xml'] }
  ]
});

saveFileDialog.then(result => {
  if (!result.canceled) {
    const filePath = result.filePath;
    console.log(`文件将保存到: ${filePath}`);

    // 你要保存的 XML 内容
    const xmlContent = `
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <item>
    <name>示例数据</name>
    <value>123</value>
  </item>
</root>`;

    // 将 XML 内容写入文件
    fs.writeFile(filePath, xmlContent, (err) => {
      if (err) {
        console.error('保存文件时出错:', err);
      } else {
        console.log('文件保存成功!');
      }
    });
  } else {
    console.log('用户取消了保存操作');
  }
});

```
This behavior only occurs in Electron applications, including those built using Electron, such as Visual Studio Code, QQ,WeChat PC (version 4.0) and other apps.
However, Windows built-in applications like Notepad or third-party editors such as Notepad++ do not exhibit this behavior. In these apps, once illegal characters are removed from the file name, the modified name is accepted without requiring a file extension.

This inconsistency suggests that the showSaveDialog function in Electron has stricter handling or some kind of caching issue when validating file names, leading to unexpected behavior. This issue affects the user experience, as users are required to manually add file extensions even when they expect the file to save without one.

This issue seems to be related to or may have similarities with the following issue reported previously:
[#25615 - SaveDialog doesn’t allow saving a file with some extensions on Windows](https://github.com/electron/electron/issues/25615).

Both issues may indicate an underlying problem in how the dialog.showSaveDialog function interacts with Windows file system rules.

![Image](https://github.com/user-attachments/assets/3f6bd0c9-5eeb-4642-9a97-1fb488ab573f)
![Image](https://github.com/user-attachments/assets/297e7f88-d86e-430d-9267-8a41676952d2)
![Image](https://github.com/user-attachments/assets/7ff1b07d-75a0-44fa-9920-728e4afbd125)
![Image](https://github.com/user-attachments/assets/ef1b6db9-1ea7-4bd5-8737-9e589b3ee840)






Discussion / hints:
@dfvips does this happen in Chrome itself?
> [@dfvips](https://github.com/dfvips) does this happen in Chrome itself?

I just tested it in Chrome, and everything worked fine. This issue does not occur in Chrome itself, it only happens in Electron applications. But if Chrome doesn't specify an extension, it will be saved successfully without a file extension. 
![Image](https://github.com/user-attachments/assets/302b6ea0-6754-4e9e-bbe0-0004f6107ffb)
![Image](https://github.com/user-attachments/assets/039adef6-2af4-4709-9275-19b0cf37d1d1)

<video src="https://github.com/user-attachments/assets/a4d23bcd-0296-4151-9b95-45db70689303" autoplay muted controls></video>
<video src="https://github.com/user-attachments/assets/e2e9aacd-aed0-4211-b6af-fc4ff40f4714" autoplay muted controls></video>


Fix patch:
diff --git a/shell/browser/ui/file_dialog_win.cc b/shell/browser/ui/file_dialog_win.cc
index cfc8c6068c648..e142477ad0820 100644
--- a/shell/browser/ui/file_dialog_win.cc
+++ b/shell/browser/ui/file_dialog_win.cc
@@ -144,10 +144,14 @@ static void ApplySettings(IFileDialog* dialog, const DialogSettings& settings) {
   // We set file extension to the first none-wildcard extension to make
   // sure the dialog will update file extension automatically.
   for (size_t i = 0; i < filterspec.size(); ++i) {
-    if (std::wstring(filterspec[i].pszSpec) != L"*.*") {
+    std::wstring spec(filterspec[i].pszSpec);
+    if (spec != L"*.*") {
       // SetFileTypeIndex is regarded as one-based index.
       dialog->SetFileTypeIndex(i + 1);
-      dialog->SetDefaultExtension(filterspec[i].pszSpec);
+      // "*.jpg;*.png" => "*.jpg"
+      std::wstring first_spec = spec.substr(0, spec.find(L';'));
+      // "*.jpg" => "jpg"
+      dialog->SetDefaultExtension(first_spec.substr(2).c_str());
       break;
     }
   }


Test patch: