同事之前给我提了一个需求,想实现在网页里点击链接地址后直接打开指定的地址(路径是内网共享的目录,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或其它客户端软件,基本上也是使用类似的原理,实现点击网页链接呼起本地的客户端应用程序(应用程序想干嘛就自己实现)
注意点击时,会弹出一个提示。