Module: Omnizip::Platform::NtfsStreams

Defined in:
lib/omnizip/platform/ntfs_streams.rb

Overview

NTFS Alternate Data Streams handler Windows-only feature for managing file alternate streams

Class Method Summary collapse

Class Method Details

.archive_streams(file_path) ⇒ Hash<String, String>

Archive streams to a hash

Parameters:

  • file_path (String)

    Path to file

Returns:

  • (Hash<String, String>)

    Stream name => data



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/omnizip/platform/ntfs_streams.rb', line 165

def self.archive_streams(file_path)
  return {} unless available?

  streams_data = {}
  streams = list_streams(file_path)

  streams.each do |stream|
    next if stream == "$DATA"

    data = read_stream(file_path, stream)
    streams_data[stream] = data if data
  end

  streams_data
end

.available?Boolean

Check if NTFS streams are available

Returns:

  • (Boolean)

    true if available



13
14
15
# File 'lib/omnizip/platform/ntfs_streams.rb', line 13

def self.available?
  Platform.supports_ntfs_streams?
end

.copy_streams(source_path, dest_path) ⇒ Integer

Copy all alternate streams from source to destination

Parameters:

  • source_path (String)

    Source file

  • dest_path (String)

    Destination file

Returns:

  • (Integer)

    Number of streams copied



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/omnizip/platform/ntfs_streams.rb', line 113

def self.copy_streams(source_path, dest_path)
  return 0 unless available?
  return 0 unless File.exist?(source_path)
  return 0 unless File.exist?(dest_path)

  copied = 0
  streams = list_streams(source_path)

  streams.each do |stream|
    next if stream == "$DATA" # Skip main data stream

    data = read_stream(source_path, stream)
    copied += 1 if data && write_stream(dest_path, stream, data)
  end

  copied
end

.delete_stream(file_path, stream_name) ⇒ Boolean

Delete alternate data stream

Parameters:

  • file_path (String)

    Path to file

  • stream_name (String)

    Stream name

Returns:

  • (Boolean)

    true if successful



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/omnizip/platform/ntfs_streams.rb', line 92

def self.delete_stream(file_path, stream_name)
  return false unless available?
  return false unless File.exist?(file_path)

  begin
    # Use PowerShell to remove stream
    cmd = "powershell -Command \"Remove-Item -Path '#{file_path}' " \
          "-Stream '#{stream_name}'\""
    system(cmd)
    $CHILD_STATUS.success?
  rescue StandardError => e
    warn "Failed to delete NTFS stream: #{e.message}" if ENV["DEBUG"]
    false
  end
end

.has_streams?(file_path) ⇒ Boolean

Check if file has any alternate streams

Parameters:

  • file_path (String)

    Path to file

Returns:

  • (Boolean)

    true if has streams



154
155
156
157
158
159
# File 'lib/omnizip/platform/ntfs_streams.rb', line 154

def self.has_streams?(file_path)
  return false unless available?

  streams = list_streams(file_path)
  streams.any? { |s| s != "$DATA" }
end

.list_streams(file_path) ⇒ Array<String>

List alternate data streams for a file

Parameters:

  • file_path (String)

    Path to file

Returns:

  • (Array<String>)

    List of stream names



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/omnizip/platform/ntfs_streams.rb', line 21

def self.list_streams(file_path)
  return [] unless available?
  return [] unless File.exist?(file_path)

  streams = []

  begin
    # Use PowerShell to list streams
    cmd = "powershell -Command \"Get-Item '#{file_path}' -Stream * | " \
          "Select-Object -ExpandProperty Stream\""
    output = `#{cmd} 2>&1`

    if $CHILD_STATUS.success?
      streams = output.lines.map(&:strip).reject do |s|
        s.empty? || s == ":$DATA"
      end
    end
  rescue StandardError => e
    warn "Failed to list NTFS streams: #{e.message}" if ENV["DEBUG"]
  end

  streams
end

.read_stream(file_path, stream_name) ⇒ String?

Read alternate data stream

Parameters:

  • file_path (String)

    Path to file

  • stream_name (String)

    Stream name

Returns:

  • (String, nil)

    Stream content or nil



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/omnizip/platform/ntfs_streams.rb', line 50

def self.read_stream(file_path, stream_name)
  return nil unless available?
  return nil unless File.exist?(file_path)

  begin
    # Read using alternate stream syntax: file.txt:StreamName
    stream_path = "#{file_path}:#{stream_name}"
    File.binread(stream_path)
  rescue Errno::ENOENT, Errno::EINVAL
    nil
  rescue StandardError => e
    warn "Failed to read NTFS stream: #{e.message}" if ENV["DEBUG"]
    nil
  end
end

.restore_streams(file_path, streams_data) ⇒ Integer

Restore streams from hash

Parameters:

  • file_path (String)

    Path to file

  • streams_data (Hash<String, String>)

    Stream name => data

Returns:

  • (Integer)

    Number of streams restored



186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/omnizip/platform/ntfs_streams.rb', line 186

def self.restore_streams(file_path, streams_data)
  return 0 unless available?
  return 0 unless File.exist?(file_path)

  restored = 0

  streams_data.each do |stream_name, data|
    restored += 1 if write_stream(file_path, stream_name, data)
  end

  restored
end

.stream_info(file_path, stream_name) ⇒ Hash?

Get stream information

Parameters:

  • file_path (String)

    Path to file

  • stream_name (String)

    Stream name

Returns:

  • (Hash, nil)

    Stream info or nil



136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/omnizip/platform/ntfs_streams.rb', line 136

def self.stream_info(file_path, stream_name)
  return nil unless available?
  return nil unless File.exist?(file_path)

  data = read_stream(file_path, stream_name)
  return nil unless data

  {
    name: stream_name,
    size: data.bytesize,
    exists: true,
  }
end

.write_stream(file_path, stream_name, data) ⇒ Boolean

Write alternate data stream

Parameters:

  • file_path (String)

    Path to file

  • stream_name (String)

    Stream name

  • data (String)

    Stream data

Returns:

  • (Boolean)

    true if successful



72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/omnizip/platform/ntfs_streams.rb', line 72

def self.write_stream(file_path, stream_name, data)
  return false unless available?
  return false unless File.exist?(file_path)

  begin
    # Write using alternate stream syntax: file.txt:StreamName
    stream_path = "#{file_path}:#{stream_name}"
    File.binwrite(stream_path, data)
    true
  rescue StandardError => e
    warn "Failed to write NTFS stream: #{e.message}" if ENV["DEBUG"]
    false
  end
end