jphastings-Trash 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/trash.rb +131 -0
  2. metadata +102 -0
data/trash.rb ADDED
@@ -0,0 +1,131 @@
1
+ require "rubygems"
2
+ require "fileutils"
3
+ require "sys/uname"
4
+ require "sys/admin"
5
+ require "sys/filesystem"
6
+ require "time"
7
+ require "iconv"
8
+
9
+ # A class representing the database file used to store information about
10
+ # items stored in a windows recycle bin.
11
+ #
12
+ # Details as to the structure can be found here: http://www.cybersecurityinstitute.biz/INFO2.htm
13
+ class WindowsRecycleDB
14
+
15
+ # Open the INFO file and test it to make sure its valid
16
+ def initialize(filename)
17
+ @fh = open(filename,"r+b")
18
+ ## Check this is a valid INFO2 file
19
+ # Read the first 16 bytes
20
+ if @fh.readpartial(20).unpack("VVVvvV") != [5,0,0,800,0,0] # NB. the fourth element is the size of each record
21
+ # Perhaps there is a better error to raise
22
+ raise RuntimeError.new, "The file is not in the expected format"
23
+ end
24
+ end
25
+
26
+ # Lists all the files stored in this INFO's recycle bin
27
+ def records
28
+ # Get to the end of the header
29
+ @fh.seek(20)
30
+ records = []
31
+ while not @fh.eof?
32
+ record = @fh.readpartial(800).unpack("Z260VVQVa520")
33
+ break if @fh.eof?
34
+ records.push({
35
+ :record_number => record[1],
36
+ :filename_ascii => record[0],
37
+ :filename => Iconv.new("UTF-8","UTF-16LE").iconv(record[5]).strip,
38
+ :size => record[4],
39
+ :drive_letter => (record[2]+65).chr,
40
+ :delete_time => Time.at((record[3]/(10**7))-11644473600),
41
+ })
42
+ end
43
+ records
44
+ end
45
+
46
+ # Adds a new record to the INFO database, and returns where the file should be moved to to comply with
47
+ # the newly added data.
48
+ def add(filename)
49
+ raise StandardError, "That filename is invalid, its too long" if filename.length > 255
50
+ # Get the next record number
51
+ p n = getNextRecordNumber
52
+
53
+ filename = File.expand_path(filename)
54
+ filename.gsub!(/^([a-z])/){$1.upcase}
55
+
56
+ utf8 = filename
57
+ # Are there any UTF8 characters to deal with?
58
+ if not filename =~ /^[\x21-\x7E]+$/i
59
+ # Use File::SEPARATOR
60
+ filename = filename[0,3]+(filename[3..-1].split("\\").collect { |chunk| ((chunk =~ /^[\x21-\x7E]+$/i) ? chunk : chunk.gsub(/([^a-z0-9_])/i,"")[0..5].upcase+"~1"+File.extname(chunk))}.join("\\"))
61
+ end
62
+
63
+ test = open("temp.txt","w")
64
+ # Go to the end of the file, where the next record needs to be written
65
+ @fh.sysseek(0, IO::SEEK_END)
66
+ @fh.write filename.ljust(280,"\000")
67
+ @fh.write [n].pack("V")
68
+ @fh.write [filename.match(/^([A-Z]):/i)[1].upcase[0] - 65].pack("V")
69
+ @fh.write [((Time.now.to_f+11644473600)*(10**7)).to_i].pack("Q")
70
+ @fh.write [(open(utf8).read.length / Sys::Filesystem.stat(filename[0,3]).block_size).ceil].pack("V")
71
+ @fh.write Iconv.new("UTF-16LE","UTF-8").iconv(utf8).ljust(520,"\000")
72
+ @fh.write "\x0D\x0A"
73
+ "D#{filename[0..0].downcase}#{n+1}"+File.extname(utf8)
74
+ end
75
+
76
+ private
77
+ def getNextRecordNumber
78
+ begin
79
+ @fh.sysseek(-540, IO::SEEK_END)
80
+ @fh.readpartial(4).unpack("V")[0] + 1
81
+ rescue
82
+ 0
83
+ end
84
+ end
85
+ end
86
+
87
+ class File
88
+ # Moves the file whose filename is given to the Trash, Recycle Bin or equivalent of the OS being used.
89
+ #
90
+ # Will return a NotImplementtedError if your OS is not implemented.
91
+ def self.trash(filename)
92
+ filename = self.expand_path(filename)
93
+
94
+ # Different Operating systems
95
+ case Sys::Uname.sysname
96
+ when "Darwin"
97
+ if filename =~ /^\/Volumes\/(.+?)\//
98
+ # External Volume, send to /Volumes/-volume name-/.Trashes/501/
99
+ FileUtils.mv(filename,"/Volumes/#{$1}/.Trashes/501/")
100
+ else
101
+ # Main drive, move to ~/.Trash/
102
+ self.move(filename,self.expand_path("~/.Trash/"))
103
+ end
104
+ when /^Microsoft Windows/
105
+ raise NotImplementedError, "There are some issues with Windows at the moment, sorry"
106
+ break
107
+ drive = filename.match(/^([A-Z]):/)[1]
108
+ case Sys::Filesystem.stat("#{drive}:\\").base_type
109
+ when "FAT32"
110
+ bindir = drive+":\\Recycled\\"
111
+ when "NTFS"
112
+ bindir = drive+":\\RECYCLER\\"+Sys::Admin.get_user(Sys::Admin.get_login).sid+"\\"
113
+ else
114
+ raise NotImplememntedError, "I can't tell what filesystem this drive is using, I'm not going to presume where your Recycled/Recycler folder is"
115
+ break
116
+ end
117
+
118
+ begin
119
+ info = WindowsRecycleDB.new(self.expand_path(bindir+"INFO2"))
120
+ moveto = info.add(filename)
121
+ # For some reason this move line is failing, no idea why
122
+ # If I copy the command and the strings its using to a new file it works...
123
+ FileUtils.mv(filename.gsub("/","\\"),self.expand_path(bindir+moveto).gsub("/","\\"))
124
+ rescue
125
+ raise StandardError, "Couldn't update the Recycle Bin, no action taken"
126
+ end
127
+ else
128
+ raise NotImplementedError, "Sorry, Trash is not yet supported on your operating system (#{Sys::Uname.sysname})"
129
+ end
130
+ end
131
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jphastings-Trash
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - JP Hastings-Spital
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-21 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: fileutils
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: sys-uname
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: sys-admin
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: sys-filesystem
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ - !ruby/object:Gem::Dependency
56
+ name: iconv
57
+ type: :runtime
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ description:
66
+ email: trash@projects.kedakai.co.uk
67
+ executables: []
68
+
69
+ extensions: []
70
+
71
+ extra_rdoc_files: []
72
+
73
+ files:
74
+ - trash.rb
75
+ has_rdoc: true
76
+ homepage: http://projects.kedakai.co.uk/trash
77
+ post_install_message:
78
+ rdoc_options: []
79
+
80
+ require_paths:
81
+ - .
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: "0"
87
+ version:
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: "0"
93
+ version:
94
+ requirements:
95
+ - The iconv, sys-admin and sys-filesystem dependencies are only for Windows systems
96
+ rubyforge_project:
97
+ rubygems_version: 1.2.0
98
+ signing_key:
99
+ specification_version: 2
100
+ summary: Implements File.trash to move a file to the Recycle Bin, Trash or OS equivalent
101
+ test_files: []
102
+