我想处理一个发送文件的post请求。但是在Apache服务器上用bash编写的CGI脚本无法将文件上传到服务器。我可以将错误归结为/dev/stdin
没有按预期工作。它没有写出二进制流,而是抛出了一个错误。我正在使用轻型-权重开源内容管理系统Lichen和我最初遇到这个问题的原因。
在下面,我将首先简化这个问题,并展示我在尝试使用shell脚本从HTML前端和CGI后端上传文件时所做的所有错误。
简化问题
用于上传文件的html post表单如下所示:
<form action="http://guestserver/cgi-bin/upload.cgi" method="post" enctype="multipart/form-data">
<p><input type="file" name="filename" id="file"></p>
<p><input type="submit" value="Upload"></p>
</form>
它将其请求发送到upload.cgi
:
#!/bin/sh
# Exit immediately if a command exits with a non-zero status.
set -e
# replace spaces with underscores
# which is done by tr "thisGetsReplaced" "byThis" ;
# -s means squeeze repeated occurence
# echo $PATH_INFO |
# gets filename and passes output to next (by '|')
sanitized=$(echo $PATH_INFO | tr -s ' ' '_')
# move one dir up and look if file exists there
if [ -f ..$sanitized ]; then
cat /dev/stdin > /dev/null
echo 'Status: 409 Conflict'
echo 'Content-Type: text/plain'
echo ''
echo 'File already exists.'
exit 0
fi
# Actual file write
mkdir -p ..$(dirname $sanitized)
cat /dev/stdin > ..$sanitized # line that throws error
# I guess if file write at this point was successful
# it exits with something non-zero
# so the script is STOPPED
echo 'Status: 204 No Content'
echo "X-File-Name: $(basename $sanitized)"
echo "X-File-Path: $(dirname $sanitized)"
echo ''
但是$PATH_INFO
不会保存正在上传的文件名字符串,我们稍后会看到,因此上传失败。另一方面,在生产环境中,会创建一个具有正确文件名的新文件,该文件名可以在正确的目录中看到,但是该文件为空。:o
我想知道它是怎么做到的?
一个test.cgi脚本,用于验证测试环境中的所有数据是否都已相应发送,并证明PATH_INFO为空:
#!/bin/sh
echo "Content-Type: text/html"
echo "<html><head></head><body>"
echo SERVER_<?> = $SERVER_<?> # just so I don't have to write so much
echo PATH_INFO = $PATH_INFO
dd count=1 bs=$CONTENT_LENGTH # prints file content
echo "</body></html>"
当客户端通过html文件发布表单运行时,具有以下输出:
> SERVER_SOFTWARE = Apache/2.4.55 (Unix)
> GATEWAY_INTERFACE = CGI/1.1
> SERVER_PROTOCOL = HTTP/1.1
> SERVER_PORT = 80
> SERVER_PROTOCOL = HTTP/1.1
> SERVER_PORT = 80
> REQUEST_METHOD = POST
> HTTP_ACCEPT =
> PATH_INFO =
> \------WebKitFormBoundaryMNBsYvUe3DbH9tpE Content-Disposition: form-data; name="filename"; filename="uploaded file.jpg" Content-Type: image/jpeg ÿØÿàJFIFÿÛC %# , #&')\*)-0-(0%()(ÿÀ,àÿÄÿÄ; !"#$312%4CB“5ADQRcdƒabe„”•ÿÚ?•ñ£Ö˜þFßE%ò}õ\[/ì³è1Æ'¬YÇçªÙæÞõÑTÚuJn4îÝ)ÎV“¦9îª ©“1í”»ge¢R…Z¿MÑŽ¼ÜÃÛ—d´¯±¦#ø4¦‚ðœDÐŽæ…c4û°e¥4ê×1žOO qu»Ö:ûïAB¬?ÙܶbZÎf³ª‹¹yçDÖÒáSªµù¦
最后一位被故意剪掉了,这样就不会显示80kb的文件,而这正是dd count=1 bs=$CONTENT_LENGTH
命令打印的内容。它在打印上传文件的内容(只是编码不正确)方面做得很好,证明它以某种方式工作。然而,文件内容从未保存在服务器上。
这样我们就可以确认upload. cgi脚本在我们的测试环境中接收到文件,尽管PATH_INFO
没有接收到文件,因此cgi脚本失败了。
这也是Apache的错误消息:Premature end of script headers: upload.cgi
和/var/log/httpd/error_log
处的apache错误日志显示以下错误代码:
> dirname: missing operand
> Try 'dirname --help' for more information.
> /srv/http/cgi-bin/upload.cgi: line 20: ..: Is a directory
> \[Mon Mar 06 12:29:04.166828 2023\] \[cgid:error\] \[pid 297:tid 140340891719360\] \[client 192.168.56.1:63168\] Premature end of script headers: upload.cgi, referer: http://localhost:5500/
指向upload.cgi脚本中的第20行(完整的上下文请查看上面的脚本),尽管我猜它实际上指的是第19行:
mkdir-p ..$(已清理的目录名$)
其中,dirname
在$sanitized之后在其参数上失败:
已清理=$(回显$PATH_INFO|tr-s """_")
实际上是一个空字符串,因为$PATH_INFO没有值! -正如我们所看到的。
我非常感谢任何帮助,并将非常高兴,如果有一个解决这个问题。:)的目标是有文件正确上传到服务器上。
2条答案
按热度按时间pnwntuvh1#
如果你是来修复你的Lichen upload.cgi脚本的,请跳到最后一部分,我们在这里修复了
/dev/stdin
。前两部分是解决我在搜索真正的错误时(最后一部分)所创建的错误。路径信息
冷静点,一切都在正常运转。
你的第一个错误是
PATH_INFO
从url中取值,所以你的post-request实际上应该包含文件名,就像这个例子一样,例如http://server/cgi-bin/test.cgi/thisWillBePassedTo-PATH_INFO.file
。所以你的简化问题是错误的,这就解释了为什么你仍然可以在你的生产环境中上传文件,但是没有任何内容(我们将在第三部分中看到),因为名称在
PATH_INFO
中被正确地传输,这给你带来了很多困惑。下一次请查看the docs
正确设置标题
关于错误:
Premature end of script headers
虽然互联网提供了十几种设置CGI脚本头的方法,但长时间的试验和错误过程表明,正确的头只发送文本看起来像这样:
大写字母还是小写字母并不重要,但是空的
echo ''
(设置换行符)非常重要!此外,您还可以发送状态头,它们看起来会有所不同(您可以在upload.cgi脚本的下一个代码块中看到它们)。
目录号:/器械/标准输入:无此类设备或地址
我试图运行静态网站内容管理系统(CMS)Lichen,当这个错误发生
cat: /dev/stdin: No such device or address
.我假设服务器环境导致这个错误(我运行的是Arch/Linux)
为了解决这个问题,我不得不使用一个不同的命令来读取
stdin
,这个命令叫做dd
。用
dd
修改upload.cgi
脚本中的两行代码就完成了:请注意,来自前端的post请求应该只发送文件的主体,否则处理数据流的CGI脚本将无法正确保存您发送的文件。更多信息请参见this post的改进部分。
7kjnsjlb2#
使用CGI shell脚本上传文件的简单演示
CGI脚本通常位于自己的cgi-bin中,在那里它们也有执行权限,以及它们所在的文件夹。(可以用
chmod o=rwx /path/to/folder-and-or-cgiFile
设置,这通常是错误的来源)。此外,Apache必须进行广泛的配置,阅读上面提供的链接以了解更多信息,不要忘记重新启动Apache,否则您的更改将不会有任何效果。服务器端
在我们的例子中,我们将使用shell脚本,它是预装在GNU/Linux机器上的。在我的例子中,我使用GNU bash,Version 5.1.16,它可以通过输入
sh
在命令提示符下调用。提示,分析Apache的错误日志,如果事情不工作:
cat /var/log/httpd/error_log
客户端
接下来,我们需要一个前端来尝试一些东西。下面是我们的html部分:
解释
在我们的html文件中需要额外的Javascript,以提供粘贴到url的文件名,然后在后端通过$PATH_INFO访问该文件名,清理以删除任何空格,并在cgi-bin之外创建一个目录,其中dd或cat打印来自
/dev/stdin
的数据流。我们的前端也会发送一些需要删除的文件头。CGI脚本会删除前四行
这是一个非常古怪的解决方案。我建议多读一些关于that的文章,我不确定这个例子在这方面有多稳定。原始的源代码通过只发送主体来防止这种情况,这是用一些JavaScript完成的(如本文所示)。
这个脚本是基于CMS Lichen源代码的,但是我不得不修改一下使用"dd",否则它会失败。你可能要试试哪一个适合你。
改善
这就是使用JavaScript只发送文件体的方法,这使得CGI脚本中删除文件最后和前四行的部分变得多余:
不要忘记更改URL。
由EvenListner调用的uploadFile函数,EvenListner必须在出现html
<input type="file" id="file">
时定义我相信你可以进一步改进这个脚本,摆脱复杂的网址编辑和删除文件头的原因。请让我知道你是如何改进脚本的。
而且要注意upload.cgi脚本至少应该通过
.htaccess
来保护--我对网络安全知之甚少!