同事之前给我提了一个需求,想实现在网页里点击链接地址后直接打开指定的地址(路径是内网共享的目录,file://share.xx.com\x\x)。
浏览器因为有安全的限制,是无法访问 web 页面时,可以打开本地PC的目录。当你点击带有本地文件链接的超链接(file://),控制台上会报错:Not allowed to load local resource:
最开始在网上搜索了一下,有二个插件看上去似乎可以满足需求。
(1)Enable local file links
(2)Local Explore – File Manager on web browser
插件启用后,类似下面这种效果(跟他们想要的效果还是有区别)。
Local Explore,自定义了协议,然后呼起本地 exe,再打开资源管理器,是期望的效果。但是它最大的问题是:如果路径有中文,就乱码,无法正常使用。
它的原理倒是比较简单,修改超链接为 LocalExplore:file://xxxx,如果注册表添加了对该协议的监听,当浏览器访问该协议时,会触发指定的 exe 并传入相关的参数。
我要解决乱码问题,先处理浏览器扩展插件,再就是替换 exe 的实现就可以了。
(1)替换插件,解决因插件 escape 导致的乱码问题(注:不太能理解作者为啥要用 JSON.parse 处理一下)
对比了二个插件的实现,我准备在 Local Explore 插件的基础上进行精简。只留下必要的代码,然后通过开发者模式,加载进 chrome 的扩展程序里。
background.js 里的代码被我删光了,content.js 只进行一个操作,遍历文档所有超链接,然后修改其 href 属性。
$(document).ready(function() { var optOpenFolders = "true"; var protocolStr = "LocalExplorer:"; // var clickEventHandler = function(evt) { // evt.preventDefault(); // chrome.extension.sendMessage({ // cmd: "click", // data: this.href // }); // }; $("a").each(function(i, e) { if (e.href !== undefined && e.href !== null && e.href !== "") { if (!e.href.match(/^file:\/\//)) { return; } if (e.href.match(/^file:\/\//)) { if (window.debug) console.log(e.href); e.href = protocolStr + e.href; if (e.target) e.target = "_self"; } // $(e).off("click", clickEventHandler); // $(e).on("click", clickEventHandler); } }); });
manifest.json 也做了一点修改
{ "update_url": "https://clients2.google.com/service/update2/crx", "version": "2021.7.6", "short_name": "Meteoric Local Explorer", "name": "Meteoric Local Explorer - File Manager on web browser", "description": "__MSG_extDescription__", "default_locale": "zh_CN", "icons": { "128": "icon/icon128.png", "32": "icon/icon32.png", "16": "icon/icon16.png" }, "browser_action": { "default_icon": "icon/icon32.png", "default_title": "Meteoric Local Explorer" }, "content_scripts": [{ "matches": ["<all_urls>"], "js": ["jquery.js", "content.js"], "all_frames": false, "run_at": "document_start" }], "background": { "scripts": ["background.js"] }, "options_page": "", // options.html "permissions": [ "http://*/*", "https://*/*", "tabs" ], "manifest_version": 2 }
(2)替换 exe,支持打开中文链接
这里我直接用 C# 简单写了一个 exe,实现了基本的功能。为了精简 Release 版本生成的内容,我对几处设置作了简单的调整
(1)项目属性里面的,生成 –> 高级 –> 高级生成设置,输出 –> 调试信息,选择无,避免生成 pdb 文件;
(2)App.config 的文件属性,修改‘生成操作’为‘嵌入的资源’,避免生成 *.exe.config 文件;
这样生成的 Release 目录就是比较干净的了,只有一个叫 LocalExplorer.exe 的文件。替换掉安装在 C 盘里面的 exe(默认路径在:"C:\Program Files (x86)\LocalExplorer\LocalExplorer.exe" )
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace FriendTimesLocalExplorer { class Program { static void Main(string[] args) { /* for (int i = 0, len = args.Length; i < len; i++) { // Get first param } */ if (args.Length < 1) { MessageBox.Show("File Path is Empty", "System Tips", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } string filePath = args[0]; // escape filePath = Uri.UnescapeDataString(filePath).Replace("/", "\\"); // delete protocol filePath = filePath.Replace("localexplorer:", ""); filePath = filePath.Replace("file:", "");
// get right file path filePath = filePath.Substring(filePath.IndexOf(‘\\‘)); //Console.WriteLine(filePath); if (Directory.Exists(filePath) || File.Exists(filePath)) { Process p = new Process(); p.StartInfo.FileName = "explorer.exe"; p.StartInfo.CreateNoWindow = true; p.StartInfo.UseShellExecute = false; // hidden p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; p.EnableRaisingEvents = true; p.StartInfo.RedirectStandardError = true; if (File.Exists(filePath)) { p.StartInfo.Arguments = @"/select," + filePath; } else { p.StartInfo.Arguments = filePath; } try { p.Start(); // explorer.exe 异常结束时,会导致启动不断重启 p.WaitForExit(); if (p != null) { p.Close(); p.Dispose(); p = null; } } catch (Exception e) { MessageBox.Show(e.ToString(), "System error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } else { MessageBox.Show("Not Found Path : \n" + filePath, "System error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } }
浏览器插件 和 exe 都进行替换后,就能实现点击链接调用本地exe,再通过 exe 打开指定的目录了。迅雷、QQ或其它客户端软件,基本上也是使用类似的原理,实现点击网页链接呼起本地的客户端应用程序(应用程序想干嘛就自己实现)
注意点击时,会弹出一个提示。