[강좌] php+mysql 게시판 만들기 #소-2
안녕하세요. 디망쉬입니다.
이걸로 강좌는 끝나는군요. 쩝. 마지막 강좌입니다. 유종의 미를 거둘 수 있게 나름대로 열심히 타이핑 하겠습니다. ^^; (하지만 감기로 인해 어질 어질)
소. 자료실 기능 및 잘잘한 기능 붙이기
b. 자료실 기능
사실 매우 꺼려지는 부분입니다. 게시판에 파일 업로드 기능을 넣음으로서 여러 분이 사용하실 서버의 보안은 한단계 낮아지게 되거든요. 여차하면 순식간에 한바탕 난리가 날 수도 있습니다. 이 기능을 넣고 말고는 여러 분의 선택이지만, 넣으신다면 여러모로 부족한 기능을 훨씬 강화시키고(기능=보안 라는 의미에요) 다듬어서 안정적인 서비스가 되도록 하셔요.
1. 자료실 기능에 필요한 정보들 별 거 없습니다. 보안상 저는 자료실 기능에서 파일 이름과 경로, 파일 사이즈 등을 DB 에 직접 저장시키기로 했지요. 이 정보가 필요하겠죠.
이것 뿐입니다. 이것의 attribute 는 어떻게 될까요?
filepath varchar(100), filename varchar(100), filesize int(4), |
바로 이겁니다. filepath 에 업로드한 파일이 존재할 디렉토리 이름을, filename 에 업로드한 파일의 이름을, filesize 에는 업로드한 파일의 용량을 저장합니다.
그렇다면 일단 글 쓸 때 파일 업로드 입력 폼을 만들어볼까요?
? /*-------------------------- filename : fill.php3 --------------------------*/ require "function.php3";
error_board();
echo (" // 전체 화면 출력
<html>
<head> <title>글 입력</title> </head>
<body> <center> 광고 및 욕설 글은 언제나 발견되는 대로 삭제됩니다. ^^<p>
<form method="post" action="run.php3" enctype="multipart/form-data"> <input type="hidden" name="board" value="$board">
<input type="hidden" name="mode" value="write">
<table width="550" cellspacing="1" border="0" cellpadding="2"> <tr> <td align="left"><font size="2"><b> |<a href="list.php3?board=$board&page=$nowpage"> 게시물 목록</a> | </b></font></td> </tr> </table>
<table width="400" cellspacing="1" bgcolor="#A5A595" border="0" cellpadding="5"> <!-- 작성자명 --> <tr> <td width="30%" bgcolor="#A5A595" align="center"> <font size="2" color="white"><b>작성자명</b></font></td> <td width="70%" bgcolor="white" align="left"> <font color="white"> <input type="text" name="fil[name]" value="$mwbwd[name]" size="10" maxlength="10"> </font></td> </tr>
<!-- Email --> <tr> <td width="30%" bgcolor="#A5A595" align="center"> <font size="2" color="white"><b>E-mail</b></font></td> <td width="70%" bgcolor="white" align="left"> <font color="white"> <input type="text" name="fil[email]" value="$mwbwd[email]" size="20" maxlength="256"> </font></td> </tr>
<!-- Homepage --> <tr> <td width="30%" bgcolor="#A5A595" align="center"> <font size="2" color="white"><b>Homepage</b></font></td> <td width="70%" bgcolor="white" align="left"> <font color="white"> <input type="text" name="fil[homepage]" value="$mwbwd[homepage]" size="20" maxlength="256"> </font></td> </tr>
<!-- Password --> <tr> <td width="30%" bgcolor="#A5A595" align="center"> <font size="2" color="white"><b>비밀번호</b></font></td> <td width="70%" bgcolor="white" align="left"> <font color="white"> <input type="password" name="fil[pw]" size="10" maxlength="20"> </font></td> </tr>
<!-- Subject --> <tr> <td width="30%" bgcolor="#A5A595" align="center"> <font size="2" color="white"><b>글 제목</b></font></td> <td width="70%" bgcolor="white" align="left"> <font color="white"> <input type="text" name="fil[title]" size="30" maxlength="256"> </font></td> </tr>
<!-- 파일 업로드 --> <tr> <td width="30%" bgcolor="#A5A595" align="center"> <font size="2" color="white"><b>파 일</b></font></td> <td width="70%" bgcolor="white" align="left"> <font color="white"> <input type="file" name="userfile" size="20" maxlength="256"> </font></td> </tr>
<!-- Text --> <tr> <td width="30%" bgcolor="#C5C5B5" align="center"> <font size="2" color="white"><b>본 문</b></font></td> <td width="70%" bgcolor="white" align="left"> <font color="white"> <textarea name="fil[text]" wrap="hard" rows="10" cols="36"></textarea> </font></td> </tr> </table> <input type="submit" value="저장하자"> <input type="reset" value="재작성"> </form>
<table width="550" cellspacing="1" border="0" cellpadding="2"> <tr> <td align="right"><font size="2"><b> |<a href="list.php3?board=$board&page=$nowpage"> 게시물 목록</a> | </b></font></td> </tr> </table>
</center>
</body>
</html> "); // 전체 화면 출력 완료 ?>
여기에다가
<!-- 파일 업로드 --> <tr> <td width="30%" bgcolor="#A5A595" align="center"> <font size="2" color="white"><b>파 일</b></font></td> <td width="70%" bgcolor="white" align="left"> <font color="white"> <input type="file" name="userfile" size="20" maxlength="256"> </font></td> </tr> |
이걸 넣었습니다. 잘 보시면 보이실 듯. ^^;
이제 업로드가 되어 서버로 넘어오는 파일의 정보를 DB 에 저장하고 파일 자체는 파일로 저장하면 됩니다.
여기서 바로 재미있는게 나옵니다. 위에서 파일 업로드 관련 정보가 userfile 이라는 이름의 변수로 넘어가게 했죠? (<input> tag 에서용) 이걸 통해 업로드된 파일은 $userfile_name 과 $userfile_size 라는 변수를 통해 새로 태어납니다. $userfile_name 에는 업로드된 파일의 이름이, $userfile_size 에는 업로드된 파일의 용량이. 오오오. 편하죠? 매우 편합니다.
이제 run.php3 에서 글 입력하는 부분을 약간 다듬어보죠.
$result = mysql_query("INSERT INTO $board VALUES('', $ist[num], $ist[idx], '$fil[title]', '$fil[text]', 0, 0, 0, 0, 0, '', '', 0, '$fil[name]', '$passwd', '$fil[email]', '$fil[homepage]', $intime)", $connect); |
여기에서 세번째 줄에 있는 '', '', 0 이 부분이 바로 파일 업로드 관련입니다. 이것을
$result = mysql_query("INSERT INTO $board VALUES('', $ist[num], $ist[idx], '$fil[title]', '$fil[text]', 0, 0, 0, 0, 0, '', '$userfile_name', $userfile_size, '$fil[name]', '$passwd', '$fil[email]', '$fil[homepage]', $intime)", $connect); |
로 고칩니다. 이제 파일이 저장될 경로명을 정한 뒤 넣으면 되겠군요.
그런데 왜 저장될 디렉토리를 지정해줄까요? 여러 사람들이 파일을 업로드하다보면 이미 업로드된 파일과 동일한 이름의 파일이 업로드될 수 있습니다. 이럴 경우 기존의 파일이 사라질 수 있죠. 그래서 각 각의 파일을 제 각 각의 디렉토리에 넣어주는 겁니다. 물론 이러한 디렉토리는 고유한 이름을 지녀야하구요.
저는 설명만 하면 되니(여러분은 설명을 이해하고 응용하면 되구요) 간단하게 경로를 입력된 시각으로 하겠습니다.
$result = mysql_query("INSERT INTO $board VALUES('', $ist[num], $ist[idx], '$fil[title]', '$fil[text]', 0, 0, 0, 0, 0, '$intime', '$userfile_name', $userfile_size, '$fil[name]', '$passwd', '$fil[email]', '$fil[homepage]', $intime)", $connect); |
이제 업로드된 파일을 해당 디렉토리를 만든 뒤 그곳에 저장하면 됩니다.
그전에 어느 디렉토리를 파일 저장 폴더로 쓸지 정할 필요가 있겠군요.
간단하게 upload/게시판명 에 하겠습니다. 현재 게시판 만들며 테스트하고 있는 게시판의 이름(mysql 에서 테이블명)이 testboard 니까 이건 파일을 올리면 upload/testboard 에 올라가겠죠? 이때 중요한 것은 testboard 디렉토리의 퍼미션(permission)이 777 이어야 합니다. 만약 리눅스나 유닉스등의 시스템에서 root 권한이 있다면 디렉토리의 access 그룹을 nobody 로 한 뒤 770 으로 하셔도 되구요. 퍼미션이 뭔지 잘 모르신다구요? ^^; 어쩔 수 없죠. 넘어갈 수 밖에. ^^; (흑. 그래요. 어지러워서 빨리 타이핑 완료하고픈 마음이 커서 그래유 T_T)
mkdir upload mkdir upload/testboard chmod 777 upload/testboard |
라고 하시면 됩니다. 물론 run.php3 파일이 있는 곳에서요.
자아. 준비가 됐습니다. 이제 run.php3 에서 파일이 올라가게 하죠.
if ($userfile_size > 0) { mkdir("upload/$board/$intime", 0755); exec("mv "$userfile" "upload/$board/$intime/$userfile_name""); chmod("upload/$board/$intime/$userfile_name", 0644); } |
상당히 쉽습니다. 일단 $userfile_size, 즉 파일의 용량이 0 바이트가 아닐 경우 해당 내용을 실행한다는거죠. 0 바이트면? 파일을 올린게 아니거나 깨진 파일이므로 업로드를 하지 않습니다.
mkdir("upload/$board/$intime", 0755); |
으흠. 이건 upload/$board 에 글이 작성된 시각의 디렉토리를 만듭니다. 디렉토리의 퍼미션은 755(rwxr-xr-x). 여기서 mkdir 함수는 디렉토리를 만드는 함수죠.
그런 뒤
exec("mv "$userfile" "upload/$board/$intime/$userfile_name""); |
업로드된 파일을 방금 만든 디렉토리에 업로드된 파일 이름으로 이동시킵니다(mv). 이때 exec 함수는 해당 시스템에서의 시스템 명령어(?)를 실행시키는 것입니다.
자 이제 업로드된 파일의 퍼미션을 644 로 바꿉니다. 왜냐구요? 해당 파일을 읽기만 가능하고 실행을 하지 못하게 하려구요. 그건 또 왜냐구요? ^^? 보안때문입니다. 만약에, 그러니까 마아아안약에 업로드된 파일이 cgi 확장자이고 서버는 cgi 확장자를 지원하며, 어떠한 방법을 통해 cgi 의 위치를 알아내어 이것을 웹 브라우저를 통해 실행시킬 경우? 헤. 끔찍하군요. 그래서 해당 파일의 권한에서 실행 권한을 뺏어버린 겁니다.
이걸로 업로드는 끝났습니다. 이제 다운로드가 되게 해보죠. 이것을 하기 위해 우리는 dn.php3 를 새로 만들어야 합니다. 어떻게 만들까요? 그걸 생각하기 전에 어떻게 파일을 다운로드 시킬까요? 순서를 짜보죠.
해당 파일의 글 번호를 넘겨줌 --> DB 에서 해당 글 번호를 근거로 파일 이름과 위치를 알아냄 --> 웹 브라우저로 해당 파일을 보내줌
일단 첫 번째 것은
dn.php3?board=testboard&no=10 |
뭐 이런 식이면 되겠죠. 정확히 하자면
dn.php3?board=$board&no=$no |
이겠구요.
두 번째 과정은 우리가 많이 해왔던 select 함수를 통해 할 수 있습니다.
$result = mysql_query("SELECT * FROM $board WHERE no=$no", $connect);
$fileinfo = mysql_fetch_array($result); |
이렇게 하면
$fileinfo[filepath] 에는 파일 경로가, $fileinfo[filename] 에는 파일 이름이, $fileinfo[filesize] 에는 파일 용량이 |
저장됩니다. 우리가 필요한 것은 물론 앞의 두 개입니다. 이것을 이제 웹 브라우저로 보내주면 끝납니다.
그것을 하기 위해서는 파일 access 를 해야 합니다. fopen, fread 가 그것이죠.
일단 fopen 함수는 파일을 읽어오는, 즉 여는 함수입니다. 해당 파일을 읽어와 이걸 fread 로 읽어낸 뒤 출력해주면 웹 브라우저가 이걸 받아내어 클라이언트 하드에 저장하는거죠. 어...어렵나요? 소스부터 보겠습니다.
<? /*-------------------------- filename : dn.php3 --------------------------*/ $connect = mysql_connect("localhost","아이디","비번"); mysql_select_db("사용DB명",$connect);
$result = mysql_query("SELECT * FROM $board WHERE no=$no", $connect);
$ff = mysql_fetch_array($result);
$paths = "upload/$board/$ff[filluptime]/$ff[filename]";
if ($fp = fopen($paths, "r")) { Header("Content-type: application/octet-stream"); Header("Content-Disposition: attachment; filename=".$ff[filename]); Header("Content-Description: PHP Generated Data");
while ($fd=fread( $fp,filesize($paths))) { print($fd); } } fclose($fp); ?> |
대부분의 것은 이미 우리가 구현해본 것들입니다. 안해본 것은
if ($fp = fopen($paths, "r")) { Header("Content-type: file/unknown"); Header("Content-Disposition: attachment; filename=".$ff[filename]); Header("Content-Description: PHP Generated Data");
while ($fd=fread( $fp,filesize($paths]))) { print($fd); } } |
이거군요. 일단 파일을 열어야겠죠. fopen 으로요. 이때 파일을 읽기 모드로 열기 위해 "r" 이라는 걸 넣은겁니다. "w" 로 하면 쓰기 모드로 열기 때문에 이 파일을 지우거나 할 수 있죠. 어쨌건 단지 파일을 읽어오면 되기에 r 로 합니다.
이렇게 열은 파일 정보를 $fp 에 저장합니다. 왜 fp 냐구요? C 언어로 파일 다룰 때 File Pointer 의 약자로 fp 를 많이 쓰는데 그게 습관이 되서리. ^^;
자아. 파일일 열었으니 파일을 다운로드 시킬 정보를 클라이언트, 즉 웹 브라우저에게 보내줘야 합니다.
헤더 정보를 뭐가 한다고 했죠? 예이 Header 함수죠.
Header("Content-type: file/unknown"); Header("Content-Disposition: attachment; filename=".$ff[filename]); Header("Content-Description: PHP Generated Data"); |
이겁니다. 여기서 중요한 건
Header("Content-type: file/unknown"); |
이거죠. 이것의 파일 타입을 gif 라던가 뭐 그런 식으로 했고, 그 타입을 웹 브라우저가 자체적으로 열 수 있는 타입이라면 하드에 저장하려 하지 않고 일단 웹 브라우저가 처리하려 합니다. 골치 아프죠. 그래서 해당 파일이 어떤 타입인지 나도 몰라! 라고 해주기 위해 file/unknown 이라고 한거랍니다.
그 아래 아래는 뭐 그다지 중요하진 않구요. 정말 중요한 건
while ($fd=fread( $fp,filesize($paths]))) { print($fd); } |
이놈이죠. fread 함수로 해당 파일의 내용을 읽어와(가져와서) fd 에 저장하고 화면에 뿌려주는 겁니다. 이걸 파일을 다 읽을 때까지 무한 반복시키는거죠.
어째
while ($view=mysql_fetch_array($result)) { } |
이것과 모양이 비슷하죠? ^^;
자. 이렇게 읽어오고 뿌려주고를 완료했으면 파일 열은 것을 닫아줘야 합니다. 반드시요. 그렇지 않으면? 무슨 일이 일어날지. ^^;
짠. 닫았습니다. :]
으흠. 이제 파일이 있을 경우 파일 명을 출력하고 그걸 클릭하면 다운로드 되게 해보겠습니다.
일단 간단하게 글 본문 읽는 상태(view.php3) 에서 파일 다운로드가 가능하게 해보겠습니다.
if ($view[filesize] > 0) { $view[contents] .= "<p align="right"> <a href="dn.php3?board=$board&no=$view[no]"> $view[filename]</a>"; } |
에게. 간단하죠? 이걸 화면 출력하기 전에 넣어주면 됩니다.
위 루틴은 해당 글의 파일 용량 attribute 를 검색해서 용량이 0 보다 크면 파일이 존재한다고 체크하고, 글 본문 맨 뒤에다가
<p align="right"> <a href="dn.php3?board=$board&no=$view[no]"> $view[filename]</a> |
이걸 붙이는 겁니다. 이렇게 하면 글 본문 맨 아래 우측에 업로드된 파일 이름이 출력되어 있고 이걸 클릭하면 다운로드가 되죠. ^^;
이 기능은 약간의 문제가 있습니다. 큰 용량의 파일을 업로드할 경우 실제로 글이 DB 에 저장된 시각과 글 입력이 완료된 시각이 달라지게 됩니다. 그걸 방지하기 위해 파일을 맨 나중에 업로드 되게 해놨죠. 그런데 역시 문제가 생깁니다. 글이 입력되면 게시물 리스트에 해당 글을 읽을 수 있습니다. 물론 파일도 다운로드 되겠죠. 그런데 만일 파일 용량이 커서 업로드 되고 있는 상태라면 글은 읽어지지만 업로드된 자료는 다운로드가 안될 것입니다. 이거 어떻게 해결하면 좋을까요? ^^ 제 이번 강좌의 마지막 숙제입니다. 잘 풀어보세요. :]
수고들 하셨어요. ^^/ 이제 강좌의 후기인 '년-0' 이 남았군요. ^^; (어감 이상하죠?)
---------------------------- 함께하면 즐거운 사이트들 (-_-; 광고임 -_-;) http://game.creple.com/delthia http://creple.com http://coco.st ----------------------------
- ?! 디망쉬
|