본문 바로가기
프로그래밍/PHP

PHP 파일 업로드와 다운로드 만들기

by pentode 2018. 4. 21.

PHP 에서 간단히 파일 업로드와 다운로드를 구현해 봅니다. 파일 업로드 할때는 파일명 중복 방지와 파일명을 추측해서 다운로드 하는 것을 방지하기 위해 랜덤하게 파일명을 만들어서 서버에 저장합니다. 보안을 위해서는 파일 업로드 위치를 웹루트 밖에 하는것이 좋습니다.


파일 정보를 데이터베이스에 저장할 때도 파일을 구분할 파일 아이디를 예측 가능하지 않게 만들어서 다운로드시 사용합니다. 보안을 위해서는 다운로드 프로그램에 권한이 적용되면 더 좋습니다.


전체 예제 파일은 글 하단에 첨부하여 두었습니다.



1. 파일정보를 저장할 테이블 구조

- 데이터베이스는 캐릭터셋 utf8, collation은 utf8_general_ci 로 만들었습니다.


CREATE TABLE upload_file (

  file_id   VARCHAR(255) NOT NULL PRIMARY KEY,

  name_orig VARCHAR(255),

  name_save VARCHAR(255),

  reg_time  TIMESTAMP NOT NULL

);


- file_id : 각각의 파일을 유일하게 구별하는 아이디 값입니다.

- name_orig : 업로드 당시의 원래 파일 이름입니다. 다운로드시 이 이름으로 파일이 생성됩니다.

- name_save : 예측하기 힘들게 변경된 파일 이름입니다. 이 이름으로 서버에 파일이 저장됩니다.

- reg_time : 파일이 업로드된 시간 입니다.



2. 파일 업로드 폼(up/upload.php)


<script type="text/javascript">

function formSubmit(f) {

    // 업로드 할 수 있는 파일 확장자를 제한합니다.

var extArray = new Array('hwp','xls','doc','xlsx','docx','pdf','jpg','gif','png','txt','ppt','pptx');

var path = document.getElementById("upfile").value;

if(path == "") {

alert("파일을 선택해 주세요.");

return false;

}

var pos = path.indexOf(".");

if(pos < 0) {

alert("확장자가 없는파일 입니다.");

return false;

}

var ext = path.slice(path.indexOf(".") + 1).toLowerCase();

var checkExt = false;

for(var i = 0; i < extArray.length; i++) {

if(ext == extArray[i]) {

checkExt = true;

break;

}

}


if(checkExt == false) {

alert("업로드 할 수 없는 파일 확장자 입니다.");

    return false;

}

return true;

}

</script>


<form name="uploadForm" id="uploadForm" method="post" action="upload_process.php" 

      enctype="multipart/form-data" onsubmit="return formSubmit(this);">

    <div>

        <label for="upfile">첨부파일</label>

        <input type="file" name="upfile" id="upfile" />

    </div>

    <input type="submit" value="업로드" />

</form>


- 업로드 폼을 체크하는 formSubmit(); 자바스크립트에서 업로드 할 수 있는 파일의 확장자를 제한하고 있습니다.

- 파일 업로드를 위해서 method="post" 이고, enctype="multipart/form-data" 로 지정해야 합니다.

- 파일 선택 엘리먼트의 이름은 "upfile" 입니다.






3. 서버측 파일 업로드 처리(upload_process.php)


<?php

$db_conn = mysqli_connect("localhost", "testdbadm", "testdbadm", "testdb");


if(isset($_FILES['upfile']) && $_FILES['upfile']['name'] != "") {

    $file = $_FILES['upfile'];

    $upload_directory = 'data/';

    $ext_str = "hwp,xls,doc,xlsx,docx,pdf,jpg,gif,png,txt,ppt,pptx";

    $allowed_extensions = explode(',', $ext_str);

    

    $max_file_size = 5242880;

    $ext = substr($file['name'], strrpos($file['name'], '.') + 1);

    

    // 확장자 체크

    if(!in_array($ext, $allowed_extensions)) {

        echo "업로드할 수 없는 확장자 입니다.";

    }

    

    // 파일 크기 체크

    if($file['size'] >= $max_file_size) {

        echo "5MB 까지만 업로드 가능합니다.";

    }

    

    $path = md5(microtime()) . '.' . $ext;

    if(move_uploaded_file($file['tmp_name'], $upload_directory.$path)) {

        $query = "INSERT INTO upload_file (file_id, name_orig, name_save, reg_time) VALUES(?,?,?,now())";

        $file_id = md5(uniqid(rand(), true));

        $name_orig = $file['name'];

        $name_save = $path;

        

        $stmt = mysqli_prepare($db_conn, $query);

        $bind = mysqli_stmt_bind_param($stmt, "sss", $file_id, $name_orig, $name_save);

        $exec = mysqli_stmt_execute($stmt);

      

        mysqli_stmt_close($stmt);

        

        echo"<h3>파일 업로드 성공</h3>";

        echo '<a href="file_list.php">업로드 파일 목록</a>';

        

    }

} else {

    echo "<h3>파일이 업로드 되지 않았습니다.</h3>";

    echo '<a href="javascript:history.go(-1);">이전 페이지</a>';

}


