Tôi đã nghĩ rằng tôi sẽ viết một câu trả lời ngắn (đối với tôi đây là ngắn gọn) để tôi có thể tóm tắt các điểm của mình.
Một số "Phương pháp hay nhất" khi tạo hệ thống lưu trữ tệp. Lưu trữ tệp là một danh mục rộng nên số dặm của bạn có thể thay đổi đối với một số trong số này. Hãy xem chúng giống như gợi ý về những gì tôi thấy hoạt động tốt.
Tên tệp Không lưu trữ tệp với tên do người dùng cuối cung cấp. Họ có thể và sẽ sử dụng tất cả các loại nhân vật tồi tệ sẽ khiến cuộc sống của bạn trở nên khốn khổ. Một số có thể tệ như '
dấu ngoặc kép, về cơ bản trên linux làm cho nó không thể đọc hoặc thậm chí xóa tệp (trực tiếp). Một số thứ có thể đơn giản như một không gian nhưng tùy thuộc vào nơi bạn sử dụng nó và hệ điều hành trên máy chủ của bạn, bạn có thể kết thúc bằng
one%20two.txt
hoặc one+two.txt
hoặc one two.txt
có thể hoặc không thể tạo ra tất cả các loại vấn đề trong các liên kết của bạn.
Điều tốt nhất cần làm là tạo một hàm băm, giống như sha1
điều này có thể đơn giản như {user_id}{orgianl_name}
Tên người dùng làm cho nó ít có khả năng xảy ra xung đột với tên tệp của người dùng khác.
Tôi thích làm file_hash('sha1', $contents)
theo cách đó nếu ai đó tải lên cùng một tệp nhiều hơn thì một khi bạn có thể nắm bắt được điều đó (nội dung giống nhau thì hàm băm giống nhau). Nhưng nếu bạn mong đợi có các tệp lớn, bạn có thể muốn thực hiện một số đánh dấu băng ghế dự bị trên đó để xem loại hiệu suất của nó. Tôi chủ yếu xử lý các tệp nhỏ nên nó hoạt động tốt.
Bất kể bạn làm gì, tôi sẽ đặt tiền tố bằng dấu thời gian time().'-'.$filename
. Đây là thông tin hữu ích cần có, vì đây là thời gian tuyệt đối mà tệp được tạo.
Đối với tên mà người dùng cung cấp cho tệp. Chỉ cần lưu trữ nó trong bản ghi cơ sở dữ liệu. Bằng cách này, bạn có thể hiển thị cho họ tên mà họ mong đợi, nhưng sử dụng tên mà bạn biết luôn an toàn cho các liên kết.
$ filename ='some crapy ^ fileane.jpg';
$ext = strrchr($filename, '.');
echo "\nExt: {$ext}\n";
$hash = sha1('some crapy^ fileane.jpg');
echo "Hash: {$hash}\n";
$time = time();
echo "Timestamp: {$time}\n";
$hashname = $time.'-'.$hash.$ext;
echo "Hashname: $hashname\n";
Ouputs
Ext: .jpg
Hash: bb9d2c2c7c73bb8248537a701870e35742b41c02
Timestamp: 1511853063
Hashname: 1511853063-bb9d2c2c7c73bb8248537a701870e35742b41c02.jpg
Bạn có thể dùng thử tại đây
Đường dẫn không bao giờ lưu trữ đường dẫn đầy đủ đến tệp. Tất cả những gì bạn cần trong cơ sở dữ liệu là băm từ việc tạo tên băm. Đường dẫn "gốc" đến thư mục mà tệp được lưu trữ phải được thực hiện bằng PHP. Điều này có một số lợi ích.
- ngăn chặn việc chuyển nhượng thư mục. Bởi vì bạn không đi qua bất kỳ phần nào của con đường xung quanh, bạn không phải lo lắng nhiều về việc ai đó trượt
\..\..
ở đó và đi những nơi mà họ không nên làm. Một ví dụ kém về điều này là ai đó ghi đè.htpassword
bằng cách tải lên một tệp có tên như vậy với thư mục nằm ngang trong đó. - Có các liên kết trông đồng nhất hơn, kích thước đồng nhất, bộ ký tự đồng nhất.
https://en.wikipedia.org/wiki/Directory_traversal_attack
- Bảo trì. Đường dẫn thay đổi, Máy chủ thay đổi. Nhu cầu về hệ thống của bạn thay đổi. Nếu bạn cần định vị lại các tệp đó, nhưng bạn đã lưu trữ đường dẫn đầy đủ tuyệt đối đến chúng trong DB, bạn đang gặp khó khăn khi dán mọi thứ lại với nhau bằng
symlinks
hoặc cập nhật tất cả hồ sơ của bạn.
Có một số ngoại lệ cho điều này. Nếu bạn muốn lưu trữ chúng trong một thư mục hàng tháng hoặc theo tên người dùng. Bạn có thể lưu phần đó của đường dẫn, trong một trường riêng biệt. Nhưng ngay cả trong trường hợp đó, bạn có thể xây dựng nó động dựa trên dữ liệu được lưu trong bản ghi. Tôi thấy tốt nhất là nên tiết kiệm thông tin đường dẫn càng ít càng tốt. Và chúng tạo một cấu hình hoặc một hằng số mà bạn có thể sử dụng ở tất cả những nơi bạn cần để đặt đường dẫn đến tệp.
Cũng là path
và liên kết rất khác nhau, vì vậy chỉ cần lưu tên, bạn có thể liên kết nó từ bất kỳ trang PHP nào bạn muốn mà không cần phải trừ dữ liệu khỏi đường dẫn. Tôi luôn thấy rằng việc thêm vào tên tệp sau đó trừ khỏi một đường dẫn sẽ dễ dàng hơn.
Cơ sở dữ liệu (chỉ là một số gợi ý, việc sử dụng có thể khác nhau) Như mọi khi với dữ liệu, hãy tự hỏi bản thân, ai, cái gì, ở đâu, khi nào
- id -
int
khóa chính tự động gia tăng - user_id -
int
khóa ngoại, ai đã tải nó lên - băm -
char[40] *sha1*, unique
cái gì băm - tên băm -
varchar
{timestampl} - {hash}. {ext} ở đâu tên tệp trên ổ cứng - tên tệp -
varchar
tên ban đầu do người dùng đặt, theo cách đó, chúng tôi có thể cho họ biết tên mà họ mong đợi (nếu điều đó quan trọng) - trạng thái -
enum[public,private,deleted,pending.. etc]
trạng thái của tệp, tùy thuộc vào trường hợp sử dụng của bạn, bạn có thể phải xem lại tệp hoặc có thể một số tệp là riêng tư chỉ người dùng mới có thể xem chúng, có thể một số là công khai, v.v. - status_date -
timestamp|datetime
thời gian trạng thái đã được thay đổi. - create_date -
timestamp|datetime
khi nào thời gian tệp được tạo, dấu thời gian được ưu tiên hơn vì nó giúp làm một số việc dễ dàng hơn nhưng nó phải giống cách sử dụng dấu thời gian trong tên băm, trong trường hợp đó. - loại -
varchar
- loại kịch câm, có thể hữu ích để đặt loại kịch câm khi tải xuống, v.v.
Nếu bạn muốn những người dùng khác tải lên cùng một tệp và bạn sử dụng file_hash
bạn có thể tạo hash
trường một chỉ mục duy nhất được kết hợp của user_id
và hash
theo cách này, nó sẽ chỉ xung đột nếu cùng một người dùng tải lên cùng một tệp. Bạn cũng có thể làm điều đó dựa trên dấu thời gian và mã băm, tùy thuộc vào nhu cầu của bạn.
Đó là những thứ cơ bản mà tôi có thể nghĩ ra, đây không phải là điều tuyệt đối chỉ là một số lĩnh vực tôi nghĩ sẽ hữu ích.
Sẽ rất hữu ích khi tự có mã băm, nếu bạn tự lưu trữ nó, bạn có thể lưu trữ nó trong CHAR(40)
cho sha1 (chiếm ít dung lượng hơn trong DB thì VARCHAR
) và đặt đối chiếu, thành UTF8_bin
là hệ nhị phân. Điều này làm cho các tìm kiếm trên nó phân biệt chữ hoa chữ thường. Mặc dù có rất ít khả năng xảy ra xung đột hàm băm, nhưng điều này chỉ bổ sung thêm một chút bảo vệ vì các hàm băm là các chữ cái viết hoa và viết thường.
Bạn luôn có thể tạo hashname
nhanh chóng nếu bạn lưu trữ tiện ích mở rộng và dấu thời gian tách biệt. Nếu bạn thấy mình tạo ra mọi thứ hết lần này đến lần khác, bạn có thể chỉ muốn lưu trữ nó trong DB để đơn giản hóa công việc trong PHP.
Tôi thích chỉ đặt hàm băm trong liên kết, không có tiện ích mở rộng, không có bất kỳ thứ gì nên các liên kết của tôi trông như thế này.
http://www.example.com/download/ad87109bfff0765f4dd8cf4943b04d16a4070fea
Đơn giản thực sự, chung chung thực sự, an toàn trong các url luôn có cùng kích thước, v.v.
hashname
cho "tệp" này sẽ giống như thế này
1511848005-ad87109bfff0765f4dd8cf4943b04d16a4070fea.jpg
Nếu bạn có xung đột với cùng một tệp và người dùng khác nhau (mà tôi đã đề cập ở trên). Bạn luôn có thể thêm phần dấu thời gian vào liên kết, user_id hoặc cả hai. Nếu bạn sử dụng user_id, có thể hữu ích nếu bạn đặt nó bằng các số không. Ví dụ:một số người dùng có thể có ID:1
và một số có thể là ID:234
vì vậy bạn có thể để nó vào 4 chỗ và đặt chúng thành 0001
và 0234
. Sau đó, thêm nó vào hàm băm, hầu như không gây chú ý:
1511848005-ad87109bfff0765f4dd8cf4943b04d16a4070fea0234.jpg
Điều quan trọng ở đây là vì sha1
luôn là 40
và id luôn là 4
chúng ta có thể tách hai loại một cách chính xác và dễ dàng. Và bằng cách này, bạn vẫn có thể tra cứu nó một cách duy nhất. Có rất nhiều tùy chọn khác nhau nhưng rất nhiều tùy thuộc vào nhu cầu của bạn.
Truy cập Chẳng hạn như tải xuống. Bạn phải luôn xuất tệp bằng PHP, đừng cấp cho họ quyền truy cập trực tiếp vào tệp. Cách tốt nhất là lưu trữ các tệp bên ngoài webroot (phía trên public_html
hoặc www
thư mục ). Sau đó, trong PHP, bạn có thể đặt tiêu đề thành đúng loại ans về cơ bản là đọc ra tệp. Điều này hoạt động cho hầu hết mọi thứ ngoại trừ video. Tôi không xử lý video nên đó là một chủ đề nằm ngoài kinh nghiệm của tôi. Nhưng tôi thấy tốt nhất nên nghĩ về nó vì tất cả dữ liệu tệp đều là văn bản, các tiêu đề của nó tạo nên văn bản đó thành hình ảnh hoặc tệp excel hoặc pdf.
Lợi thế lớn của việc không cấp cho họ quyền truy cập trực tiếp vào tệp là nếu bạn có trang web thành viên, không muốn nội dung của bạn có thể truy cập mà không cần đăng nhập, bạn có thể dễ dàng kiểm tra bằng PHP nếu họ đã đăng nhập trước khi cung cấp nội dung cho họ. Và, vì tệp nằm ngoài webroot, họ không thể truy cập tệp theo bất kỳ cách nào khác.
Điều quan trọng nhất là chọn thứ gì đó nhất quán, vẫn đủ linh hoạt để xử lý mọi nhu cầu của bạn.
Tôi chắc rằng mình có thể đưa ra nhiều điều hơn, nhưng nếu bạn có bất kỳ đề xuất nào, hãy bình luận.
LƯU LƯỢNG QUY TRÌNH CƠ BẢN
- Người dùng gửi biểu mẫu (
enctype="multipart/form-data"
)
https://www.w3schools.com/tags/att_form_enctype.asp
- Máy chủ nhận bài đăng từ biểu mẫu, Super Globals
$_POST
và$_FILES
http://php.net/manual/en/reserved.variables.files .php
$_FILES = [
'fieldname' => [
'name' => "MyFile.txt" // (comes from the browser, so treat as tainted)
'type' => "text/plain" // (not sure where it gets this from - assume the browser, so treat as tainted)
'tmp_name' => "/tmp/php/php1h4j1o" // (could be anywhere on your system, depending on your config settings, but the user has no control, so this isn't tainted)
'error' => "0" //UPLOAD_ERR_OK (= 0)
'size' => "123" // (the size in bytes)
]
];
-
Kiểm tra lỗi
if(!$_FILES['fielname']['error'])
-
Vệ sinh tên hiển thị
$filename = htmlentities($str, ENT_NOQUOTES, "UTF-8");
-
Lưu tệp, tạo bản ghi DB (PSUDO-CODE)
Như thế này:
$path = __DIR__.'/uploads/'; //for exmaple
$time = time();
$hash = hash_file('sha1',$_FILES['fielname']['tmp_name']);
$type = $_FILES['fielname']['type'];
$hashname = $time.'-'.$hash.strrchr($_FILES['fielname']['name'], '.');
$status = 'pending';
if(!move_uploaded_file ($_FILES['fielname']['tmp_name'], $path.$hashname )){
//failed
//do somehing for errors.
die();
}
//store record in db
http://php.net/manual/en/ Chức năng.move -uploaded-file.php
-
Tạo liên kết (thay đổi tùy theo định tuyến), cách đơn giản là tạo liên kết của bạn như sau
http://www.example.com/download?file={$hash}
nhưng xấu hơn thìhttp://www.example.com/download/{$hash}
-
người dùng nhấp vào liên kết để chuyển đến trang tải xuống.
lấy INPUT và tra cứu hồ sơ
$hash = $_GET['file'];
$stmt = $PDO->prepare("SELECT * FROM attachments WHERE hash = :hash LIMIT 1");
$stmt->execute([":hash" => $hash]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
print_r($row);
http://php.net/manual/en/intro.pdo.php
Vv ....
Chúc mừng!