想测试病毒🦠文件上传,文件创建不出来怎么办?

前言

最近项目有个检测上传文件病毒扫描的功能,需求是页面上通过上传控件,上传文件到服务器,然后后端扫描文件流,并且返回扫描结果。 逻辑不复杂,但有个致命问题 ,调试时得弄个病毒文件测试,但是由于公司电脑里都安装了杀毒软件,病毒文件刚创建出来了瞬间就被杀毒给删了,没文件咋上传?

真的就没办法在公司电脑里调试真实病毒文件了吗?

思路

先不考虑杀毒软件,首先看下怎么创建出来病毒文件,很简单,用下面这段字符创建一个txt纯文本文件,就是一个病毒文件。

X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

这段文本是EICAR测试文件的字符串,一般用来检测杀毒软件的,可以用任意类型文件,比如文件起名为test.png``test.pdf都可以当做病毒文件,而且这种文件本身并不是病毒,不会对电脑系统造成任何影响。

然后分析下怎么不用创建实体文件来上传,文件上传本质都是用的<input type="file"元素的change事件来实现的,change时获取dom上的files属性来获取上传的文件信息,那这个files属性是否可以自己创建然后替换呢?

下面代码就是创建一个内容为纯文本的File对象,只需要把dom上的files给替换掉就行了。

javascript 复制代码
const virusText = `X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*`;
const file = new File([virusText], 'test.txt', { type: 'text/plain'});

Demo

先来写个Demo调试下。

用Nest创建一个文件上传api npx nest new nest-app

typescript 复制代码
@Post('upload')
@UseInterceptors(
  FileInterceptor('file'),
)
uploadFile(
@UploadedFile() file: Express.Multer.File
) {
  console.log('nest file', file);
  return true;
}
html 复制代码
<input type="file" id="input1" />
javascript 复制代码
const input1 = document.getElementById('input1');
input1.onchange = function (e) {
  // const input = e.target || e.dataTransfer;
  const file = input1.files[0];
  console.log('input change', file.name, input1.value);

  const url = '/api/upload';
  const formData = new FormData();
  formData.append('file', file);

  window.fetch(url, {
      method: "POST",
      mode: 'cors',
      body: formData,
  });

  // 每次上传文件后清空input value,可以重复多次上传同一文件
  input1.value = '';
}

一个很简单的上传文件例子,测试下效果,新旧一个txt,上传。

nest api里打出了log,文件名和size都正确。

方案1:替换File对象

然后尝试用上面的新建File对象,在上例input change事件里替换File对象。

javascript 复制代码
// input change事件

const virusText = `X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*`;
const file = input.files[0];

// 用原文件名和type替换
const newFile = new File([virusText], file.name, { type: file.type });
// 或者直接定义一个txt
const newFile = new File([virusText], 'test.txt', { type: 'text/plain'});

// 用newFile调用api上传
const formData = new FormData();
formData.append('file', newFile);
...

测试下效果。

68个字符,正好跟病毒测试字符串长度一致(多一个转义字符),你也可以在nest api里把buffer返回到前台download下来,会发现被杀毒软件删除了,也就说明传给后端的文件流确实是病毒文件。

api返回buffer浏览器download代码参考:

javascript 复制代码
// nest
const { originalname } = file;
const fileExtension = originalname.substring(originalname.lastIndexOf('.') + 1);
const output = 'dest.' + fileExtension;
res.set('Content-Disposition', `attachment; filename="${output}"`);
res.send(file.buffer);

// js
window.fetch(...)
    .then(res => res.blob())
    .then(blob => {
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.style.display = 'none';
        a.href = url;
        a.download = file.name;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
    });

方案2:假如不能改代码

上例是通过修改了change事件里的上传代码来实现的,那假如不能改代码呢?比如下面场景:

  • 比如测试一个线上环境;
  • 比如上传逻辑封装到了第三方里,无法修改。

这时就能想到另一种方案,上面说了,上传文件本质就是监听input元素的change事件,那能不能有方法主动触发change事件呢?同时在input dom的files属性里动态添加文件。

javascript 复制代码
window._test = function (fileName, index = 0) {
  // 根据参数序号获取input dom
  const input = document.querySelectorAll('input[type="file"]')[index];
  if (!input) return;
  
	const virusText = `X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*`;
  const typeMap = {
    'txt': 'text/plain',
    'png': 'image/png',
    'zip': 'application/zip',
    'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'pdf': 'application/pdf',
  };
  // 获取文件扩展名
  const fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1);
  // 获取MIME type类型
  const type = typeMap[fileExtension] || 'text/plain';
  // 创建File对象
  const file = new File([virusText], fileName, { type });
  // 创建FileList对象
  const fileList = new ClipboardEvent("").clipboardData || new DataTransfer();
  fileList.items.add(file);

  // 用Object.defineProperty更改input dom files值
  Object.defineProperty(input, "files", {
    value: fileList.files,
    configurable: true,
  });
  // 用Object.defineProperty更改input dom value值
  Object.defineProperty(input, "value", {
    value: `C:\\fakepath\\${file.name}`,
    configurable: true,
  });
  // 触发input change事件
  const event = new Event("change", { bubbles: true });
  input.dispatchEvent(event);

  // 最后清空dom的files和value值,这样就可以继续手动上传文件
  delete input['files'];
  delete input['value'];
}

