简介:eWebEditor 11.0 中文商业版是一套面向 Web 开发者的所见即所得在线编辑器,适合需要在后台系统中集成图文混排、新闻发布、通告编辑与讨论贴撰写能力的项目使用。它内置 Word 导入功能,可减少从文档粘贴内容时的格式整理工作,对中小企业建站、CMS 后台及 ASP.NET 项目集成较有参考价值,对需要快速上线内容编辑模块的开发者较为实用。压缩包共 718 个文件,约 30.09MB,其中 gif、css、jpg 数量较多,主要用于界面图标、皮肤样式与图片素材;htm、aspx、js 构成示例页面和前端交互逻辑,另有 dll、exe、config、sln 等运行组件与项目文件,目录结构较完整,便于按模块查阅和二次开发。已有 872 人学习下载。借助配置、分页、登录、上传、菜单等示例页面,读者可较快理解编辑器的集成方式、上传流程与界面定制思路,也可作为了解经典在线编辑器架构与前后端配合方式的参考样本。
1. 老 OA 里的公文粘贴之痛:eWebEditor 11.0 中文商业版到底解决什么问题
接手一套跑了十来年的内网 OA,最先出问题的往往不是业务逻辑,而是编辑器:业务科室把一份带三线表、页眉、自动编号的公文粘进来,格式全散、图片变成红叉、提交后数据库里塞满mso-开头的垃圾样式。eWebEditor 11.0 中文商业版就是针对这个场景做的——纯 ASP 后端,体积小,挂在 IIS 上就能跑,粘贴 Word 内容和内置的 Word 导入按钮两条链路都在。它不适合新立项的系统,但适合手里压着一堆 ASP 老系统、又必须让业务方继续贴公文的人。下面按环境、Word 导入链路、权限收口、排错、清洗脚本一层层拆。
2. 最小可运行环境:IIS + ASP 把编辑器从白屏跑到能提交
很多人拿到包,解压丢进网站根目录,浏览器一开就是白屏或者 500,第一反应是「包坏了」。实际排查下来,八成是三个环境问题:IIS 根本没装 ASP 功能、应用池跑在 64 位模式下而组件是 32 位的、上传目录没有写权限。这三个问题都不会给你明确的报错,只会给你一个空白页面和一条 500.100。所以先把环境跑通,再谈配置,顺序别反过来。
2.1 认目录:改哪几个文件就够,哪些别碰
打开压缩包,目录结构大致如下。不同渠道拿到的包在命名上会有出入,但职责划分基本一致,按职责认比按文件名认可靠。
| 路径 | 职责 | 是否需要改 |
|---|---|---|
| 编辑器入口页(根目录下的 asp 文件) | 接收 id、style 参数,输出编辑器界面 | 一般不动 |
| 样式配置目录 | 工具栏按钮、宽高、上传规则、过滤开关 | 必改 |
| 后台管理目录 | 登录、样式管理、上传文件管理 | 改路径 + 加访问限制 |
| 上传落盘目录 | 图片、附件实际存放位置 | 改 ACL,单独限制解析 |
| 数据库目录 | Access 库或 SQL 连接串 | 换库时必改 |
| 后端脚本目录 | 上传、保存、文档转换等处理脚本 | 按需改 |
只有一个原则:样式配置是最该动的,入口页是最不该动的。入口页里往往写死了相对路径和会话校验,改了它,后面每一个功能点都得跟着改。样式配置则是设计出来给你改的——它决定了这个编辑器在你系统里长什么样、能传什么、留不留 Word 样式。
2.2 打开 ASP 与 32 位支持,把目录权限给对
环境这一步不建议全靠图形界面点,命令能省掉大量来回确认。在服务器上以管理员身份执行下面几条:
# 1. 安装 IIS 的 ASP 与 ISAPI 扩展功能(已装会提示已存在,忽略即可) dism /online /enable-feature /featurename:IIS-ASP /all /norestart dism /online /enable-feature /featurename:IIS-ISAPIExtensions /all /norestart # 2. Word 转换组件是 32 位的,应用池必须开 32 位模式 %windir%\system32\inetsrv\appcmd set apppool "你的应用池名" /enable32BitAppOnWin64:true # 3. 上传目录给应用池账户写权限,但不要给执行权限 icacls "D:\wwwroot\editor\uploadfile" /grant "IIS_IUSRS:(OI)(CI)M" /T icacls "D:\wwwroot\editor\uploadfile" /deny "IIS_IUSRS:(OI)(CI)X" /T # 4. 应用池回收,让 32 位设置生效 %windir%\system32\inetsrv\appcmd recycle apppool "你的应用池名"三条命令对应三个参数含义。enable32BitAppOnWin64:true改的是应用池的位数模型,改完必须回收应用池才生效,很多人改完直接刷新页面,看到的还是 500,就是这个原因。(OI)(CI)M里的 OI 是对象继承、CI 是容器继承、M 是修改权限,缺了继承,上传子目录就写不进去。最后那条deny ... X是给上传目录断掉执行权限,防止有人传一个改了扩展名的脚本上来被解析——这道闸比任何扩展名白名单都硬。
还需要补一个容易被忽略的点:如果是 64 位系统跑 32 位组件,除了应用池,C:\Windows\SysWOW64\inetsrv下的配置视图和应用池设置必须对得上。判断方法很简单,在服务器上跑一次appcmd list apppool /name:"你的应用池名",看enable32BitAppOnWin64是不是 true,别靠记忆。
2.3 后台建样式,页面上挂一个能提交的表单
环境通了,下一步是让编辑器真正出现在页面上并且能把内容交回来。经典做法是 iframe 嵌入 + 同名隐藏域同步:
<!-- 父页面:一个隐藏域负责承载最终 HTML,一个 iframe 承载编辑器 --> <form id="frm" method="post" action="/save.asp"> <textarea id="content1" name="content1" style="display:none"></textarea> <iframe id="editorFrame" src="/editor/ewebeditor.asp?id=content1&style=standard" width="100%" height="420" frameborder="0" scrolling="no"></iframe> <input type="button" value="保存" onclick="submitWithContent()" /> </form> <script type="text/javascript"> function submitWithContent() { var frame = document.getElementById("editorFrame"); // 编辑器内部把渲染结果回写到父页面的同名域,再提交 document.getElementById("content1").value = frame.contentWindow.getHTML(); document.getElementById("frm").submit(); } </script>两个参数是关键。id=content1告诉编辑器「你渲染出来的内容要同步到父页面哪个域」,必须和隐藏域或 textarea 的 name/id 对上,对不上就是提交空值,这个坑每年都有人踩。style=standard指向后台建好的样式,样式里才定义了工具栏上有哪些按钮、允许上传什么文件、是否保留 Word 内联样式。所以正确顺序是:先进后台建样式 → 记住样式名 → 页面里引用它,而不是页面里随便写一个名字然后发现工具栏少了一半按钮。
getHTML()这一类 API 在不同版本里命名可能不同,拿到包之后先找一份接口说明或者看编辑器脚本里暴露的方法名,别照抄。如果拿不到脚本能力,退一步用「编辑器自己同步 + 表单直接提交」也行,同步时机由编辑器内部负责。
3. 两条 Word 导入链路:粘贴清洗与 .doc 上传转换的参数与边界
「Word 导入」这四个字在这个产品里其实是两套完全不同的机制,混为一谈是后面一堆问题的根源。第一条是粘贴链路:用户在本地 Word 里 Ctrl+C,进浏览器 Ctrl+V,编辑器拦截粘贴事件,把剪贴板里的 HTML 洗一遍再插进去。第二条是上传转换链路:用户点工具栏的 Word 导入按钮,选一个本地 .doc 文件,文件被送到服务端,服务端调转换组件把它变成 HTML 再塞进编辑区。前者全在浏览器里完成,后者依赖服务端装没装转换组件。搞清楚这一点,出问题时才知道该往哪边看。
3.1 粘贴链路:剪贴板里的 Word HTML 是怎么被清理的
Word 往剪贴板写内容时,会同时放好几套格式:纯文本、RTF、HTML。浏览器粘贴时,IE 内核下能通过clipboardData拿到 HTML 或纯文本那一套;现代浏览器只让你拿到纯文本或者一个被高度裁剪的 HTML 片段。eWebEditor 的做法是在编辑区的onpaste上做拦截,先取出 HTML,用一串正则把 Office 私有标记去掉,再回填。
这些私有标记的数量非常可观。一份普通两页公文粘过来,标签量可能是原文字数的十几倍,里面大部分是<o:p>、<st1:chsdate>、class=MsoNormal、style="mso-bidi-font-size:10.5pt"这类东西,还有一个被注释包起来的长块:
// 独立于编辑器也能用:把一段 Word 剪贴板 HTML 洗成可读的干净 HTML function cleanWordHtml(raw) { var s = raw; // 1. 干掉 Word 的条件注释块,里面通常是整段 mso 样式表定义 s = s.replace(/<!--\[if[\s\S]*?<!\[endif\]-->/gi, ""); // 2. 干掉带命名空间的私有标签:<o:p> <st1:xxx> <w:xxx> <v:xxx> s = s.replace(/<\/?(o|st1|w|v|m|aml):[^>]*>/gi, ""); // 3. 逐条剥离 mso- 开头的行内样式,其他样式保留 s = s.replace(/style\s*=\s*"([^"]*)"/gi, function (m, css) { var kept = css.split(";").filter(function (d) { return d && !/^\s*mso-/i.test(d); }); return kept.length ? 'style="' + kept.join(";") + '"' : ""; }); // 4. 类名里带 Mso 的基本没有语义,整条删掉 s = s.replace(/\s*class\s*=\s*"?Mso[^"\s>]*"?/gi, ""); // 5. 清掉空的 span 壳子,这类标签会层层嵌套 s = s.replace(/<span[^>]*>\s*<\/span>/gi, ""); return s; }五步各有分工。第 1 步处理的是 Word 生成的<!--[if gte mso 9]>条件注释,里面往往嵌着一整份样式表,不删会让页面样式被 Word 的风格带偏。第 2 步的命名空间标签在标准 HTML 里根本没有意义,留着只会让后续解析器报错。第 3 步是收益最大的一步,mso-前缀的样式属性是 Word 自己的排版信息,浏览器并不认识,全部删掉对呈现几乎没有影响,但能砍掉一半体积。第 4、5 步是收尾,MsoNormal这类类名在你的站点里没有对应的 CSS 规则,空 span 更是纯粹的噪音。
参数上唯一需要决策的是:保留哪些样式。字号、行高、缩进、表格边框这几类建议保留,因为公文格式的合规性就体现在这里;字体名建议不保留,因为服务端和浏览器的字体环境跟客户端不一致,保留反而会出现「本机好看、别人打开变形」的玄学问题。
3.2 上传转换链路:ActiveX、服务端组件与那个必踩的桌面目录
点工具栏的 Word 导入按钮,走的是另一条路。客户端把选中的 .doc 文件上传到服务端临时目录,服务端调本地安装的 Word 或第三方转换组件,把文档另存为 HTML,再把这段 HTML 读出来回填到编辑区。ASP 下的典型写法是这样:
<% ' 把上传上来的 .doc 转成过滤型 HTML Dim wdApp, wdDoc, srcFile, outFile srcFile = Server.MapPath("/uploadfile/tmp/in.doc") outFile = Server.MapPath("/uploadfile/tmp/out.htm") Set wdApp = Server.CreateObject("Word.Application") wdApp.Visible = False ' 服务端必须不可见,否则会弹窗卡死进程 wdApp.DisplayAlerts = False ' 关掉所有对话框,遇到损坏文件直接失败不阻塞 Set wdDoc = wdApp.Documents.Open(srcFile, False, True) ' 第三个参数 True = 只读打开 wdDoc.SaveAs outFile, 10 ' 10 = wdFormatFilteredHTML,比 8 干净很多 wdDoc.Close False wdApp.Quit Set wdDoc = Nothing Set wdApp = Nothing %>三个参数值得掰开说。Visible = False是硬要求,服务端跑一个可见的 Word 实例,一旦有人在服务器上点了什么,整个进程就卡在那里,后续所有转换全部排队。DisplayAlerts = False处理的是损坏文档、格式不兼容这类情况,不关掉的话 ASP 页面会一直等到超时。SaveAs的第二个参数是格式常量,8是标准 HTML,10是过滤型 HTML——后者会去掉大量 Office 特有的冗余标记,体积能小一半左右,代价是极少数复杂排版(比如某些文本框、艺术字)会丢失。公文场景下我一般选 10,因为业务方在意的是表格和段落,不是花哨效果。
真正让人头疼的不是代码,是权限。IIS 应用池账户去启动 Word 这个 COM 组件,需要过三关:
- 组件服务里找到 Word 文档对应的 DCOM 配置,把「标识」改成交互式用户或者一个固定的、有桌面目录权限的账户;
- 给
C:\Windows\SysWOW64\config\systemprofile\Desktop(32 位组件)和C:\Windows\System32\config\systemprofile\Desktop(64 位)授读写权限,目录不存在就手工建一个; - 转换用的临时目录,IIS_IUSRS 要有写权限,而且这个目录不能允许脚本执行。
漏掉第 2 步的表现非常典型:页面转半天,最后抛一个「无法创建对象」或者干脆 80070005 拒绝访问,日志里看不出任何和 Word 有关的信息。这是我见过最典型的黑匣子问题之一,第一次遇到能耗掉一整个下午。
3.3 样式配置里的参数:把不需要的按钮全关掉
后台建样式那一步,实际上是在填一张参数表。这张表决定了编辑器的行为边界,也是后面所有安全问题的第一道口子。关键项大致是这些:
| 配置项 | 作用 | 建议值 | 说明 |
|---|---|---|---|
| 编辑器宽度/高度 | 初始尺寸 | 100% / 420 | 宽度用百分比,适配不同分辨率 |
| 上传目录 | 文件落盘位置 | 独立目录,站点根之外更好 | 别和程序目录混在一起 |
| 允许上传扩展名 | 白名单 | jpg,png,gif,pdf,doc,docx,xls,xlsx | 不给 asp/aspx/htm |
| 单文件大小上限 | 限制体积 | 2048(KB) | 必须和 IIS 请求限制一致 |
| 保留 Word 内联样式 | 粘贴时的行为 | 开(仅字号/缩进/表格) | 全开会带进一堆 mso- |
| 脚本过滤 | 是否允许 script 等标签 | 开 | 关掉等于把门敞开 |
| 允许 iframe / object | 嵌入能力 | 关 | 内网也别开 |
| 是否自动加水印/水印文字 | 图片处理 | 按需 | 内网公文一般不需要 |
这里面最容易出事的是「允许上传扩展名」和「脚本过滤」两项。前者如果留了htm、html,配合上传目录可执行,就是一个现成的上传入口;后者如果关掉,任何能编辑内容的人都能往正文里塞脚本。这两项在默认配置里未必是最严的,装完之后第一件事就是进后台把它们收紧,别等到出问题再回头改。
4. 权限与过滤收口:上传目录、脚本白名单、后台入口的三道闸
一个老编辑器部署完能跑,和能长期跑,中间隔着的就是这一章。这类系统的攻击面很集中:上传、富文本内容、后台入口。三个地方各收一道,剩下的基本是运维层面的常规动作。下面这三道闸,任何一道没关,前面所有的配置都白做。
4.1 上传目录:白名单加断解析,两道一起上
扩展名白名单是第一道。后端脚本里维护一个允许列表,取文件扩展名的时候用小写、去掉末尾空格和点、只取最后一个点之后的部分。为什么强调「最后一个点」——a.asp;.jpg这种命名在部分容器下会被当成 asp 解析,只取第一个点就中招了。同时要做文件头校验,图片读前几个字节判断是不是真的图片,PDF 看%PDF,这一步能挡掉大量改扩展名的尝试。
第二道是断掉上传目录的脚本解析能力。IIS 下最简单的做法是在上传目录里单独放一份web.config:
<!-- 放在 uploadfile 目录下,让这个目录只能读静态文件 --> <configuration> <system.webServer> <handlers accessPolicy="Read" /> <staticContent> <clientCache cacheControlMaxAge="7.00:00:00" /> </staticContent> </system.webServer> </configuration>accessPolicy="Read"是关键,它把这个目录下的处理程序权限压到只读,asp、aspx 这些扩展名即使被传上来也不会被解析,只当普通文件下载。这条配置比在全局配置里一条条列禁止扩展名可靠得多,因为它只管一个目录,不会影响站点其他地方。附加的clientCache是可选项,让图片在浏览器端缓存七天,减少重复请求。
还有一个更彻底的做法:把上传目录放到站点根目录之外,用一个虚拟目录或者反向映射指过去。这样即便某天配置被人改错,脚本也不会落在能被访问到的路径下。代价是路径配置要改,迁移时容易漏。
4.2 内容过滤:黑名单挡不住,得往白名单方向走
编辑器输出的是 HTML,而 HTML 天然可以带脚本。这类老系统常见的过滤方式是黑名单:删<script>、删onclick、删javascript:。黑名单的问题是永远漏——<img src=x onerror=...>、<svg onload=...>、大小写混写、属性里插换行、用 HTML 实体编码绕过,办法太多了。
我的做法是两层。第一层在编辑器侧打开自带的脚本过滤开关,把script、iframe、object、embed、style这几类整块禁掉,属性层面禁掉on*事件和javascript:协议。这一层挡住的是无意识的粘贴污染。第二层在入库前做一次服务端过滤,方向改成白名单:只承认一张标签清单,清单之外的标签一律转义成文本;属性也只承认href、src、alt、title、colspan、rowspan、width、height这几个,其余全部丢弃。
服务端这一层不要指望一次写完美,重点是「默认拒绝」。写过滤函数时先把所有<转义,再把白名单标签按规则放回来,这个方向写出来的逻辑天然比黑名单严,漏的概率低一个量级。具体实现放到第 6 章。
4.3 后台入口与许可:改名、限 IP、换口令
后台管理目录默认路径是公开的,任何知道这套系统的人都能猜到。装完第一件事是改目录名,改成一个没有规律的字符串,然后在 IIS 里对这个路径加 IP 限制,只允许运维网段访问。这两步加起来,能挡掉绝大部分自动化扫描。
接着是默认口令。这类系统的初始管理员账号密码往往是固定的,装完必须立刻改,改成 12 位以上、包含大小写和符号的。别用系统里其他地方用过的密码,编辑器后台一旦被登录,攻击者可以直接改上传规则,前面所有收口全部作废。
关于许可这块,商业版一般有域名绑定或者授权文件校验。迁移、换域名、加二级域名的时候要同步处理,否则会出现「编辑器能显示但保存报错」或者「上传按钮点了没反应」这类看起来像程序 bug 的现象。遇到这类表现,先确认授权是不是还匹配当前访问地址,再去看代码。
5. 避坑与排查:五条翻车记录的现象、原因与解决
下面这五条都是我或者身边同事真实遇到过的,按「现象 → 原因 → 解决」写,方便对照。前三条偏环境和浏览器,后两条偏权限和内容。
5.1 环境与浏览器侧的三条
第一条:编辑器区域一片空白,页面其他部分正常。
现象是父页面能打开,iframe 区域纯白,右键查看框架源代码能看到编辑器页面的 HTML 确实返回了,但没有任何渲染。原因通常是 ASP 功能没装或者应用池位数不对,编辑器页在服务端就抛了异常,返回的是一段空内容或者半截错误页。解决方式是按 2.2 节的三条命令逐条确认:dism看 ASP 功能是否启用、appcmd list apppool看 32 位设置是否为 true、回收应用池。确认完还白屏,就看 IIS 日志里这条请求的状态码,500 说明是服务端问题,200 说明是前端脚本问题,方向完全不同。
第二条:点 Word 导入按钮没反应,或者提示「无法创建对象」。
现象是按钮点下去毫无反馈,或者弹一个含糊的错误框。原因分两种:浏览器侧 ActiveX 控件被安全策略拦了,或者服务端 COM 组件起不来。区分方法是换一台没装过安全策略的机器试,或者在服务器上直接跑一段创建 Word.Application 的测试脚本。如果是服务端,八成是 3.2 节说的桌面目录权限没给;如果是浏览器侧,需要在受信任站点里把站点加进去,并调整 ActiveX 相关设置。内网环境下这一步通常由 IT 统一下发策略,别让每个用户自己点。
第三条:粘贴进来的表格全散架,合并单元格变成一堆碎片。
现象是 Word 里好好的三线表,粘进编辑器后行列错位、合并单元格没了。原因是 Word 的表格结构里用了大量rowspan、colspan以及嵌套表格,粘贴清洗时如果正则写得太粗暴,把带rowspan的td当成普通标签处理,结构就塌了。解决方式是在清洗规则里给table、tr、td、th以及colspan、rowspan开白名单,别用「删除所有 style 属性」这种一刀切的做法。改完拿三份真实公文做回归,别拿自己随手敲的两行表格测。
5.2 权限与内容侧的两条
第四条:上传图片提示成功,但页面上是红叉。
现象是上传接口返回成功,编辑区也插入了<img>,但图片路径 404 或者 403。原因通常有两个:上传目录的物理路径和配置里写的虚拟路径不一致,或者上传目录的 ACL 只给了写没给读。前者在迁移服务器时特别常见,配置里写的是/uploadfile/,实际目录却挂在了别的盘符下没做映射。解决方式是先用浏览器直接访问一张已知存在的图片,确认路径可达;再检查icacls输出里 IIS_IUSRS 有没有读权限。顺带提一句,别把上传目录放在需要身份验证的区域里,否则图片请求会跳登录页。
第五条:保存成功,但列表页显示的内容样式全乱,甚至把整页布局撑破。
现象是编辑时看着正常,保存后在详情页打开,字体、行高、段落间距全不对,有时还会把外层容器撑变形。原因是入库的 HTML 里带了大量 Word 的行内样式和固定宽度,比如style="width:623.7pt",这个宽度在你的详情页容器里必然溢出。解决方式是在入库前做一次样式瘦身:删掉所有mso-属性、删掉width和height的绝对值、把pt单位统一换成px或者直接去掉。这一步放在服务端做比放在前端做可靠,因为前端清洗可以被绕过。
6. 入库前再洗一遍:把 Word 脏 HTML 压成可控标签
浏览器侧清洗解决的是「粘贴时看着舒服」,服务端清洗解决的是「入库后不会出事」,两者不能互相替代。我的习惯是在保存接口里加一道SafeHtml,方向是默认拒绝:先整体转义,再按白名单把标签放回来。
<% Function SafeHtml(s) Dim re Set re = New RegExp re.Global = True : re.IgnoreCase = True ' 1. 整块删除危险容器,连同内容一起 re.Pattern = "<(script|style|iframe|object|embed|link)[\s\S]*?</\1>" s = re.Replace(s, "") ' 2. 删掉所有事件属性 onerror/onload/onclick... re.Pattern = "\son\w+\s*=\s*(""[^""]*""|'[^']*'|[^\s>]+)" s = re.Replace(s, "") ' 3. 删掉伪协议与表达式 re.Pattern = "(javascript|vbscript)\s*:" s = re.Replace(s, "") re.Pattern = "expression\s*\(" s = re.Replace(s, "") ' 4. 删掉绝对宽高,避免撑破详情页容器 re.Pattern = "\s(width|height)\s*=\s*(""[^""]*""|'[^']*'|[^\s>]+)" s = re.Replace(s, "") SafeHtml = s End Function %>四步的顺序有讲究:先删整块容器,再删属性,最后删宽高。顺序反了,先删属性会把<script>里的内容打散,反而更难清理。这段函数只做减法和黑名单,真正严的白名单重建建议用支持回调的语言写——正则没有回调能力,做属性级白名单会很别扭,把这一步放到应用层用别的语言处理更省事。
验证方法我一般固定三步:拿一份带合并单元格的公文、一份带图片和页眉的公文、一份从网页复制过来的混合内容,分别走一遍粘贴和上传两条链路,然后看三件事——数据库里存下来的 HTML 长度是不是在合理范围(一份两页公文超过 200KB 基本就是没洗干净)、详情页打开有没有样式溢出、把内容再复制出来粘回编辑器会不会二次膨胀。三步都过,才算这套配置能交付。
从那以后我每接一套老编辑器,都先把「粘贴一份带合并单元格的公文」当成第一条验收用例,跑不通就不往下配别的。希望帮到你。
本文还有配套的精品资源,点击获取