从发现.NET Framework中SmtpClient的Bug并拿出解决方案,然后给微软开发者社区提交Bug开始,总共耗时一个多月,对Bug修复的代码最终被采纳,现已合并到.NET Core Libraries (CoreFX)主线中。

修复记录https://github.com/dotnet/corefx/commit/94b1f1eae84fd4823cfa2bbdde6fc87c46b57908

虽然对Bug修复实际生效的代码只有20个字符,但意义重大,这个Bug不遇上一点事没有,遇上了不对框架开刀是非常棘手的,而且备受折磨也稍微有点难摸得清头脑。

相关详情见我的另外一篇博客《记录一次.Net框架Bug发现和提交过程:.Net Framework和.Net Core均受影响

问题描述

我们用SmtpClientSendAsyncSendMailAsync异步方法发送邮件,并且要求使用DeliveryFormat = SmtpDeliveryFormat.SevenBit 格式来编码中文内容,本来预期是邮件内容中带中文的SubjectAttachments file name 都会进行Base64编码。

但实际结果是:如果邮件服务器支持SMTPUTF8扩展,那么异步发送SevenBit邮件并不会进行Base64编码,同步方法没有此问题。

问题根源

原因是在SmtpClient.SendMailCallback方法中,message.BeginSend allowUnicode参数直接使用的ServerSupportsEai,而不是统一的IsUnicodeSupported()

 private void SendMailCallback(IAsyncResult result)
{
......
_message.BeginSend(_writer, DeliveryMethod != SmtpDeliveryMethod.Network,
ServerSupportsEai, new AsyncCallback(SendMessageCallback), result.AsyncState);
......
}

ServerSupportsEai改成IsUnicodeSupported()问题解决。就这20个字符的改动~

另外附对现有带Bug的.Net框架修复方法

比如使用的.NET Framework 4.7.2,纯天然原生自带此Bug,我们可以用我们的代码修复它。

最开始测试时以为此方法无效,没想到是Hook错了地方,换到最深层次调用地方,一抓一个准。

使用DotNetDetour库对.Net框架内方法进行Hook,找出SmtpClient.ServerSupportsEai最结果最终是从SmtpConnection.ServerSupportsEai得来的,也许是C#编译后把整个调用过程都优化掉了,变成了取值的地方直接调用的SmtpConnection中的方法,导致Hook前面的方法都是不会被执行,Hook SmtpConnection.ServerSupportsEai一抓一个准。

附上Hook代码:

public class Hook : IMethodMonitor {
public bool ServerSupportsEai {
[Monitor("System.Net.Mail", "SmtpConnection")]
get {
Console.WriteLine("Hook");
return !true?org():false;//什么情况下要Hook? AsyncLocal和CallContext上下文为什么在这里传不进来?
}
}
[Original]
public bool org() {
return false;
}
}

另外引出了另外一个折磨人Bug,异步环境下,ServerSupportsEai的调用栈中上下文怎么会丢失?难道哪里使用了类似ThreadPool.UnsafeXXX这种效果?我们没法通过CallContext(AsyncLocal)给Hook代码传参数,只能写死,不管调用方要不要修改返回值,都只能得到修改后的结果,尴尬不尴尬。

