OSDN Git Service

- update css(for textarea)
[feedblog/feedgenerator.git] / filemanager.rb
1 #!/usr/local/bin/ruby\r
2 # -*- coding: utf-8 -*-\r
3 #\r
4 #= Galleryマネージャ用アプリケーション\r
5 #\r
6 #Autohr::    Kureha Hisame (http://lunardial.sakura.ne.jp/)\r
7 #Version::   1.0.0.0\r
8 #Copyright:: Copyright 2013 Kureha Hisame (http://lunardial.sakura.ne.jp/)\r
9 #License::   GPLv3\r
10 \r
11 require "cgi"\r
12 require "cgi/session"\r
13 require "erb"\r
14 require "rexml/document"\r
15 require "pstore"\r
16 require "fileutils"\r
17 \r
18 require "./common.rb"\r
19 require "./define.rb"\r
20 \r
21 # = WebSecurityExceptionクラス\r
22 #\r
23 # 独自定義の例外クラスです。WebFilerインスタンス内の処理でセキュリティ違反が発生した場合にthrowされます。\r
24 class WebSecurityException < Exception\r
25 end\r
26 \r
27 # = FileExistedExceptionクラス\r
28 #\r
29 # 独自定義の例外クラスです。WebFileインスタンスの処理で、既に存在するファイルに上書きしようとした場合にthrowされます。\r
30 class FileExistedException < Exception\r
31 end\r
32 \r
33 # = FileNotExistedExceptionクラス\r
34 #\r
35 # 独自定義の例外クラスです。WebFileインスタンスの処理で、対象ファイルが存在しない場合にthrowされます。\r
36 class FileNotExistedException < Exception\r
37 end\r
38 \r
39 # = HtmlWriterクラス\r
40\r
41 # テンプレートファイル(*.erb)を読み込み、管理するクラスです\r
42 class HtmlWriter\r
43   # 初期化メソッドです\r
44   #\r
45   # _template_ :: テンプレートファイル(*.erb)のパス\r
46   # _binding_ :: binding変数\r
47   def initialize(template, binding)\r
48     @erb = ERB.new(myopen(template, "r:utf-8") {|f| f.read}, nil, "-")\r
49     @binding = binding\r
50   end\r
51 \r
52   # テンプレートファイルの文字列を返却するメソッドです\r
53   def to_code\r
54     @erb.result(@binding)\r
55   end\r
56 end\r
57 \r
58 # = WebFilerクラス\r
59 #\r
60 # Web上にローカルと同じフォルダ管理機能を利用できるクラスです\r
61 class WebFiler\r
62   # 初期化メソッドです\r
63   #\r
64   # _basepath_ :: クラス内で扱う最上位のフォルダ(root)とする実パス\r
65   def initialize(basepath)\r
66     @basepath = basepath\r
67     @basepath << "/" unless @basepath[-1..-1] == "/"\r
68     @relpath_list = []\r
69     @sort_type = ''\r
70     @sort_reverse = ''\r
71 \r
72     raise "Target dir not found." unless File.directory?(pwd)\r
73   end\r
74 \r
75   # ファイルに使用できない文字列を調べるための正規表現を定義します\r
76   FILEREGEXP = /\A[-_+~^0-9a-zA-Z]+(\.[-_+~^0-9a-zA-Z]+)*\z/\r
77 \r
78     attr_reader :basepath\r
79   attr_accessor :relpath_list, :sort_type, :sort_reverse\r
80 \r
81   # ディレクトリ内部のファイル・フォルダ一覧を取得するメソッド\r
82   def ls\r
83     filelist = Dir::entries(pwd)\r
84     filelist.delete(".")\r
85     filelist.delete("..")\r
86 \r
87     # ファイルリスト一覧のソート処理。\r
88     case @sort_type\r
89     when "ftype"\r
90       if @sort_reverse == "true"\r
91         filelist.sort!{|a, b| \r
92           (File.ftype(pwd + b) <=> File.ftype(pwd + a)).nonzero? || \r
93           (File.basename(pwd + a) <=> File.basename(pwd + b))\r
94         }\r
95       else\r
96         filelist.sort!{|a, b| \r
97           (File.ftype(pwd + a) <=> File.ftype(pwd + b)).nonzero? || \r
98           (File.basename(pwd + a) <=> File.basename(pwd + b))\r
99         }\r
100       end\r
101     when "ctime"\r
102       filelist.sort!{|a, b| File.ctime(pwd + b) <=> File.ctime(pwd + a) }\r
103       # ファイルリストを逆順にするか\r
104       filelist.reverse! if @sort_reverse == "true"\r
105     when "mtime"\r
106       filelist.sort!{|a, b| File.mtime(pwd + b) <=> File.mtime(pwd + a) }\r
107       # ファイルリストを逆順にするか\r
108       filelist.reverse! if @sort_reverse == "true"\r
109     when "size"\r
110       if @sort_reverse == "true"\r
111         filelist.sort!{|a, b| \r
112           (File.ftype(pwd + a) <=> File.ftype(pwd + b)).nonzero? || \r
113           (File.size(pwd + a) <=> File.size(pwd + b)).nonzero? || \r
114           (File.basename(pwd + a) <=> File.basename(pwd + b))\r
115         }\r
116       else\r
117         filelist.sort!{|a, b| \r
118           (File.ftype(pwd + a) <=> File.ftype(pwd + b)).nonzero? || \r
119           (File.size(pwd + b) <=> File.size(pwd + a)).nonzero? || \r
120           (File.basename(pwd + a) <=> File.basename(pwd + b))\r
121         }\r
122       end\r
123     when "name"\r
124       filelist.sort!{|a, b| File.basename(pwd + a) <=> File.basename(pwd + b) }\r
125       # ファイルリストを逆順にするか\r
126       filelist.reverse! if @sort_reverse == "true"\r
127     else\r
128       if @sort_reverse == "true"\r
129         filelist.sort!{|a, b| \r
130           (File.ftype(pwd + b) <=> File.ftype(pwd + a)).nonzero? || \r
131           (File.basename(pwd + a) <=> File.basename(pwd + b))\r
132         }\r
133       else\r
134         filelist.sort!{|a, b| \r
135           (File.ftype(pwd + a) <=> File.ftype(pwd + b)).nonzero? || \r
136           (File.basename(pwd + a) <=> File.basename(pwd + b))\r
137         }\r
138       end\r
139     end\r
140     \r
141     filelist\r
142   end\r
143 \r
144   # ディレクトリ内部のファイル・フォルダ一覧の詳細情報を取得するメソッド\r
145   def lsinfo\r
146     filelist = Dir::entries(pwd)\r
147     filelist.delete(".")\r
148     filelist.delete("..")\r
149 \r
150     fileinfo = {}\r
151     filelist.each { |fname| \r
152       fileinfo[fname] = {}\r
153       fileinfo[fname][:ftype] = File.ftype(pwd + "/" + fname)\r
154       fileinfo[fname][:atime] = File.atime(pwd + "/" + fname)\r
155       fileinfo[fname][:ctime] = File.ctime(pwd + "/" + fname)\r
156       fileinfo[fname][:mtime] = File.mtime(pwd + "/" + fname)\r
157       fileinfo[fname][:size] = File.size(pwd + "/" + fname) / 1000\r
158       \r
159       fileinfo[fname][:sort] = File.mtime(pwd + "/" + fname).to_i\r
160     }\r
161     \r
162     fileinfo\r
163   end\r
164 \r
165   # ファイルタイプを取得するメソッド\r
166   def ftype(fname)\r
167     File.ftype(pwd + File.basename(fname))\r
168   end\r
169 \r
170   # ディレクトリを移動するメソッド\r
171   def cd(pathname)\r
172     if pathname == ".."\r
173       @relpath_list.delete_at(-1) unless @relpath_list.length == 0\r
174     elsif pathname.match(FILEREGEXP)\r
175       if File.directory?(pwd + "/" + File.basename(pathname))\r
176         @relpath_list << File.basename(pathname)\r
177       else\r
178         raise FileExistedException\r
179       end\r
180     else\r
181       raise WebSecurityException\r
182     end\r
183   end\r
184 \r
185   # ディレクトリを絶対指定で移動するメソッド\r
186   def cd_abs(relpath_arr)\r
187     relpath_arr.each { |name|\r
188       unless name.match(FILEREGEXP)\r
189         raise WebSecurityException\r
190       end\r
191     }\r
192     @relpath_list = relpath_arr\r
193   end\r
194 \r
195   # 現在のディレクトリを表示するメソッド\r
196   def pwd\r
197     @basepath + relpath\r
198   end\r
199 \r
200   # 相対パスを算出するメソッド\r
201   def relpath\r
202     if @relpath_list.length == 0\r
203       ""\r
204     else\r
205       @relpath_list.join("/") << "/"\r
206     end\r
207   end\r
208 \r
209   # ファイルをアップロードするメソッド\r
210   def upload(file, fname)\r
211     fname.gsub!(/( | )/, "_")\r
212     \r
213     if File.exist?(pwd + File.basename(fname))\r
214       raise FileExistedException\r
215     elsif File.basename(fname).match(FILEREGEXP)\r
216       open(pwd + File.basename(fname), "w") do |f|\r
217         f.binmode\r
218         f.write file.read\r
219       end\r
220     else\r
221       raise WebSecurityException\r
222     end\r
223   end\r
224   \r
225   # ファイルを移動するメソッド\r
226   def move(from_name, dest_name)\r
227     if File.exist?(pwd + File.basename(dest_name))\r
228       raise FileExistedException\r
229     end\r
230     \r
231     unless File.exist?(pwd + File.basename(from_name))\r
232       raise FileNotExistedException\r
233     end\r
234     \r
235     unless File.basename(from_name).match(FILEREGEXP)\r
236       raise WebSecurityException\r
237     end\r
238     \r
239     unless File.basename(dest_name).match(FILEREGEXP)\r
240       raise WebSecurityException\r
241     end\r
242     \r
243     File.rename(pwd + File.basename(from_name), pwd + File.basename(dest_name))\r
244   end\r
245 \r
246   # ファイルを消去するメソッド\r
247   def delete(fname)\r
248     if File.exist?(pwd + File.basename(fname)) && fname.match(FILEREGEXP)\r
249       File.delete(pwd + File.basename(fname))\r
250     else\r
251       raise WebSecurityException\r
252     end\r
253   end\r
254 \r
255   # ディレクトリを製作するメソッド\r
256   def mkdir(dirname)\r
257     dirname.gsub!(/( | )/, "_")\r
258     if File.exist?(pwd + File.basename(dirname))\r
259       raise FileExistedException\r
260     elsif dirname.match(FILEREGEXP)\r
261       Dir.mkdir(pwd + File.basename(dirname))\r
262     else\r
263       raise WebSecurityException\r
264     end\r
265   end\r
266 \r
267   # 内部が空のディレクトリを消去するメソッド\r
268   def rmdir(dirname)\r
269     if File.exist?(pwd + File.basename(dirname)) && dirname.match(FILEREGEXP)\r
270       Dir.rmdir(pwd + File.basename(dirname))\r
271     else\r
272       raise WebSecurityException\r
273     end\r
274   end\r
275 \r
276 end\r
277 \r
278 # = Controllerクラス\r
279 #\r
280 # コントローラ部分に相当する処理を受け持つクラスです\r
281 class Controller\r
282   def self.update_session(session, filer)\r
283     session["filelist"] = filer.ls\r
284     session["fileinfo"] = filer.lsinfo\r
285     session["sort_type"] = filer.sort_type\r
286     session["sort_reverse"] = filer.sort_reverse\r
287     session["pwd"] = filer.pwd\r
288     session["relpath_list"] = filer.relpath_list.join("/")\r
289   end\r
290 \r
291   def Controller.MultiForm(cgi, session, params, db)\r
292     db.transaction do\r
293       # 共通初期化処理\r
294       filer = WebFiler.new(IMGPATH)\r
295       begin\r
296         filer.relpath_list = params["relpath_list"].split("/")\r
297         filer.sort_type = params["sort_type"]\r
298         filer.sort_reverse = params["sort_reverse"]\r
299       rescue\r
300       end\r
301       filer.sort_type = "ftype" if filer.sort_type == nil or filer.sort_type.empty?\r
302       Controller.update_session(session, filer);\r
303       \r
304       case params["action"]\r
305         # アップロード時\r
306       when "upload"\r
307         if (cgi["updata"].size == 0)\r
308           session["error"] = "アップロードするファイルが選択されていません!"\r
309         elsif (cgi["updata"].size <= UPLOADLIMIT)\r
310           begin\r
311             # ファイル名称指定判定\r
312             upload_filename = nil\r
313             unless params["file_rename"].empty?\r
314               upload_filename = File.basename(params["file_rename"])\r
315               # 空白は自動変換する\r
316               upload_filename.gsub!(/( | )/, "_")\r
317             else\r
318               upload_filename = cgi["updata"].original_filename\r
319             end\r
320             \r
321             filer.upload(cgi["updata"], upload_filename)\r
322             # 独自改造 開始\r
323             if params["thumbs"] == "true" and File.extname(upload_filename) =~ /\.(jpg|JPG|png|PNG)\Z/\r
324               begin\r
325                 require('./resize.rb')\r
326                 if filer.relpath_list.size == 0\r
327                   ResizeManager.create_thumbs(IMGPATH + upload_filename)\r
328                 else\r
329                   ResizeManager.create_thumbs(IMGPATH + filer.relpath_list.join("/") + "/" + upload_filename)\r
330                 end\r
331               rescue LoadError\r
332                 # With no action\r
333               rescue => evar\r
334                 # With no action\r
335                 session["error"] = "サムネイル作成に失敗しました。<br>"\r
336                 session["error"] << evar.to_s\r
337               end\r
338             end\r
339             # 独自改造 終了\r
340           rescue FileExistedException\r
341             session["error"] = "既に同名のファイルが存在します!"\r
342           rescue WebSecurityException\r
343             session["error"] = "ファイル名に使用できない文字列が含まれています!"\r
344           end\r
345         else\r
346           session["error"] = "ファイルの容量が大きすぎます!"\r
347         end\r
348         \r
349         # ファイル一覧を更新\r
350         Controller.update_session(session, filer);\r
351 \r
352         session["info"] = "正常にファイル(#{upload_filename})のアップロードが完了しました。" if session["error"] == ""\r
353         \r
354         # 移動時\r
355       when "move"\r
356         session["selectlist"] = []\r
357         count = 0\r
358         from_name = nil;\r
359         dest_name = params["destname"]\r
360         session["filelist"].each do |file|\r
361           if params["filename_" + file] == "true"\r
362             count = count + 1\r
363             from_name = file\r
364           end\r
365         end\r
366         \r
367         if count == 0\r
368           mes = "対象が選択されていません。"\r
369           session["error"] = mes\r
370         elsif count > 1\r
371           mes = "一つ以上の対象がチェックされています。<br>"\r
372           mes << "リネーム処理は一度に一つの対象にしか行えません。"\r
373           session["error"] = mes\r
374         elsif dest_name.empty?\r
375           mes = "リネーム名称が入力されていません。"\r
376           session["error"] = mes\r
377         else\r
378           begin\r
379             filer.move(from_name, dest_name)\r
380           rescue FileExistedException\r
381             session["error"] = "既に同名のファイルが存在します!"\r
382           rescue FileNotExistedException\r
383             session["error"] = "選択したファイルは既に削除されました。"\r
384           rescue WebSecurityException\r
385             session["error"] = "リネーム名称に使用できない文字列が含まれています!"\r
386           end\r
387         end\r
388         \r
389         # ファイル一覧を更新\r
390         Controller.update_session(session, filer);\r
391 \r
392         session["info"] = "正常にファイルのリネームが完了しました。" if session["error"] == ""\r
393 \r
394         # 削除時\r
395       when "delete"\r
396         session["dellist"] = []\r
397         count = 0\r
398         session["filelist"].each do |file|\r
399           if params["filename_" + file] == "true" && filer.ftype(file) == "file"\r
400             filer.delete(file) \r
401             count = count + 1\r
402           elsif params["filename_" + file] == "true" && filer.ftype(file) == "directory"\r
403             begin\r
404               filer.rmdir(file)\r
405               count = count + 1\r
406             rescue\r
407               mes = "対象のフォルダは空ではありません!<br>"\r
408               mes << "フォルダを削除する場合は、事前にフォルダ内部の全てのファイルを削除してください。"\r
409               session["error"] = mes\r
410             end\r
411           end\r
412         end\r
413         \r
414         # ファイル一覧を更新\r
415         Controller.update_session(session, filer);\r
416 \r
417         session["info"] = "正常にファイルの削除が完了しました。" if session["error"] == "" && count != 0\r
418 \r
419         # ディレクトリ製作時\r
420       when "mkdir"\r
421         begin\r
422           filer.mkdir(params["dirname"])\r
423         rescue FileExistedException\r
424           session["error"] = "既に同名のフォルダが存在します!"\r
425         rescue WebSecurityException\r
426           session["error"] = "フォルダ名に使用できない文字列が含まれています!"\r
427         end\r
428         \r
429         # ファイル一覧を更新\r
430         Controller.update_session(session, filer);\r
431 \r
432         session["info"] = "正常にフォルダの作成が完了しました。" if session["error"] == ""\r
433 \r
434         # ディレクトリ移動時\r
435       when "cd"\r
436         begin\r
437           filer.cd(params["arg"])\r
438         rescue\r
439           session["error"] = "移動先のフォルダが見つかりません!"\r
440         end\r
441         \r
442         # ファイル一覧を更新\r
443         Controller.update_session(session, filer);\r
444 \r
445         # 絶対位置でのディレクトリ移動時\r
446       when "cd_abs"\r
447         if params["arg"].to_i >= 0\r
448           begin\r
449             movepath = []\r
450             params["arg"].to_i.times { |i|\r
451               movepath << params["relpath_list"].split("/")[i]\r
452             }\r
453             filer.cd_abs(movepath)\r
454           rescue\r
455             session["error"] = "移動先のフォルダが見つかりません!"\r
456           end\r
457         else\r
458           session["error"] = "移動先のフォルダが見つかりません!"\r
459         end\r
460         \r
461         # ファイル一覧を更新\r
462         Controller.update_session(session, filer);\r
463 \r
464         # 表示更新時\r
465       when "refresh"\r
466         \r
467         # 初期表示\r
468       else\r
469         \r
470       end\r
471     end\r
472   end\r
473 end\r
474 \r
475 def main\r
476   # SESSION変数、パラメータなどを取得します\r
477   cgi = CGI.new\r
478   session = CGI::Session.new(cgi)\r
479   params = Hash[*cgi.params.to_a.map{|k, v| [k, v[0].to_s]}.flatten]\r
480   params.each { |k, v| params[k] = cgi[k].read if cgi[k].respond_to?(:read) && k != "updata"}\r
481   \r
482   # ロガーを作成する\r
483   logger = WebLogger.get_logger(cgi.script_name, LOG_DIR, LOG_RELEASE_MODE)\r
484   \r
485   # コントローラー部分\r
486   # セッション管理\r
487   session["info"] = ""\r
488   session["error"] = ""\r
489   if session["login"] != "true"\r
490     # ログイン情報を確認\r
491     LOGININFO.each {|h|\r
492       if (cgi["loginid"] == h[:id] && cgi["password"] == h[:password])\r
493         session["login"] = "true"\r
494         session["name"] = h[:name]\r
495         \r
496         # ログを記録\r
497         logger.info "ログインユーザ:#{h[:id]}, IPアドレス:#{cgi.remote_addr}"\r
498         \r
499         # ワークフォルダの中をクリーンアップします\r
500         filelist = Dir::entries("./work")\r
501         # 削除条件 : 最終変更日時から1日(60*60*24sec)かつ、ファイルタイプがファイルの場合\r
502         filelist.each do |file|\r
503           File.delete("./work/#{file}") if Time.now - File.ctime("./work/#{file}") > 86400 && File.ftype("./work/#{file}") == "file"\r
504         end\r
505         break\r
506       end\r
507     }\r
508     \r
509     # ログイン失敗ユーザを記録\r
510     if (session["login"] != "true" and (cgi["loginid"] != "" or cgi["password"] != ""))\r
511       session["error"] = "ログインに失敗しました。ユーザIDまたはパスワードを確認して下さい。"\r
512       logger.info "次のアクセスがログインに失敗しました。ログインID:#{cgi["loginid"]}, IPアドレス:#{cgi.remote_addr}"\r
513     end\r
514   end\r
515 \r
516   # ログアウト処理\r
517   if params["mode"] == "logout"\r
518     session["login"] = nil\r
519     session["logini"] = nil\r
520     session["password"] = nil\r
521     session.delete\r
522   end\r
523 \r
524   \r
525   begin\r
526     # セッションが有効な場合のみコントローラを実行します\r
527     if session["login"] == "true"\r
528       db = PStore.new("./work/#{session.session_id}_file.dat")\r
529       # コントローラ部分\r
530       Controller.MultiForm(cgi, session, params, db)\r
531     end\r
532 \r
533     # ビュー部分\r
534     if session["login"] != "true"\r
535       # セッションが存在しない場合は強制的にエラーページに遷移\r
536       htmlwriter = HtmlWriter.new("./erbtemp/login.html.erb", binding)\r
537     else\r
538       htmlwriter = HtmlWriter.new("./erbtemp/filemanager.html.erb", binding)\r
539     end\r
540 \r
541     # 実際にHTMLを出力します\r
542     cgi.out{htmlwriter.to_code}\r
543   rescue => exception\r
544     # ログに記録を実施します\r
545     logger.error(exception.to_s)\r
546     logger.error(exception.backtrace.join "\n")\r
547     \r
548     # エラーが発生した場合、それを画面に表示します\r
549     htmlwriter = HtmlWriter.new("./erbtemp/exception.html.erb", binding)\r
550     cgi.out{ htmlwriter.to_code }\r
551   end\r
552 end\r
553 \r
554 begin\r
555   main\r
556 rescue => evar\r
557   # エラーが発生した場合、それを画面に表示します\r
558   detail = ("%s: %s (%s)\n" %\r
559             [evar.backtrace[0], evar.message, evar.send('class')]) +\r
560               evar.backtrace[1..-1].join("\n")\r
561             puts "content-type: text/html\n\n<plaintext>\n" + detail\r
562 end\r