강좌
클라우드/리눅스에 관한 강좌입니다.
프로그램 분류

소. 자료실 기능 및 잘잘한 기능 붙이기 -3 이외 필요하면 추가하죠

작성자 정보

  • 웹관리자 작성
  • 작성일

컨텐츠 정보

본문

[강좌] 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)) {
}

이것과 모양이 비슷하죠? ^^;

자. 이렇게 읽어오고 뿌려주고를 완료했으면 파일 열은 것을 닫아줘야 합니다. 반드시요.
그렇지 않으면? 무슨 일이 일어날지. ^^;

fclose($fp);

짠. 닫았습니다. :]

으흠. 이제 파일이 있을 경우 파일 명을 출력하고 그걸 클릭하면 다운로드 되게 해보겠습니다.

일단 간단하게 글 본문 읽는 상태(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
----------------------------


- ?! 디망쉬

관련자료

댓글 0
등록된 댓글이 없습니다.

공지사항


뉴스광장


  • 현재 회원수 :  60,041 명
  • 현재 강좌수 :  35,855 개
  • 현재 접속자 :  110 명