从.Net框架Bug的提交到修复代码成功合并到.NET CoreFX主线的更多相关文章

  1. 记录一次.Net框架Bug发现和提交过程:.Net Framework和.Net Core均受影响

    SmtpClient一处代码编写错误导致异步发送邮件时DeliveryFormat配置项无法正确工作,异步操作已经完全不受我们设置属性控制了,UTF-8内容(如中文)转不转码完全看对方邮件服务器心情! ...

  2. Bug报告提交规范

    首先声明,bug的测试规范应该在公司的正式文档建立.本建议非正式文档,有些内容可能不正确,有些内容可能需要继续商榷,甚至有些内容同公司规范有冲突.如果发现问题,直接忽略本文相应内容.本帖本意仅就工作中 ...

  3. tp框架表单提交注意!不要提交到当前方法

    tp框架  表单提交到当前方法,会重复执行显示部分和保存部分的代码.导致不知名的错误.

  4. github上测试服出现bug,如何回滚并获得合并之前的分支

    使用场景: 当我们提交了一个pr,但是该pr合并之后,经过在测试测试有问题,需要回滚.这个时候主master代码将会被回滚到提交你的pr之前的代码.而你的pr由于已经被合并过了,所以无法继续提交. 这 ...

  5. FDMemTable三层提交数据总是不成功的原因

    提交数据的代码如下: procedure TForm1.btnSaveClick(Sender: TObject);var LDeltas: TFDJSONDeltas;begin if FDMemT ...

  6. 意外作出了一个javascript的服务器,可以通过js调用并执行任何java(包括 所有java 内核基本库)及C#类库,并最终由 C# 执行你提交的javascript代码! 不敢藏私,特与大家分

    最近研发BDC 云开发部署平台的数据路由及服务管理器意外作出了一个javascript的服务器,可以通过js调用并执行任何java(包括 所有java 内核基本库)及C#类库,并最终由 C# 执行你提 ...

  7. 从bug中学习怎么写代码

    博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:从bug中学习怎么写代码.

  8. github的拉取、提交,创建分支与合并

    前期准备: 1.安装git  官网地址:https://git-scm.com/(下载下来,直接下一步)   2.github账号(这有点废话)   3.配置github密钥 下载及安装好git后,右 ...

  9. 基于Gitlab统计代码行--统计所有仓库、所有提交人的代码总行数(新增加-删除)

    公司绩效考核要求,统计GITLAB仓库所有人提示有效代码行业 脚本1: 统计所有仓库.所有提交人的代码总行数(新增加-删除) 脚本2: 统计所有仓库.所有提交人的代码提交汇总与删除汇总 脚本3: 统计 ...

随机推荐

  1. c++趣味之变量名,颠覆所有教科书的VisualStudio

    GCC不参与这次的趣味. 所有的教程都会告诉你,c++的变量名,类名,函数名都应该是字母或下划线开头的字母.数字.下划线组合,像这样: int _abc123; 实际上,VisualStudio并不遵 ...

  2. Spark GraphX快速入门

    GraphX是Spark用于图形并行计算的新组件.在较高的层次上,GraphX通过引入一个新的Graph抽象来扩展Spark RDD:一个定向的多图,其属性附加到每个定点和边.为了支持图计算,Grap ...

  3. win7 中 sql server2005 卸载简介

    注:卸载前一定要做好备份,一定要清理干净,不然重装会出错(只针对完全卸载,没试过只删除一个版本的) 工具:①Windows Install Clean Up  ②SrvInstw.exe 1.停止所有 ...

  4. php curl参数详解之post方法

    利用记录的URL参数解释,写一个post方法: <?php function do_post($url, $data) { $ch = curl_init(); //设置CURLOPT_RETU ...

  5. Glyphicons 字体图标

  6. 《Java大学教程》—第10章 图形和事件驱动程序

    10.2 Swing程序包AWT(Abstract Window Toolkit): 抽象窗口工具集.依赖于本地操作系统的组件被称为重量级(Heavy Weight)组件,因为它们都会大量使用系统资源 ...

  7. vue中父组件调用子组件函数

    用法: 子组件上定义ref="refName",  父组件的方法中用 this.$refs.refName.method 去调用子组件方法 详解: 父组件里面调用子组件的函数,父组 ...

  8. C#异步编程のTask模型返回值Task<TResult>应用

    文中所有Task<TResult>的返回值都是直接用task.result获取,这样如果后台任务没有执行完毕的话,主线程会等待其执行完毕,这样的话就和同步一样了(看上去一样,但其实awai ...

  9. nginx.exe启动错误:bind() to 0.0.0.0:80 failed (10013: An attempt was made to access a socket in a way forbidden by its access permissions)

    启动nginx.ese之后 nginx: [emerg] bind() to 0.0.0.0:80 failed (10013: An attempt was made to access a soc ...

  10. esp8266驱动液晶屏

    ESP8266 + 1.44 TFT LCD https://www.joaquim.org/esp8266-wifi-scan/ LCD ILI9341 (320×240). Source Code ...