Node.js - 临时文件名

73
在Node.js中,我该如何生成一个类似于mkstemp(3)的唯一临时文件名?我想使用fs.rename原子地写入一个文件。

48
这不是一个工具推荐。这是关于是否存在某个API的问题。我的意思是,到底怎么了,90%的SO问题都是关于是否存在某个API来完成某些任务的。要么全部关闭,要么全部保留。 - Tomáš Zato
8个回答

55

另一个广受欢迎的软件包是tmp


33
为什么这是真正的答案? - cambecc
首先,tmp包现在更受欢迎。其次,在https://github.com/raszi/node-tmp/blob/master/README.md#about中记录了一些小优点。 - Steve Jansen
@k0pernikus,你可以在图书馆网站上找到最新的示例。 - Marc
请注意,此软件包使用 os.tempdir() 方法。在 MacOS 上,这可能会导致问题,因为它返回符号链接而不是绝对路径。 - GROVER.

28

或许您已经在此期间找到了node-temp


23

不使用任何额外插件:

var crypto = require('crypto');
var fs = require('fs'); 

var filename = 'foo'+crypto.randomBytes(4).readUInt32LE(0)+'bar';
fs.writeFileSync(filename, 'baz');

编辑:请阅读评论。


60
小心,这是一个非常危险的经典反模式!多年来,这种代码已经导致了许多漏洞,这也正是mkstemp被创建以取代mktemp的确切原因。为了安全起见,应该使用O_CREAT和O_EXCL标志打开文件,防止另一个用户在你写入文件之前预测文件名并在那里创建符号链接。我想,如果你绝对确定另一个用户无法在一百万年内预测文件名,那么这不是必需的,但为什么要冒这个险呢?http://en.wikipedia.org/wiki/Symlink_race - Geoff
2
我认为该文件名中的熵不足,它输出foo1492796329bar。使用带有随机盐的SHA1会更好。 - Josue Alexander Ibarra
5
我同意,32位熵值过于小。对于短期文件,64位应该足够了。既然我们已经有了随机值,为什么还要建议使用哈希呢? - Emil Vikström
24
考虑到另外两个答案推荐安装任意代码以在安装时运行任意代码,会任意更改并且依赖于多个其他的软件包,而且设计过于复杂,因此这种方法无论如何都更好。 - polkovnikov.ph
1
@Geoff,加密安全的随机字节是无法预测的。唯一的问题是用户应该执行以下操作:crypto.randomBytes(16).toString('base64').replace(/\//,'_')。根据我的经验,mktemp存在严重的跨平台问题,可能会出现挂起/破坏问题。 - Erik Aronesty
显示剩余2条评论

11
在Node和Python中创建临时文件容易出现涉及权限更改和跨平台问题的竞争条件,特别是在Windows上,其中测试ACL非常具有挑战性。
这导致许多现代语言中请求临时文件时可能会导致机器无响应。(如果Node无法访问临时目录,则根据ACL的情况,即使文件不存在,它也可能得到EEXIST,其结果可能是一个无限循环)。
最简单的解决方案是使用足够的熵,使碰撞的可能性可以忽略不计(在密码学意义上)。
这还具有使临时文件创建在所有平台上都是安全的的副作用,而无需对源代码进行深入审查。只要你的随机数是安全的,就没有办法预测将用于篡夺权限或访问的文件。
并且它有助于需要传递文件名而不是文件句柄的程序。
const crypto = require('crypto');
const os = require('os'); 
const path = require('path'); 

function tmpFile(prefix, suffix, tmpdir) {
    prefix = (typeof prefix !== 'undefined') ? prefix : 'tmp.';
    suffix = (typeof suffix !== 'undefined') ? suffix : '';
    tmpdir = tmpdir ? tmpdir : os.tmpdir();
    return path.join(tmpdir, prefix + crypto.randomBytes(16).toString('hex') + suffix);
}

唯一的缺点是它无法在FAT格式的分区上运行,而这种格式在USB存储设备上仍然很常见。

9

尝试使用此功能,它是安全的并且没有漏洞。NODE 8.x LTS

function tempFile (name = 'temp_file', data = '', encoding = 'utf8') {
    const fs = require('fs');
    const os = require('os');
    const path = require('path');

    return new Promise((resolve, reject) => {
        const tempPath = path.join(os.tmpdir(), 'foobar-');
        fs.mkdtemp(tempPath, (err, folder) => {
            if (err) 
                return reject(err)

            const file_name = path.join(folder, name);

            fs.writeFile(file_name, data, encoding, error_file => {
                if (error_file) 
                    return reject(error_file);

                resolve(file_name)
            })
        })
    })
}

它解决了临时文件路径的问题,拒绝mkdtemp或writeFile错误。
// C:\Users\MYPC\AppData\Local\Temp\foobar-3HmKod\temp_file
// /temp/Temp/foobar-3HmKod/temp_file
tempFile().then(path => console.log(path)).catch(e => console.log("error", e)) //or

// C:\Users\MYPC\AppData\Local\Temp\foobar-9KHuxg\hola.txt
// /temp/Temp/foobar-9KHuxg/hola.txt
tempFile('hola.txt', 'hello there').then(path => console.log(path)).catch(e => console.log("e", e))

19
如果没有一些严谨的支持,声称这段代码是“安全而且没有漏洞”的说法就不具有合理性。 - l0b0
这个函数不仅会创建一个临时文件,还会为这个文件创建一个临时目录。因此,即使调用者稍后删除了临时文件(这也是首次使用临时文件的原因),临时目录仍然存在。因此,频繁调用此函数将在 /tmp 文件夹中产生垃圾。 - kayahr

6

kinematic's answer类似,但增加了2个字节的额外熵,并使用字母代替数字:

import Crypto from 'crypto';
import {tmpdir} from 'os';
import Path from 'path';

function tmpFile(ext) {
    return Path.join(tmpdir(),`archive.${Crypto.randomBytes(6).readUIntLE(0,6).toString(36)}.${ext}`);
}

使用方法:

const file = tmpFile('tar.gz'); // "/tmp/archive.1scpz5ew5d.tar.gz"

我正在创建档案,因此我选择“archive”作为基础名称,但您可以根据需要更改它。

16个字节是更安全的数字 - 即使使用情况极端,也不会发生碰撞。 - Erik Aronesty
@ErikAronesty 当然可以,但是在编写时,node不支持大于6字节的整数。您可以与16字节一起使用...实际上,我们仍然限制在64位(readBigInt64LE)。看起来他们仍然没有添加任意长度的bigint方法,但如果您想要128位,可以分2个块完成。 - mpen
我将 Crypto.randomBytes(16) 转换为十六进制,然后就可以放心使用了。安全、快速,毫无问题。 - Erik Aronesty
哦,是的,你可以直接将缓冲区转换为十六进制或base64,这很有效。crypto.randomBytes(16).toString('hex') - mpen
1
Path.join(tmpdir(),`${prefix}${Crypto.randomBytes(16).toString('hex')}${suffix}`);} - Erik Aronesty

3

-1

使用 npm 包 tempfile

import tempfile from 'tempfile';

或者

let tempfile = "";
import('tempfile').then(a => {
    tempfile = a.default;
});

然后:

let filename = tempfile() //e,g: /tmp/5fc8a50f-e9cd-4420-b62a-e365b3ef2c2a
let imagefilename = tempfile(".jpeg") // e,g: /tmp/5a6dea08-a704-4fa8-9d0a-8ffa5e134543.jpeg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接