自动下载完整的.NET源代码

    从去年MS就开始喊着要开放.NET源代码,如今终于在一个含糊不清的授权协议下,通过Source Server的方式放了出来。虽然是如愿开源,但目前只提供几个核心模块,而且还限制必须在VS2008里面在线访问。
    其实既然决定开放,何必搞那么多限制出来,让偶等用着不爽还得自己动手丰衣足食,写个程序直接模拟VS2008获取源码的行为,一次性全部拖回来备用。
    空口无凭,上载一个
System.Xml 的源码包,和一个理论上能跑的程序原型,有兴趣的朋友可以试试。回头有时间了把相关代码整理一下再放出来,下面先大概说一下思路。

    首先,VS2008虽然要
打个补丁才能用源码调试,但这并不是下载源码的必要条件。MS发布源码所依赖的Source Server的机制,很早就通过 WinDbg 的发布包引入了。有兴趣的朋友可以使用 WinDbg 工具自己架设 Source Server,具体步骤请参考 %Debugging Tools for Windows%\sdk\srcsrv\ 目录下文档和脚本。
    而这个 Source Server 说白就是从 .pdb 调试符号文件里面,获取被绑定的已索引文件列表,然后根据系统_NT_SOURCE_PATH环境变量指定的服务器,拼接出一个 URL 然后通过http下载。具体这块的配置,可以参考
    Configuring Visual Studio to Debug .NET Framework Source Code
    讨厌的是MS在dbghelp.dll里面做了一些手脚,直接通过url去下载调试符号和源码是不被允许的。刚好scz前面研究过symbol server的行为,基本确定ms服务端是根据user agent来进行判断的。也就是说在发起请求时,把当前http的user agent改成ms认可的方式,就能够模拟dbghelp.dll通过srcsrv.dll完成的工作,具体代码类似

[code:c#]

public const string DefaultUserAgent = "Microsoft-Symbol-Server/9.9.9.9 (C; V; ;)";

    public void Restart(Uri uri)
    {
      _request = (HttpWebRequest)WebRequest.Create(uri);
      _request.UserAgent = DefaultUserAgent;   

      IAsyncResult result = (IAsyncResult)_request.BeginGetResponse(
        new AsyncCallback(ResponseCallback), this);
      ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle,
        new WaitOrTimerCallback(TimeoutCallback), this, _timeout, true);     
    }

[/code]

    另外一个罗嗦的事情,是每个链接建立后,MS服务器会先返回一个罗嗦的license声明,srcsrv.dll会弹出一个对话框让你Accept才能继续下载。
    例如你通过命令行去下载一个文件

wget  --user-agent=Microsoft-Symbol-Server/9.9.9.9
    Http://ReferenceSource.microsoft.com/source/.net/8.0/DEVDIV/
    depot/DevDiv/releases/whidbey/REDBITS/ndp/fx/src/Misc/
    InvariantComparer.cs/1/InvariantComparer.cs

    会直接返回一对垃圾信息,类似

SYMSRVCOMMAND:
YESNODIALOG:
IDYES:Accept:Accept=0cf80f849aa7449e9d064ade971bd887
IDNO:Decline:Decline=0cf80f849aa7449e9d064ade971bd887
TITLE:End User License Agreement
BUTTONDESCRIPTION:Please read carefully and understand the license agreement above. 
If you want to accept the license agreement, please click the “Accept” button.
TEXT:This license applies to the .NET Framework components that Microsoft makes available to you
in source code form.  To print these terms, select the contents of this area and copy then paste
into an application that can print text.

MICROSOFT .NET FRAMEWORK REFERENCE LICENSE
.NET FRAMEWORK REFERENCE SOURCE CODE


This license governs use of the accompanying software. If you use the software, you accept this license.
If you do not accept the license, do not use the software.
1. Definitions
The terms "reproduce," "reproduction" and "distribution" have the same meaning here as under U.S. copyright law.

    解决方法很简单,直接用正则表达式找到里面一个随机生成的id,然后程序自动Accept即可,代码类似

[code:c#]

private static Regex _accept = new Regex(@"IDYES:Accept:Accept=(\w*)");

          if (task._auth)
          {
          }
          else
          {
            string license = Encoding.Unicode.GetString(task._buf, 0, read);

            Match m = _accept.Match(license);

            if (m.Success)
            {

              task._auth = true;

              string id = m.Groups[1].Value;

              _logger.DebugFormat("Accept license id {0} for {1}", id, task._request.Address);

              Uri uri = new Uri(string.Format("{0}?Accept={1}", task._uri.ToString(), id));

              task.Restart(uri);
            }
          }

[/code]

其他的基本上没什么需要说明的,就是很基本的http请求调用啥的,呵呵

至于从.pdb里面获取文件名列表,需要调用dbghelp.dll里面的一组api
SymInitializeW/SymCleanup用来初始化和析构symbol engine
SymLoadModuleExW/SymUnloadModule64用来加载要便利源代码的模块
SymEnumSourceFilesW用来遍历一个有.pdb符号且包含已索引源码的列表
SymGetSourceFileW和SymGetSourceFileTokenW/SymGetSourceFileFromToken两套API,都可以用来获得特定源码的下载URL,然后把这个地址用上面两个方法处理一下即可。

注意:目前的实现只是个原型,需要手动下载和配置.pdb文件,并且直接发起大量的异步http请求,对网速慢的朋友,可能需要用 -i 参数增加超时时间(确实15分钟)