我们在开发pdk365的时候,前端开发的机子突然无法访问他本机架起的Django服务器的静态文件了。
报错就类似于下面的,但我遇见的问题不同的是,它们的根路径是一样的。
The joined path (/var/folders/t9/7v8mki3s3h39fzylxfpsk9640000nn/T/tmpmvb9wxq6) is located outside of the base path component (/Users/zorgan/Desktop/app/draft1/media)
我直接去StackOverFlow查,发现其他人遇见的问题都是没有正确指定静态文件路径。然而我们是没有这个情况的,因为在我们几个后端开发的机子上,这个代码没有问题啊!😀
当时忙于其他事,我没有深入去想,以为只是配置问题。直到今天万恶的PM又问了我一次,我才开始思考这个问题。
然后...我想起来曾经从一篇博客中看到对Django早期版本的静态文件访问系统的Bug分析,那个路径拼接Bug可以达到任意文件访问的效果。这个报错怕不是对这个Bug的修补,那么我瞅了一眼settings.py
,大概猜到了为什么。去掉了最后一个'/'之后,就能正常访问了
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static/")
]
So,Why?
让我们去抛出异常的地方看一看,也就是django.utils._os.safe_join()
def safe_join(base, *paths):
"""
Join one or more path components to the base path component intelligently.
Return a normalized, absolute version of the final path.
Raise ValueError if the final path isn't located inside of the base path
component.
"""
base = force_text(base)
paths = [force_text(p) for p in paths]
final_path = abspath(join(base, *paths))
base_path = abspath(base)
# Ensure final_path starts with base_path (using normcase to ensure we
# don't false-negative on case insensitive operating systems like Windows),
# further, one of the following conditions must be true:
# a) The next character is the path separator (to prevent conditions like
# safe_join("/dir", "/../d"))
# b) The final path must be the same as the base path.
# c) The base path must be the most root path (meaning either "/" or "C:\\")
if (not normcase(final_path).startswith(normcase(base_path + sep)) and
normcase(final_path) != normcase(base_path) and
dirname(normcase(base_path)) != normcase(base_path)):
raise SuspiciousFileOperation(
'The joined path ({}) is located outside of the base path '
'component ({})'.format(final_path, base_path))
return final_path
先看一眼这个多次出现的函数normcase
的作用
def normcase(s):
"""Normalize case of pathname.
Makes all characters lowercase and all slashes into backslashes."""
s = os.fspath(s)
try:
if isinstance(s, bytes):
return s.replace(b'/', b'\\').lower()
else:
return s.replace('/', '\\').lower()
except (TypeError, AttributeError):
if not isinstance(s, (bytes, str)):
raise TypeError("normcase() argument must be str or bytes, "
"not %r" % s.__class__.__name__) from None
raise
把所有的'/'
转为'\\'
并且字母小写。
在与normcase(s)
的同一个文件里,能看到
sep = '\\'
可以看出来应该是not normcase(final_path).startswith(normcase(base_path + sep))
这个条件不成立。因为路径拼接在某些情况下,它不会帮你去掉多余的'\'
,有些会帮你去掉重复的'\'
,所以出现了在我们几个后端开发的电脑上正常,在前端的电脑上不正常的情况。至于这个成立原因是什么,由于PM不给我出问题的机子继续研究,暂时无法得知。