简介:这是一份面向ASP开发者的文件上传组件源码包,适用于在线投稿、图片分享、文件共享等需要处理用户上传文件的Web应用场景,可帮助开发者快速实现多文件上传、类型校验、大小限制、进度反馈与安全检测等核心功能。压缩包共21个文件,大小260KB,主要包含5个asp示例与调用页面、3个txt说明与配置文档、3个htm帮助页面、1个mdb数据库及dll组件文件,另有js、css、gif、bat等辅助资源,目录结构清晰,便于直接引入项目。已有111人浏览学习,适合有一定ASP基础、希望减少重复开发成本的初级及中级Web开发者参考使用。解压后可结合示例代码快速理解组件调用方式,跟随说明文档完成上传表单搭建、服务器端文件保存与合法性检查,并参考错误处理机制构建健壮的上传模块。
1. 拿到一份“ASP源码—风声ASP上传组件 v2.0.zip”,先别急着解压丢进网站
我第一次接到这类需求,是帮人把一个跑在 IIS 6 上的老站点迁到新服务器。对方扔过来一个 zip,名字就叫“ASP源码—风声ASP上传组件 v2.0.zip”,里面全是 .asp 和 .inc 文件,没有安装程序,也没有 DLL。这套东西本质上是把浏览器上传文件的 multipart/form-data 解析逻辑写成了 VBScript 类,v2.0 相比 v1.x 一般会多一些可配置属性,比如扩展名白名单、文件大小上限、自动改名,但部署方式没有本质变化。它要解决的是 ASP 没有原生上传对象的痛点:Request.Form 只能拿到表单字段,拿不到文件流。适合还要维护老 ASP 系统、或者被要求“把这套老站点跑起来”的读者。下面按我拿到这种 zip 后的完整处理路径展开:先讲原理,再落地部署,调参数,最后做安全验证。
2. 为什么一个上传都要用组件源码:从 Request.BinaryRead 到 multipart 拆包
2.1 ASP 原生拿不到文件:问题在协议层
浏览器上传文件时,表单的 enctype 是 multipart/form-data,请求体会按 boundary 分隔成多个块,每个文件块有自己的头部信息和二进制内容。ASP 的 Request.Form 和 Request.QueryString 只解析 application/x-www-form-urlencoded 格式,遇到 multipart 请求时会忽略文件部分,普通文本框的值能读到,文件流直接丢失。这是协议层面决定的,不是 IIS 配置能绕开的。
所以早期 ASP 开发者唯一的办法是用 Request.BinaryRead 把原始请求体完整读出来,再自己按 boundary 切块。一个最小示意如下:
<% Dim binBody, contentType, boundary binBody = Request.BinaryRead(Request.TotalBytes) ' 从 Content-Type 请求头里取 boundary,例如 ---------------------------7d0d contentType = Request.ServerVariables("HTTP_CONTENT_TYPE") boundary = "--" & Mid(contentType, InStr(contentType, "boundary=") + 9) ' 下面需要按 boundary 切成数组,再解析每一块里的 Content-Disposition, ' 从中找到 filename="..." 和 name="...",抽出文件二进制区。 ' 注意:块之间的分隔符是 CRLF,而二进制内容里也可能出现 CRLF, ' 所以不能只靠 InStr 找换行,得逐字节处理。 %>这段代码只是拆包前两步的示意,真正写完要处理边界判断、空行位置、文件名编码、二进制区提取,代码量会在两百行以上。Request.BinaryRead 的参数是字节数,如果超过 AspMaxRequestEntityAllowed 的限制,会在读取阶段直接报 ASP 0104 错误,这一点后文还会专门讲。大多数人选择上传组件,就是因为这些边界情况自己处理起来太容易出错。
2.2 三种实现形态,为什么源码包是相对省心的形态
ASP 上传功能历史上主要有三种实现方式:无组件手工拆包、编译 DLL 组件、ASP 源码封装类。三者的差异决定了它们各自的使用场景。
| 实现形态 | 部署成本 | 典型故障 | 适用环境 |
|---|---|---|---|
| 无组件手工拆包 | 零部署,直接放 ASP 文件 | 二进制解析出错、文件损坏、中文名乱码 | 虚拟主机、一次性上传需求 |
| 编译 DLL 组件 | 需要 regsvr32 注册,虚拟主机无法操作 | 组件未注册、64 位系统不兼容、被安全软件拦截 | 独立服务器,可控制注册表 |
| ASP 源码封装类 | 拷贝文件和 include 即可 | 类名/方法名需按源码实际定义调整 | 老系统迁移、需要长期维护 |
“风声ASP上传组件 v2.0.zip”这类包大多数属于第三种,把 multipart 解析、文件保存、大小判断、扩展名过滤都封装到一个 VBScript 类文件里,页面顶部用<!--#include file="inc/UploadCls.asp"-->引入即可。与 DLL 相比,源码包不需要在服务器上执行 regsvr32,也不存在 32 位和 64 位注册表重定向的问题;与纯手工无组件代码相比,它处理了二进制边界、空块、多文件等常见坑。缺点是所有逻辑都是脚本解释执行,上传大文件时 CPU 占用偏高,这一点要做好心理准备。
2.3 先确认包里的文件结构,再动代码
解压之后先别急着把文件复制到网站根目录。常见打包结构里会有三个部分:保存类定义的 .inc 或 .asp 文件、演示上传页和处理页、说明文档。我一般先用 PowerShell 解压并列出文件,确认类名和方法名再开始改:
Expand-Archive -Path "ASP源码—风声ASP上传组件 v2.0.zip" -DestinationPath "C:\inetpub\wwwroot\fsupload" -Force Get-ChildItem -Path "C:\inetpub\wwwroot\fsupload" -Recurse | Select-Object FullName, LengthExpand-Archive 要求 PowerShell 5.0 以上,Windows Server 2012 R2 以上的系统基本都满足。如果解压过程中提示文件损坏,先看看是不是 zip 带有密码或者使用了古老的压缩算法,找原负责人要密码比自行处理更稳妥。列出文件后,重点找包含Class关键字的文件,里面会有类似Public Function SaveFile、Public Property Let AllowExt这样的定义,这些才是后续调用时的真实接口。
3. 在 IIS 上把最小上传跑通:include 类、写上传页、处理页
3.1 准备目录和权限
上传功能落地第一步不是写代码,而是把保存文件的目录建好,并给 IIS 匿名用户写入权限。以 Windows Server 2012 以后的系统为例,IIS 应用池默认使用 ApplicationPoolIdentity,对应到文件系统里是 “IIS AppPool\应用池名称” 这个账户。右击上传目录,在安全页签里添加该账户并授予修改权限。如果站点用的是 IIS 6 兼容模式或经典管线,则可能是 IUSR 或 IUSR_机器名,不确定时先看应用程序池的高级设置里“标识”是哪一种。
目录建在哪也有讲究。常见做法是放在站点根目录下的 uploads 文件夹,比如C:\inetpub\wwwroot\fsupload\uploads,后面调用组件时传相对路径。不要直接传到站点根目录,更不要传到数据库备份目录这种敏感位置,否则后面做权限隔离会非常麻烦。
3.2 前端上传页的最小代码
一个能触发文件上传的表单,核心点是 form 的 enctype 必须是 multipart/form-data,method 必须是 post。下面是最小示例:
<form action="doUpload.asp" method="post" enctype="multipart/form-data"> <input type="text" name="remark" value="测试备注" /> <input type="file" name="file1" /> <input type="submit" value="开始上传" /> </form>如果漏掉 enctype,浏览器会按默认的 x-www-form-urlencoded 发送,服务器端收到的只是文件名这个字符串,文件内容不会进入请求体。这里的name="file1"要与后面处理页里组件的文件字段名保持一致;多个文件时,有的 v2.0 包支持name="file1"的数组形式,有的支持多个不同 name,具体以你 zip 内 demo 页的写法为准。
3.3 处理页调用组件的标准写法
处理页的核心逻辑只有四步:引入类文件、创建上传对象、设置参数、执行保存。下面是兼容大多数源码包的写法:
<!--#include file="inc/UploadCls.asp" --> <% ' 关闭错误回显,避免半截 HTML 影响判断 On Error Resume Next ' 类名按 zip 内 inc 文件实际定义调整,常见有 Upload、ClsUpload、FsUpload Set up = New Upload ' 保存目录指向站点物理路径下的 uploads 文件夹 up.SaveTo = "/uploads" ' 扩展名白名单,只允许这些类型提交 up.AllowExt = "jpg,gif,png,zip,rar,doc,pdf" ' 最大文件大小,单位是字节,这里约等于 10MB up.MaxSize = 10 * 1024 * 1024 ' 自动重命名,建议设为 True,可以绕开中文名和重名 up.AutoRename = True If up.SaveFile() Then Response.Write "OK|" & up.FilePath Else Response.Write "ERR|" & up.ErrorMsg End If %>这段代码里最需要改的是类名Upload。不同版本组件定义的类名差异很大,如果实际类名不是 Upload,调用时会报“缺少对象”的错误。SaveFile 是执行保存动作的常见方法名,有的包叫 SaveAs 或 Process,确认方法名后把这里同步替换。SaveTo 属性是保存路径,组件一般会在当前站点物理路径下拼接这个相对路径;不要用带盘符的绝对路径,迁移服务器时会失效。AllowExt 用逗号分隔且不带点号,是用来过滤文件名后缀的,不是过滤 Content-Type。设置完成后调用 SaveFile,返回 True 时 FilePath 里是保存后的相对路径,可以直接拼到 img 标签或写入数据库。
3.4 把 demo 页面改成自己的业务逻辑
v2.0 包附带的 demo 通常只是打印上传结果,实际项目里要对接数据库。常见做法是把 FilePath 和表单里的 remark 字段一起写入记录表:
' 假设数据库连接已存在于 conn 对象中 Dim sql sql = "INSERT INTO t_upload(file_name, file_path, remark, create_time) VALUES (" & _ "'" & up.FileName & "', " & _ "'" & up.FilePath & "', " & _ "'" & Replace(Request.Form("remark"), "'", "''") & "', " & _ "'" & Now() & "')" conn.Execute sql注意 Request.Form 里的字符串拼 SQL 之前必须做单引号转义,否则 remark 里出现单引号会直接把语句打断,形成注入点。这是老 ASP 项目里最常见的问题,v2.0 组件只负责文件上传,不会帮你处理 SQL 注入。FilePath 字段建议存相对路径,配合站点域名拼完整 URL,这样换域名时不用改数据库。
4. 上传大小、执行超时与中文文件名:v2.0 最常见的三个坑
4.1 ASP 0104 与 maxAllowedContentLength:上传大小被两层限制卡住
上传文件明明不大,页面却报Request object error 'ASP 0104: 80004005',这是判断上传类问题时第一个要排查的现象。原因是 IIS 对请求实体大小有两层限制:ASP 脚本层的 AspMaxRequestEntityAllowed,以及 IIS 请求过滤层的 maxAllowedContentLength。IIS 6 默认只有前者,默认值 204800 字节,也就是 200KB,传个稍大的图片就报错。
用 IIS 6 的经典命令调整大小的方式如下:
cscript.exe %SystemDrive%\inetpub\AdminScripts\adsutil.vbs SET w3svc/1/root/AspMaxRequestEntityAllowed 10485760这条命令把默认站点根路径的上传上限改成 10MB。注意 w3svc/1 里的 1 是站点序号,多站点时要对应到具体站点。修改完成后执行 iisreset 重启 IIS 让配置生效。IIS 7.5 之后,这个值改由 web.config 里的配置节控制:
<configuration> <system.webServer> <security> <requestFiltering> <requestLimits maxAllowedContentLength="10485760" /> </requestFiltering> </security> <asp> <limits maxRequestEntityAllowed="10485760" /> </asp> </system.webServer> </configuration>maxAllowedContentLength 默认是 30000000 字节,约 28.6MB,所以小文件一般不会触发这层;但如果你把组件里的 MaxSize 调大,比如允许 100MB,这两处配置必须同步调大,否则组件还没开始读流就被 IIS 拦截了。我一般会先在 web.config 里把两层都写出来,避免不同服务器默认行为不一致。
4.2 脚本超时与慢速上传的处理
文件超过 20MB 或者用户网络很慢时,经常出现上传到一半页面直接变空白,日志里没有错误码,问题多半出在 ASP 脚本超时。ASP 脚本默认执行时间是 90 秒,上传走的是脚本解释执行,整个文件流要在这 90 秒内读完并写入磁盘,否则 ASP 引擎会强制中断。
处理页顶部加一行可以临时延长单个页面的超时时间:
Server.ScriptTimeout = 300单位是秒,300 秒等于 5 分钟。如果上传需求经常达到 50MB 以上,我建议把超时设到 600,同时换用分片上传方案,而不是只依赖组件。另外,保存文件时优先使用组件内部基于 ADODB.Stream 的写入方式,避免用传统 FSO 的 TextStream 写二进制文件,TextStream 面向文本流,遇到二进制内容会丢字节。
4.3 中文文件名乱码与自动改名策略
中文文件名上传后在服务器上变成乱码,是这类组件的典型问题。原因有两个层面:页面或组件源码的编码不是 UTF-8,以及 multipart 块头里的 filename 解析时按单字节处理。最省事的办法不是去修组件的解码逻辑,而是直接启用自动改名,让服务器用生成的规则文件名保存。
' 在组件参数里开启自动重命名 up.AutoRename = True ' 也可以自己生成规则文件名,然后赋值给组件 Randomize Dim newName newName = Year(Now()) & Right("0" & Month(Now()), 2) & Right("0" & Day(Now()), 2) & "_" & Int(Rnd() * 999999) & "." & up.FileExt这段代码里,日期部分生成 20260611 这样的前缀,Rnd 生成随机数拼在后面,FileExt 是组件解析出来的原文件扩展名。这样既保留了扩展名用于判断文件类型,又完全绕开了中文解码问题。如果业务上必须保留原始文件名,再去改组件的字符集解析,但 IME 这行的收益很低。
4.4 上传失败的现象与排查对照
| 现象 | 常见原因 | 处理方向 |
|---|---|---|
| 报 ASP 0104: 80004005 | 请求实体大小超限 | 调整 AspMaxRequestEntityAllowed |
| 页面 404,Form 提交后无响应 | enctype 写错或 action 路径不对 | 检查 form enctype 和相对路径 |
| 保存成功但文件 0 字节 | 组件用了文本方式写文件 | 确认组件内部用的是 ADODB.Stream |
| 纯英文文件名正常,中文乱码 | 页面编码与解析编码不一致 | 开启 AutoRename 或统一 CodePage |
| 视频文件一直上传失败 | 脚本超时或单请求大小限制 | 加 Server.ScriptTimeout 和请求限制 |
排查顺序一般是从外到内:先看 IIS 日志有没有 404、413、500,404 是路径问题,413 是请求体太大被 IIS 拦截,500 再看组件内部哪一行报错。组件类文件里的错误处理往往只是On Error Resume Next,如果它自己吞掉了异常,就把处理页的On Error Resume Next注释掉,让错误真实抛出,能看到具体行号。
5. 上传目录安全加固与用一次真实的 multipart 请求做验证
5.1 让上传目录变成“只存不执行”的目录
文件能传上来只是开始,真正麻烦的是有人传了一个 .asp 文件并在浏览器里直接访问它。只要上传目录允许脚本执行,这一步等于把服务器大门敞开。IIS 7 之后可以在 web.config 里针对上传目录单独收紧:
<configuration> <location path="uploads"> <system.webServer> <handlers accessPolicy="Read" /> <staticContent> <mimeMap fileExtension=".jpg" mimeType="image/jpeg" /> </staticContent> </system.webServer> </location> </configuration>handlers 的 accessPolicy 设为 Read,表示该目录只提供静态文件读取,不执行 ASP、ASPX 等动态脚本。这样即使有人绕过扩展名白名单传上去一个 xx.asp,访问时也只会被当作静态内容或直接拒绝,不会执行。IIS 6 环境下,对应操作是在目录属性的“执行权限”里选择“无”,效果相同。
5.2 在组件源码里补三道防线
扩展名白名单是最浅的一层,容易被大小写、双扩展名绕过。我在现有组件上会再加两道检查:文件内容头校验和路径穿越过滤。读取文件前几个字节判断真实类型是有效手段:
Function IsImageFile(bytArr) Dim b0, b1, b2, b3 b0 = AscB(MidB(bytArr, 1, 1)) b1 = AscB(MidB(bytArr, 2, 1)) b2 = AscB(MidB(bytArr, 3, 1)) b3 = AscB(MidB(bytArr, 4, 1)) If b0 = &HFF And b1 = &HD8 And b2 = &HFF Then IsImageFile = True ' JPEG ElseIf b0 = &H89 And b1 = &H50 And b2 = &H4E And b3 = &H47 Then IsImageFile = True ' PNG ElseIf b0 = &H47 And b1 = &H49 And b2 = &H46 Then IsImageFile = True ' GIF Else IsImageFile = False End If End Function调用时机在 SaveFile 内部真正写盘之前,先检查文件头再决定是否保存。对应地,路径穿越过滤需要检查原始文件名里是否包含..或\,有就直接拒绝,避免通过构造文件名把文件写到站点目录之外:
If InStr(LCase(up.FileName), "..") > 0 Or InStr(up.FileName, "\") > 0 Then up.ErrorMsg = "Illegal filename" End If5.3 用 curl 模拟一次完整的 multipart 上传验证
页面调试完,用浏览器点几次表单只能覆盖正常路径。我习惯用 curl 构造一个真实的 multipart 请求,验证上传接口对不同大小、不同扩展名文件的响应,方便脚本化回归:
curl -i -F "remark=test" -F "file1=@D:\tmp\test.jpg" http://127.0.0.1/fsupload/doUpload.asp-F 参数会自动生成 multipart/form-data 格式,并把 test.jpg 的内容放进 file1 字段。返回里包含OK|/uploads/20260611_123456.jpg说明上传成功;返回 ERR 则对照 ErrorMsg 判断是哪层拦截。再构造一次超大文件或者把 test.jpg 改名成 test.asp 上传,确认 Response 里返回的是 ERR 而不是成 OK。全部通过后,去 uploads 目录手工检查文件字节数和文件头是否与源文件一致,再确认数据库里的 filepath 字段能拼出可访问的 URL,这个 zip 才算真正接手完成。
本文还有配套的精品资源,点击获取