mysqli_close($db_conn);

?>


- 데이터베이스는 mysqli 를 사용해서 prepared statement로 작업을 했습니다.SQL Injection을 대비하는 가장 좋은 방법 입니다.

- 서버측에서도 업로드된 파일의 확장자를 체크해야 합니다.

- 업로드 가능한 파일 용량도 체크합니다. 제한을 하지 않을 경우 대용량 파일을 업로드 하여 서버를 다운시키는 공격을 받을 수 있습니다.

- $file_iduniqid(rand(), true) 함수로 생성한 유일한 값을 md5() 로 해시해서 추측하기 힘들 값을 만들어 사용합니다.



4. 파일 목록 조회(file_list.php)


<table border="1">

<tr>

<th>파일 아이디</th>

<th>원래 파일명</th>

<th>저장된 파일명</th>

</tr>

<?php

$db_conn = mysqli_connect("localhost", "testdbadm", "testdbadm", "testdb");

$query = "SELECT file_id, name_orig, name_save FROM upload_file ORDER BY reg_time DESC";

$stmt = mysqli_prepare($db_conn, $query);

$exec = mysqli_stmt_execute($stmt);

$result = mysqli_stmt_get_result($stmt);

while($row = mysqli_fetch_assoc($result)) {

?>

<tr>

  <td><?= $row['file_id'] ?></td>

  <td><a href="download.php?file_id=<?= $row['file_id'] ?>" target="_blank"><?= $row['name_orig'] ?></a></td>

  <td><?= $row['name_save'] ?></td>

</tr>

<?php


mysqli_free_result($result); 

mysqli_stmt_close($stmt);

mysqli_close($db_conn);

?>

</table>


- 파일 다운로드를 위해 파일 목록을 조회합니다.

- download.php?file_id=<?= $row['file_id'] ?> 로 파일 다운로드 링크를 만듭니다. 파일 아이디 단순한 번호로 만들면 번호만 바꿔서 호출하여 다른 파일을 다운로드 받을 수 있게 됩니다. 필요하다면 실제 구현에서는 권한을 적용하여 자신에게 다운로드 권한이 없는 파일을 다운 받을 수 없도록 하여야 겠습니다.




5. 파일 다운로드(download.php)


<?php

$file_id = $_REQUEST['file_id'];


$db_conn = mysqli_connect("localhost", "testdbadm", "testdbadm", "testdb");

$query = "SELECT file_id, name_orig, name_save FROM upload_file WHERE file_id = ?";

$stmt = mysqli_prepare($db_conn, $query);


$bind = mysqli_stmt_bind_param($stmt, "s", $file_id);

$exec = mysqli_stmt_execute($stmt);


$result = mysqli_stmt_get_result($stmt);

$row = mysqli_fetch_assoc($result);


$name_orig = $row['name_orig'];

$name_save = $row['name_save'];


$fileDir = "data/";

$fullPath = $fileDir."/".$name_save;

$length = filesize($fullPath);


header("Content-Type: application/octet-stream");

header("Content-Length: $length");

header("Content-Disposition: attachment; filename=".iconv('utf-8','euc-kr',$name_orig));

header("Content-Transfer-Encoding: binary");


$fh = fopen($fullPath, "r");

fpassthru($fh);


mysqli_free_result($result);

mysqli_stmt_close($stmt);

mysqli_close($db_conn);


exit;

?>


- GET 방식으로 받은 file_id를 이용하여 파일정보를 조회합니다.

- Content-Disposition 헤더를 사용하여 다운로드될 파일 이름을 원래 파일명으로 지정합니다. 데이터베이스에 UTF-8로 데이터가 저장되어 있으므로 euc-kr로 변환해야 한글 파일명이 깨지지 않습니다.



이것으로 간단히 PHP 에서 파일을 업로드하고 다운로드 하는 방법을 알아보았습니다. 실제 구현에서는 데이터베이스 연결정보를 추출해서 include 파일로 만들고, 업로드 위치를 웹루트 밖으로 바꾸고, 업로드/다운로드에 권한을 적용하고, 데이터베이스 에러처리, 파일 관련 에러처리 등이 추가되어야 겠습니다.


※ 전체소스

upload.zip


반응형