知识点:

  1. document.querySelectorAll获取所有input type=file,然后按参数序号找dom,这里也可以直接传dom,都行。
  2. MIME type类型的获取,这里只定义了部分与扩展名的映射,其它的可以自行在网上找。
  3. FileList对象创建,这里也可以直接用js Array,主要看项目change方法里是怎么获取的文件。
  4. Object.defineProperty由于安全限制问题,input type=file的dom的files\value属性无法直接更改,需要用Object.defineProperty方式更改。
  5. delete input['files\value']最后需要清空dom的files和value值,这样就可以继续手动上传文件。

这样就可以直接在浏览器F12 console里运行上面的代码,不需要改代码,也不用手动选择文件,就能模拟文件的上传。效果就不演示了。

这里的实现参考了一个用于模拟用户操作的第三方库 user-event,upload方法源码:github.com/testing-lib...

第三方上传控件

很多项目用到了第三方UI库里的上传控件,那上面的方案是否能模拟上传呢?下面来试试Antd Design组件库里的上传控件。

先创建一个react工程,并引入antd

bash 复制代码
npx create-react-app react-app
cd react-app
npm install antd --save

测试了两种类型上传控件,一个常规的,另一个拖拽上传。

jsx 复制代码
import { Button, message, Upload } from 'antd';
import './App.css';
const { Dragger } = Upload;

const virusText = `X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*`;
function App() {
  
  const onChange = (info) => {
    if (info.file.status !== 'uploading') {
      console.log('file', info.file);
    }
    if (info.file.status === 'done') {
      message.success(`${info.file.name} file uploaded successfully`);
    } else if (info.file.status === 'error') {
      message.error(`${info.file.name} file upload failed.`);
    }
  };
  
  // 用一个button click事件来模拟
  const onClick = () => {
    const fileName = 'test1.txt'; // 起名为text1.txt
    const typeMap = {
      'txt': 'text/plain',
      'png': 'image/png',
      'zip': 'application/zip',
      'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
      'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      'pdf': 'application/pdf',
    };
    const fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1);
    const type = typeMap[fileExtension] || 'text/plain';
    const file = new File([virusText], fileName, { type });

    // 0是第一个控件,1是第二个控件
    const input = document.querySelectorAll('input[type="file"]')[0];

    const fileList = new ClipboardEvent("").clipboardData || new DataTransfer();
    fileList.items.add(file);

    Object.defineProperty(input, "files", {
      value: fileList.files,
      configurable: true,
    });
    Object.defineProperty(input, "value", {
      value: `C:\\fakepath\\${file.name}`,
      configurable: true,
    });
    const event = new Event("change", { bubbles: true });
    input.dispatchEvent(event);

    delete input['files'];
    delete input['value'];
  }

  return (
    <div className="App">
      {/* 一般上传控件 */}
      <Upload name="file" action="http://localhost:3000/api/upload" onChange={onChange}>
        <Button>Click to Upload</Button>
      </Upload>
      

      {/* 拖拽上传控件 */}
      <Dragger name="file" action="http://localhost:3000/api/upload" onChange={onChange}>
        Dragger
      </Dragger>
      

      <Button onClick={onClick}>Click</Button>
    </div>
  );
}

export default App;

测试效果

两个控件分别测试了下,先手动选文件上传,再点click按钮模拟病毒文件上传,而且还不影响手动和模拟的交替连续上传,完美。

总结

本文介绍了在客户端安装了杀毒软件的情况下,如何用js模拟病毒文件上传调试,两种方案:

  1. 在input change事件里直接替换File对象。需要改code,适用于本地开发环境。
  2. 通过js动态更改input files\value属性值,然后主动触发input change事件,模拟上传。不需要改code,适用于任何环境,大部分文件上传场景。

本文只是通过这个病毒文件需求扩展下思路,也可以扩展下别的作用:

  • 比如模拟超大文件上传;
  • 或者固定size的文件,用来测试上传文件体积的限制验证。

new File(["0123456789".repeat(10000000)], "test.txt", { type :'text/plain'})

如果大佬有更好的方案,欢迎交流分享。

源码:github.com/markz-demo/...

相关推荐
一生为追梦3 小时前
Linux 内存管理机制概述
前端·chrome
喝旺仔la3 小时前
使用vue创建项目
前端·javascript·vue.js
心.c3 小时前
植物大战僵尸【源代码分享+核心思路讲解】
前端·javascript·css·数据结构·游戏·html
喝旺仔la3 小时前
Element Plus中button按钮相关大全
前端·javascript·vue.js
柒@宝儿姐3 小时前
Git的下载与安装
前端·javascript·vue.js·git·elementui·visual studio
Hiweir ·3 小时前
机器翻译之数据处理
前端·人工智能·python·rnn·自然语言处理·nlp·机器翻译
敲敲敲敲暴你脑袋4 小时前
【cesium】绘制贴地线面和自定义Primitive
javascript·webgl·cesium
曈欣4 小时前
vue 中属性值上变量和字符串怎么拼接
前端·javascript·vue.js
QGC二次开发5 小时前
Vue3:v-model实现组件通信
前端·javascript·vue.js·前端框架·vue·html
努力的小雨6 小时前
从设计到代码:探索高效的前端开发工具与实践
前端