OSDN Git Service

update params's save method.
[feedblog/feedgenerator.git] / filemanager.rb
1 #!/usr/local/bin/ruby
2 # -*- coding: utf-8 -*-
3 #
4 #= FeedGeneratorから起動するファイルマネージャWEBアプリケーション
5 #
6 #Autohr::    Kureha Hisame (http://lunardial.sakura.ne.jp/) & Yui Naruse (http://airemix.com/)
7 #Version::   2.0.0.0
8 #Copyright:: Copyright 2009 FeedBlog Project (http://sourceforge.jp/projects/feedblog/)
9 #License::   GPLv3
10
11 require "cgi"
12 require "cgi/session"
13 require "erb"
14 require "rexml/document"
15 require "pstore"
16 require "fileutils"
17
18 # インターフェースのテーブルの幅
19 TABLEWIDTH = 800
20 # 画像フォルダの場所を定義
21 IMGPATH = "./../lunardial/xml/img/"
22
23 # バージョン情報を示す文字列です
24 APPVERSION = "- FileManager for Ruby version 2.0.0.0 -<br>Copyright(c) 2009 Kureha.H (<a href=\"http://lunardial.sakura.ne.jp/\" target=\"_blank\">http://lunardial.sakura.ne.jp/</a>) & Yui Naruse (<a href=\"http://airemix.com/\" target=\"_blank\">http://airemix.com/</a>)"
25 # タイトル領域に表示される文字列です
26 APPTITLE = "FileManager for Ruby version 2.0.0.0"
27
28 # アップロード可能な最大ファイルサイズ
29 UPLOADLIMIT = 2 * 1024 * 1024
30
31 # = Objectクラス
32 #
33 # 基本クラスのオーバーライドを行います
34 class Object
35   # myopenメソッド
36   #
37   # ruby-1.9.x以降ではファイルを開いた際、エンコードの指定を行わないとエラーの原因になります。
38   # ただしruby-1.8.6以前はエンコードの指定に対応していないため、独自メソッドを定義してファイルの入出力を行います。
39   #
40   # _arg[0]_ :: 入出力を行うファイルのパス
41   # _arg[1]_ :: モードの指定。例 : w:utf-8(書き込みモード・UTF-8エンコードでファイルを開く)
42   def myopen(*arg)
43     mode = arg[1]
44     arg[1] = mode[/[^:]+/] if mode && RUBY_VERSION < "1.8.7" && mode.include?(':')
45     open(*arg) do |f|
46       f.flock(/\A\w+r/ =~ mode ? File::LOCK_SH : File::LOCK_EX)
47       return yield(f)
48     end
49   end
50 end
51
52 class NilClass
53   def blank?
54     nil?
55   end
56 end
57
58 class Array
59   def blank?
60     empty?
61   end
62 end
63
64 class String
65   def blank?
66     empty?
67   end
68 end
69
70 # = WebSecurityExceptionクラス
71 #
72 # 独自定義の例外クラスです。WebFilerインスタンス内の処理でセキュリティ違反が発生した場合にthrowされます。
73 class WebSecurityException < Exception
74 end
75
76 # = FileExistedExceptionクラス
77 #
78 # 独自定義の例外クラスです。WebFileインスタンスの処理で、既に存在するファイルに上書きしようとした場合にthrowされます。
79 class FileExistedException < Exception
80 end
81
82 # = HtmlWriterクラス
83
84 # テンプレートファイル(*.erb)を読み込み、管理するクラスです
85 class HtmlWriter
86   # 初期化メソッドです
87   #
88   # _template_ :: テンプレートファイル(*.erb)のパス
89   # _binding_ :: binding変数
90   def initialize(template, binding)
91     @erb = ERB.new(myopen(template, "r:utf-8") {|f| f.read}, nil, "-")
92     @binding = binding
93   end
94   
95   # テンプレートファイルの文字列を返却するメソッドです
96   def to_code
97     @erb.result(@binding)
98   end
99 end
100
101 # = WebFilerクラス
102 #
103 # Web上にローカルと同じフォルダ管理機能を利用できるクラスです
104 class WebFiler
105   # 初期化メソッドです
106   #
107   # _basepath_ :: クラス内で扱う最上位のフォルダ(root)とする実パス
108   def initialize(basepath)
109     @basepath = basepath
110     @basepath << "/" unless @basepath[-1..-1] == "/"
111     @relpath_list = []
112     
113     raise "Target dir not found." unless File.directory?(pwd)
114   end
115   
116   # ファイルに使用できない文字列を調べるための正規表現を定義します
117   FILEREGEXP = /\A[-_+~^0-9a-zA-Z]+(\.[-_+~^0-9a-zA-Z]+)*\z/
118   
119   attr_reader :basepath
120   attr_accessor :relpath_list
121   
122   # ディレクトリ内部のファイル・フォルダ一覧を取得するメソッド
123   def ls
124     filelist = Dir::entries(pwd)
125     filelist.delete(".")
126     filelist.delete("..")
127     
128     filelist.sort!
129   end
130   
131   # ディレクトリ内部のファイル・フォルダ一覧の詳細情報を取得するメソッド
132   def lsinfo
133     filelist = Dir::entries(pwd)
134     filelist.delete(".")
135     filelist.delete("..")
136     
137     fileinfo = {}
138     filelist.each { |fname| 
139       fileinfo[fname] = {}
140       fileinfo[fname][:ftype] = File.ftype(pwd + "/" + fname)
141       fileinfo[fname][:atime] = File.atime(pwd + "/" + fname)
142       fileinfo[fname][:ctime] = File.ctime(pwd + "/" + fname)
143       fileinfo[fname][:mtime] = File.mtime(pwd + "/" + fname)
144       fileinfo[fname][:size] = File.size(pwd + "/" + fname) / 1000
145     }
146     
147     fileinfo
148   end
149   
150   # ファイルタイプを取得するメソッド
151   def ftype(fname)
152     File.ftype(pwd + File.basename(fname))
153   end
154   
155   # ディレクトリを移動するメソッド
156   def cd(pathname)
157     if pathname == ".."
158       @relpath_list.delete_at(-1) unless @relpath_list.length == 0
159     elsif pathname.match(FILEREGEXP)
160       if File.directory?(pwd + "/" + File.basename(pathname))
161         @relpath_list << File.basename(pathname)
162       else
163         raise FileExistedException
164       end
165     else
166       raise WebSecurityException
167     end
168   end
169   
170   # ディレクトリを絶対指定で移動するメソッド
171   def cd_abs(relpath_arr)
172     relpath_arr.each { |name|
173       unless name.match(FILEREGEXP)
174         raise WebSecurityException
175       end
176     }
177     @relpath_list = relpath_arr
178   end
179   
180   # 現在のディレクトリを表示するメソッド
181   def pwd
182     @basepath + relpath
183   end
184   
185   # 相対パスを算出するメソッド
186   def relpath
187     if @relpath_list.length == 0
188       ""
189     else
190       @relpath_list.join("/") << "/"
191     end
192   end
193   
194   # ファイルをアップロードするメソッド
195   def upload(file, fname)
196     fname.gsub!(/( | )/, "_")
197     if File.exist?(pwd + File.basename(fname))
198       raise FileExistedException
199     elsif File.basename(fname).match(FILEREGEXP)
200       open(pwd + File.basename(fname), "w") do |f|
201         f.binmode
202         f.write file.read
203       end
204     else
205       raise WebSecurityException
206     end
207   end
208   
209   # ファイルを消去するメソッド
210   def delete(fname)
211     if File.exist?(pwd + File.basename(fname)) && fname.match(FILEREGEXP)
212       File.delete(pwd + File.basename(fname))
213     else
214       raise WebSecurityException
215     end
216   end
217   
218   # ディレクトリを製作するメソッド
219   def mkdir(dirname)
220     dirname.gsub!(/( | )/, "_")
221     if File.exist?(pwd + File.basename(dirname))
222       raise FileExistedException
223     elsif dirname.match(FILEREGEXP)
224       Dir.mkdir(pwd + File.basename(dirname))
225     else
226       raise WebSecurityException
227     end
228   end
229   
230   # 内部が空のディレクトリを消去するメソッド
231   def rmdir(dirname)
232     if File.exist?(pwd + File.basename(dirname)) && dirname.match(FILEREGEXP)
233       Dir.rmdir(pwd + File.basename(dirname))
234     else
235       raise WebSecurityException
236     end
237   end
238   
239 end
240
241 # = Controllerクラス
242 #
243 # コントローラ部分に相当する処理を受け持つクラスです
244 class Controller
245   def Controller.MultiForm(cgi, session, params, db)
246     db.transaction do
247       case params["action"]
248         # アップロード時
249         when "upload"
250         filer = WebFiler.new(IMGPATH)
251         filer.relpath_list = session["relpath_list"].split("/")
252         
253         if (cgi["updata"].size <= UPLOADLIMIT)
254           begin
255             filer.upload(cgi["updata"], cgi["updata"].original_filename)
256           rescue FileExistedException
257             db["error"] = "既に同名のファイルが存在します!"
258           rescue WebSecurityException
259             db["error"] = "ファイル名に使用できない文字列が含まれています!"
260           end
261         else
262           db["error"] = "ファイルの容量が大きすぎます!"
263         end
264         
265         db["filelist"] = filer.ls.reverse
266         db["fileinfo"] = filer.lsinfo
267         session["relpath_list"] = filer.relpath_list.join("/")
268         
269         db["info"] = "正常にアップロードが完了しました。" if db["error"] == ""
270         
271         # 削除時
272         when "delete"
273         filer = WebFiler.new(IMGPATH)
274         filer.relpath_list = params["relpath_list"].split("/")
275         
276         db["dellist"] = []
277         count = 0
278         db["filelist"].each do |file|
279           if params["filename_" + file] == "delete" && filer.ftype(file) == "file"
280             filer.delete(file) 
281             count = count + 1
282           elsif params["filename_" + file] == "delete" && filer.ftype(file) == "directory"
283             begin
284               filer.rmdir(file)
285             rescue
286               mes = "対象のディレクトリは空ではありません!<br>"
287               mes << "ディレクトリを削除する場合は、事前にディレクトリ内部の全てのファイルを削除してください。"
288               db["error"] = mes
289             end
290           end
291         end
292         
293         db["filelist"] = filer.ls.reverse
294         db["fileinfo"] = filer.lsinfo
295         session["relpath_list"] = filer.relpath_list.join("/")
296         
297         db["info"] = "正常にファイルの削除が完了しました。" if db["error"] == "" && count != 0
298         
299         # ディレクトリ製作時
300         when "mkdir"
301         filer = WebFiler.new(IMGPATH)
302         filer.relpath_list = params["relpath_list"].split("/")
303         
304         begin
305           filer.mkdir(params["dirname"])
306         rescue FileExistedException
307           db["error"] = "既に同名のディレクトリが存在します!"
308         rescue WebSecurityException
309           db["error"] = "ディレクトリ名に使用できない文字列が含まれています!"
310         end
311         
312         db["filelist"] = filer.ls.reverse
313         db["fileinfo"] = filer.lsinfo
314         session["relpath_list"] = filer.relpath_list.join("/")
315         
316         db["info"] = "正常にディレクトリの作成が完了しました。" if db["error"] == ""
317         
318         # ディレクトリ移動時
319         when "cd"
320         filer = WebFiler.new(IMGPATH)
321         filer.relpath_list = params["relpath_list"].split("/")
322         
323         begin
324           filer.cd(params["arg"])
325         rescue
326           db["error"] = "移動先のディレクトリが見つかりません!"
327         end
328         
329         db["filelist"] = filer.ls.reverse
330         db["fileinfo"] = filer.lsinfo
331         db["pwd"] = filer.pwd
332         session["relpath_list"] = filer.relpath_list.join("/")
333         
334         # 絶対位置でのディレクトリ移動時
335         when "cd_abs"
336         filer = WebFiler.new(IMGPATH)
337         filer.relpath_list = params["relpath_list"].split("/")
338         
339         if params["arg"].to_i >= 0
340           begin
341             movepath = []
342             params["arg"].to_i.times { |i|
343               movepath << params["relpath_list"].split("/")[i]
344             }
345             filer.cd_abs(movepath)
346           rescue
347             db["error"] = "移動先のディレクトリが見つかりません!"
348           end
349         else
350           db["error"] = "移動先のディレクトリが見つかりません!"
351         end
352         
353         db["filelist"] = filer.ls.reverse
354         db["fileinfo"] = filer.lsinfo
355         db["pwd"] = filer.pwd
356         session["relpath_list"] = filer.relpath_list.join("/")
357         
358         # 表示更新時
359         when "refresh"
360         filer = WebFiler.new(IMGPATH)
361         filer.relpath_list = params["relpath_list"].split("/")
362         
363         db["filelist"] = filer.ls.reverse
364         db["fileinfo"] = filer.lsinfo
365         db["pwd"] = filer.pwd
366         session["relpath_list"] = filer.relpath_list.join("/")
367         
368         # 初期表示
369       else
370         filer = WebFiler.new(IMGPATH)
371         
372         db["filelist"] = filer.ls.reverse
373         db["fileinfo"] = filer.lsinfo
374         db["pwd"] = filer.pwd
375         session["relpath_list"] = filer.relpath_list.join("/")
376       end
377     end
378   end
379 end
380
381 def main
382   # SESSION変数、パラメータなどを取得します
383   cgi = CGI.new
384   session = CGI::Session.new(cgi)
385   params = Hash[*cgi.params.to_a.map{|k, v| [k, v[0].to_s]}.flatten]
386   params.each { |k, v| params[k] = cgi[k].read if cgi[k].respond_to?(:read) && k != "updata"}
387   
388   # ログアウト処理
389   if params["mode"] == "logout"
390     session["login"] = nil
391     session["logini"] = nil
392     session["password"] = nil
393     session.delete
394   end
395   
396   # メインコントローラー
397   # セッションが有効な場合のも実行します
398   if session["login"] == "true"
399     db = PStore.new("./work/#{session.session_id}_file.dat")
400     db.transaction do
401       db["info"] = ""
402       db["error"] = ""
403     end
404     
405     # コントローラ部分
406     Controller.MultiForm(cgi, session, params, db)
407   end
408   
409   # ビュー部分
410   if session["login"] != "true"
411     # if session["login"] == "true"
412     # セッションが存在しない場合は強制的にエラーページに遷移
413     htmlwriter = HtmlWriter.new("./erbtemp/fileindex.html.erb", binding)
414   else
415     htmlwriter = HtmlWriter.new("./erbtemp/filemanager.html.erb", binding)
416   end
417   
418   # 実際にHTMLを出力します
419   begin
420     cgi.out{htmlwriter.to_code}
421   rescue => exception
422     # エラーが発生した場合、それを画面に表示します
423     htmlwriter = HtmlWriter.new("./erbtemp/exception.html.erb", binding)
424     cgi.out{ htmlwriter.to_code }
425   end
426 end
427
428 begin
429   main
430 rescue => evar
431   # エラーが発生した場合、それを画面に表示します
432   detail = ("%s: %s (%s)\n" %
433   [evar.backtrace[0], evar.message, evar.send('class')]) +
434   evar.backtrace[1..-1].join("\n")
435   puts "content-type: text/html\n\n<plaintext>\n" + detail
436 end