ruby-fcp 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8ba6b44aeaf59819305d4f97364ec8693595a876
4
+ data.tar.gz: 7bac921fc48e3ef3a2668b968b32c053a4ce0346
5
+ SHA512:
6
+ metadata.gz: 573214e4b8d49f2438c2c5a327a4dd1fcd4f750647f98b4aab55a272a5724be12aa75a68c5bef14c60f867731e25a3ed74476f59774b5ca8bde246aa8bd14f6b
7
+ data.tar.gz: 64790ef14cc9a85ad2519ba7b68a53a91c0151a75b106a25e8b4d6e6335e397143d5d050d878026c31874bbc6416a7bca1ce6651ff57635816f135a7313d0e49
data/bin/fget ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'securerandom'
5
+ require 'ruby-fcp'
6
+
7
+ options = {}
8
+
9
+ OptionParser.new do |opts|
10
+ opts.banner = "Usage: fput.rb [options]"
11
+
12
+ opts.on("-u", "--uri URI",
13
+ "CHK@, USK@,SSK@") do |u|
14
+ options[:uri] = u
15
+ end
16
+
17
+ opts.on("-p", "--path path",
18
+ "if this is directory make sure you specified -d option") do |p|
19
+ options[:path] = p
20
+ end
21
+
22
+ opts.on("-s", "--server host:port",
23
+ "Must be a freenet client protocol server") do |s|
24
+ options[:server] = s
25
+ end
26
+
27
+ opts.on_tail("-h", "--help", "Show this message") do
28
+ puts opts
29
+ exit
30
+ end
31
+ end.parse!
32
+
33
+ if options.has_key? :uri and options.has_key? :path
34
+
35
+ if options.has_key? :server
36
+ server = options[:server].chomp.lstrip.split(':')
37
+ client = FCPClient.new("fput-#{SecureRandom.hex}",server[0],server[1])
38
+ else
39
+ client = FCPClient.new("fput-#{SecureRandom.hex}")
40
+ end
41
+
42
+ client.simple_get(options[:uri].chomp.lstrip,options[:path].chomp.chomp('/').lstrip)
43
+ client.close
44
+
45
+ else
46
+ puts "-u and -p are mandatory type -h or --help for usage information"
47
+ exit
48
+ end
data/bin/fput ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'securerandom'
5
+ require 'pathname'
6
+ require 'ruby-fcp/fcp_client'
7
+
8
+ options = { index: 'index.html' }
9
+
10
+ opts = OptionParser.new do |opts|
11
+ opts.banner = "Usage: #{$0} [options]"
12
+
13
+ opts.on("-u", "--uri URI",
14
+ "CHK@, USK@(requires insert uri),SSK@(requires inserturi)") do |u|
15
+ options[:uri] = u
16
+ end
17
+ opts.on("-p", "--path path",
18
+ "if this is directory make sure you specified -d option") do |p|
19
+ options[:path] = p
20
+ end
21
+
22
+ opts.on("-i", "--index defaultfile",
23
+ "default file for directory uploads, I believe it defaults to index.html") do |i|
24
+ options[:index] = i
25
+ end
26
+
27
+ opts.on("-s", "--server host:port",
28
+ "Must be a freenet client protocol server") do |s|
29
+ options[:server] = s
30
+ end
31
+
32
+ opts.on("-d", "--directory", "You are uploading a directory") do |d|
33
+ options[:directory] = d
34
+ end
35
+
36
+ opts.on_tail("-h", "--help", "Show this message") do
37
+ puts opts
38
+ exit
39
+ end
40
+
41
+ end.parse!
42
+
43
+ if options.has_key? :uri and options.has_key? :path
44
+
45
+ if options.has_key? :server
46
+ server = options[:server].chomp.lstrip.split(':')
47
+ client = FCPClient.new("fput-#{SecureRandom.hex}",server[0],server[1])
48
+ else
49
+ client = FCPClient.new("fput-#{SecureRandom.hex}")
50
+ end
51
+
52
+ if options[:directory]
53
+ client.simple_dir_put(options[:uri].chomp.lstrip,options[:path].chomp.chomp('/').lstrip,true,{"DefaultName" => options[:index]})
54
+ else
55
+ client.simple_put(options[:uri].chomp.lstrip,options[:path].chomp.lstrip)
56
+ end
57
+ client.close
58
+
59
+ else
60
+ puts "-u and -p are mandatory type -h or --help for usage information"
61
+ exit
62
+ end
@@ -0,0 +1,133 @@
1
+ require 'socket'
2
+ require 'thread'
3
+ require 'ruby-fcp/utils'
4
+
5
+ class Communicator
6
+ attr_reader :ConnectionIdentifier
7
+ attr_accessor :responses, :heartbeat
8
+
9
+ def initialize(client,host, port, version = 2.0)
10
+ @utils = Utils.new
11
+ @version = version
12
+ @ConnectionIdentifier = ""
13
+ @host = host
14
+ @port = port
15
+ @client = client
16
+ @responses = { peers: [],dda: [], default: [], error: [], datalengths: [] }
17
+ @tex = Mutex.new
18
+ @state = false
19
+ @queue = Queue.new
20
+ @heartbeat = 300
21
+ connect
22
+ end
23
+
24
+ def connect
25
+ @sock = TCPSocket.new @host ,@port
26
+ @sock.write @utils.packet_mangler({"Name" => @client,"ExpectedVersion" => @version},"ClientHello")
27
+ response = grab_response
28
+ unless response[:state] == -1
29
+ @sock_thrd = Thread.new {sock_thrd}
30
+ @ConnectionIdentifier = response["ConnectionIdentifier"]
31
+ @state = true
32
+ keep_alive
33
+ end
34
+ end
35
+
36
+ def sock_thrd
37
+ @threads = []
38
+ loop do
39
+ @threads.each do |thrd|
40
+ begin
41
+ @threads.delete thrd if thrd.join(0.5)
42
+ rescue RequestFinished => req
43
+ @responses[:error].push req
44
+ rescue Exception => excpt
45
+ puts "#{excpt}"
46
+ @threads.delete thrd
47
+ end
48
+ if thrd.status == false
49
+ @threads.delete thrd
50
+ elsif thrd.status == nil
51
+ @threads.delete thrd
52
+ end
53
+ end
54
+
55
+ @sock.close Thread.exit if @state == false
56
+
57
+ begin
58
+ while packet = @queue.pop(true)
59
+ @tex.synchronize{@sock.write packet}
60
+ #sort_out(packet)
61
+ end
62
+ rescue ThreadError => err
63
+ end
64
+
65
+ begin
66
+ if select([@sock], nil,nil,2)
67
+ packet = @tex.synchronize{grab_response}
68
+ sort_out(packet)
69
+ end
70
+ rescue
71
+ @sock.close Thread.exit
72
+ state = false
73
+ connect
74
+ end
75
+ end
76
+ end
77
+
78
+ def sort_out(packet)
79
+ if packet[:head].include? "NodeHello"
80
+ @ConnectionIdentifier = packet["ConnectionIdentifier"]
81
+ elsif packet[:head].include? "CloseConnectionDuplicateClientName"
82
+ @state = false
83
+ elsif packet.has_key? "Identifier"
84
+ if @responses.has_key? packet["Identifier"]
85
+ @responses[packet["Identifier"]].push packet
86
+ else
87
+ @responses[packet["Identifier"]] = [packet]
88
+ end
89
+ elsif packet[:head].include? "DDA"
90
+ @responses[:dda].push packet
91
+ elsif packet[:state] == -1
92
+ @responses[:error].push packet
93
+ elsif packet[:head].include? "Peer"
94
+ @responses[:peers].push packet
95
+ else packet
96
+ @responses[:default].push packet
97
+ end
98
+ end
99
+
100
+ def send_packet(message)
101
+ @queue.push message
102
+ end
103
+
104
+ def grab_response
105
+ response = { state: 1 }
106
+ line = @sock.readline
107
+ response[:state] = -1 if line =~ /ClosedConnectionDuplicateClientName|ProtocolError/
108
+ response[:head] = line.chomp
109
+ until line =~ /EndMessage|^Data$/
110
+ response[line.split('=')[0]] = line.split('=')[1].chomp if line.split('=').size == 2
111
+ line = @sock.readline
112
+ end
113
+ @responses[:datalengths] << response["DataLength"].to_i if response.has_key? "DataLength"
114
+ response[:data] = @sock.read @responses[:datalengths].pop if response[:head] =~ /AllData/
115
+ response
116
+ end
117
+
118
+ def keep_alive
119
+ Thread.start do
120
+ loop do
121
+ send_packet "Void\nEndMessage\n"
122
+ sleep @heartbeat
123
+ break if @state == false
124
+ end
125
+ end
126
+ end
127
+
128
+ def close
129
+ @tex.synchronize{@sock.write "Disconnect EndMessage\n"}
130
+ @sock.close
131
+ end
132
+
133
+ end
@@ -0,0 +1,290 @@
1
+ # The simple interface to FCP from ruby
2
+ # Implements raw fcp packets with sane defaults and automates some task
3
+
4
+ require 'digest'
5
+ require 'base64'
6
+ require 'ruby-fcp/communicator'
7
+
8
+ class FCPClient
9
+
10
+ attr_accessor :utils, :com
11
+
12
+ # clients name must be unique
13
+ # This performs NodeHello operations upon initialization.
14
+ # Communicator handles packet sending and recieving and sorting
15
+ def initialize(client, host = "127.0.0.1", port = 9481)
16
+ @utils = Utils.new
17
+ @com = Communicator.new(client,host,port)
18
+ end
19
+
20
+ # Simple attribute reader for you ConnectionIdentifier
21
+ def identifier
22
+ @com.ConnectionIdentifier
23
+ end
24
+
25
+ # Simple attribute reader to display all responses recieved
26
+ def responses
27
+ @com.responses
28
+ end
29
+
30
+ # return last response from request defined by Identifier
31
+ def last_response(id)
32
+ @com.responses[id].last
33
+ end
34
+
35
+ # Simple interface to put a single file onto freenet from your disk uses ClientPut message
36
+ # ==Possible values of uri:
37
+ # * CHK@ will generate and return the key your data is acessible at
38
+ # * SSK@ must have insert key provided by GenerateSSK or the method new_ssk_pair
39
+ # * KSK@filename
40
+ def simple_put(uri, filename, wait = true, opts = {})
41
+ id = @utils.id_generate
42
+ options = { "URI" => uri, "Identifier" => id, "UploadFrom" => 'disk', "Filename" => filename, "FileHash" => @utils.filehash_maker(id, filename,identifier) }.merge(opts)
43
+ @com.send_packet @utils.packet_mangler(options,"ClientPut")
44
+ #@com.fcpackets.client_put uri, id, options
45
+ if wait
46
+ wait_for id, /PutFailed|PutSuccessful/
47
+ else
48
+ id
49
+ end
50
+ end
51
+
52
+ # Another interface to ClientPut that allows you to directly put data pnto freenet
53
+ # data is your data
54
+ # ==Possible values of uri:
55
+ # * CHK@ will generate and return the key your data is acessible at
56
+ # * SSK@ must have insert key provided by GenerateSSK or the method new_ssk_pair
57
+ # * KSK@filename
58
+ def direct_put(uri,data, wait = true, opts = {})
59
+ id = @utils.id_generate
60
+ options = {"Identifier" => id, "URI" => uri ,"UploadFrom" => "direct", "DataLength" => data.bytesize }.merge(opts)
61
+ @com.send_packet @utils.packet_mangler(options,"ClientPut").sub! "EndMessage\n", "Data\n#{data}"
62
+ if wait
63
+ wait_for id,/PutFailed|PutSuccessful/
64
+ else
65
+ id
66
+ end
67
+ end
68
+
69
+ # Simple directory upload, upload one directory all at once
70
+ # automates the TestDDA for you just provide uri and directory
71
+ # Implements ClientPutDiskDir
72
+ # * CHK@ will generate and return the key your data is acessible at
73
+ # * SSK@ must have insert key provided by GenerateSSK or the method new_ssk_pair
74
+ # * KSK@filename
75
+ def simple_dir_put(uri, dir, wait = true, opts={})
76
+ id = @utils.id_generate
77
+ ddarun(dir,true,false)
78
+ options = {"Identifier" => id,"URI" => uri,"Filename" => dir, "Global" => 'true', "AllowUnreadableFiles" => 'true', "IncludeHiddenValues" => 'false'}.merge(opts)
79
+ @com.send_packet @utils.packet_mangler(options,"ClientPutDiskDir")
80
+ if wait
81
+ wait_for(id,/PutFailed|PutSuccessful/)
82
+ else
83
+ id
84
+ end
85
+ end
86
+
87
+ # simpler staight forward interface to ClientPutComplexDir, you provide with
88
+ # ==Possible values of uri:
89
+ # * CHK@ will generate and return the key your data is acessible at
90
+ # * SSK@ must have insert key provided by GenerateSSK or the method new_ssk_pair
91
+ # * KSK@filename
92
+ # As well as a list of hashes for files you want to put
93
+ # ==File hashlist format:
94
+ # * name: lib/hello or index.html, / will interperate as directory nesting
95
+ # * filename: in case of disk location of file on disk
96
+ # * uploadfrom: 'direct', 'disk' or 'redirect'
97
+ # * targeturi: in case of redirect, the location your are redirecting to
98
+ # * mimetype: not needed, but can be helpful
99
+ # * data: only nessecery in direct mode
100
+ def put_complex_dir(uri, files, wait = true,opts = {})
101
+ dirs = []
102
+ id = @utils.id_generate
103
+ files.each{ |f| dirs << f[:filename].split('/')[0...-1].join('/') + '/' if f.has_key? :filename }
104
+ (dirs.uniq).each { |dir| ddarun(dir,true,false) }
105
+ options = {"URI" => uri, "Identifier" => id}.merge(opts)
106
+ files.each_with_index do |file, index|
107
+ options["Files.#{index}.Name"] = file[:name]
108
+ options["Files.#{index}.UploadFrom"] = file[:uploadfrom]
109
+ options["Files.#{index}.DataLength"] = file[:data].bytesize if file.has_key? :data
110
+ options["Files.#{index}.Filename"] = file[:filename] if file[:uploadfrom].include? 'disk'
111
+ options["Files.#{index}.TargetURI"] = file[:targeturi] if file[:uploadfrom].include? 'redirect'
112
+ options["Files.#{index}.Metadata.ContentType"] = file[:mimetype] if file.has_key? :mimetype
113
+ end
114
+ message = @utils.packet_mangler(options,"ClientPutComplexDir")
115
+ files.each { |f| message << f[:data] if f.has_key? :data}
116
+ puts message
117
+ @com.send_packet message
118
+ if wait
119
+ wait_for(id,/PutFailed|PutSuccessful/)
120
+ else
121
+ id
122
+ end
123
+ end
124
+
125
+ # performs TestDDARequest and TestDDAResponse automagically
126
+ # read and write are true or false values
127
+ def ddarun(directory,read, write)
128
+ @com.send_packet @utils.packet_mangler({"Directory" => directory,"WantReadDirectory" => read, "WantWriteDirectory" => write} ,"TestDDARequest")
129
+ res = wait_for(:dda, /TestDDAReply/).pop
130
+ content = nil
131
+ if write
132
+ f = File.open(res["WriteFilename"],'w+')
133
+ f.write res["ContentToWrite"]
134
+ f.close
135
+ elsif read
136
+ content = File.open(res["ReadFilename"],'r').read
137
+ end
138
+ @com.send_packet @utils.packet_mangler({"Directory" => directory,"ReadContent" => content}, "TestDDAResponse")
139
+ response = wait_for(:dda ,/TestDDAComplete/).pop
140
+ File.delete(res["WriteFilename"]) if write
141
+ response
142
+ end
143
+
144
+ # just provide uri and download path/directory
145
+ # Implements ClientGet
146
+ def simple_get(uri,directory,wait = true, opts={})
147
+ id = @utils.id_generate
148
+ saveloc = File.join directory, uri.split('/')[-1]
149
+ ddarun(directory,false, true)
150
+ options = {"URI" => uri, "Identifier" => id, "ReturnType" => 'disk', "Filename" => saveloc, "TempFilename" => saveloc+".tmp" , "Persistence" => 'forever', "Global" => false, "Verbosity" => 1111111}.merge(opts)
151
+ @com.send_packet @utils.packet_mangler(options,"ClientGet")
152
+ if wait
153
+ wait_for(id,/GetFailed|DataFound/)
154
+ else
155
+ id
156
+ end
157
+ end
158
+
159
+ def direct_get(uri ,wait = true, opts={})
160
+ id = @utils.id_generate
161
+ options = {"URI" => uri, "Identifier" => id, "ReturnType" => 'direct', "Global" => false}.merge(opts)
162
+ @com.send_packet @utils.packet_mangler(options,"ClientGet")
163
+ if wait
164
+ wait_for(id,/AllData|GetFailed/)
165
+ else
166
+ id
167
+ end
168
+ end
169
+ # returns information on plugin, must be full class name as listed in freenet interface
170
+ # Implements GetPluginInfo
171
+ def get_plugin_info(pluginname, detailed = false)
172
+ id = @utils.id_generate
173
+ @com.send_packet @utils.packet_mangler({"PluginName" => pluginname, "Identifier" => id, "Detailed" => detailed },"GetPluginInfo")
174
+ wait_for id, /PluginInfo/
175
+ end
176
+
177
+ # Straigt forward, ListPeers, sometimes it may choke up and give you end list peers before your peers, in that case check the id
178
+ def listpeers
179
+ id = @utils.id_generate
180
+ @com.send_packet @utils.packet_mangler({"Identifier" => id, "WithMetaData" => true, "WithVolatile" => false},"ListPeers")
181
+ wait_for id, /EndListPeers/
182
+ end
183
+
184
+ #Uses GenerateSSK
185
+ def new_ssk_pair
186
+ id = @utils.id_generate
187
+ @com.send_packet @utils.packet_mangler({"Identifier" => id}, "GenerateSSK")
188
+ wait_for id, /SSKKeypair/
189
+ end
190
+
191
+ # returns information on a given peer not peers implements ListPeer
192
+ def peerinfo(peer)
193
+ @com.send_packet @utils.packet_mangler({"NodeIdentifier" => peer,"WithVolatile" => false,"WithMetadata" => true}, "ListPeer")
194
+ wait_for :peer, /Peer/
195
+ end
196
+
197
+ # List all persistent request just implements ListPersistentRequest
198
+ def list_persistent_requests
199
+ @com.send_packet "ListPersistentRequests\nEndMessage\n"
200
+ wait_for :default ,/EndListPersistentRequests/
201
+ end
202
+
203
+ def modify_persistent_request(id,clienttoken,priorityclass)
204
+ @com.send_packet @utils.packet_mangler({"Identifier" => id,"ClientToken" => clienttoken, "PriorityClass" => priorityclass}, "ModifyPersistentRequest")
205
+ wait_for id, /PersistentRequestModified/
206
+ end
207
+
208
+ #subscirbe to a usk, have to poll it yourself by using responses[id]
209
+ def subscribe_usk(uri, wait=false ,opts ={})
210
+ id = @utils.id_generate
211
+ @com.send_packet @utils.packet_mangler({"URI" => uri, "Identifier" => id} ,"SubscribeUSK")
212
+ id
213
+ end
214
+
215
+ def unsubscribe_usk(id)
216
+ @com.send_packet "UnsubscribeUSK\nIdentifier=#{id}\nEndMessage\n"
217
+ id
218
+ end
219
+
220
+ def proberequest(type,hopstolive=25,wait = true)
221
+ id = @utils.id_generate
222
+ @com.send_packet @utils.packet_mangler({"Identifier" => id,"Type" => type,"HopsToLive" => hopstolive}, "ProbeRequest")
223
+ if wait
224
+ wait_for id,/Probe/
225
+ else
226
+ id
227
+ end
228
+ end
229
+
230
+ # Waits for a specific pattern in a message identified by ID
231
+ def wait_for(id, pattern)
232
+ response = [ ]
233
+ loop do
234
+ begin
235
+ x = @com.responses[id].pop
236
+ print @com.responses[:error].pop
237
+ rescue
238
+ sleep(2)
239
+ end
240
+ unless x.nil?
241
+ if x[:head] =~ pattern
242
+ response << x
243
+ x.each { |key, value| puts "#{key}=#{value}" }
244
+ break
245
+ elsif x[:head] =~ /ProtocolError/
246
+ response << x
247
+ x.each { |key, value| puts "#{key}=#{value}" }
248
+ break
249
+ else
250
+ response << x
251
+ x.each { |key, value| puts "#{key}=#{value}" }
252
+ end
253
+ else
254
+ sleep(1)
255
+ end
256
+ end
257
+ response
258
+ end
259
+
260
+ # Just wait and wait given a id
261
+ def wait_for_ever(id)
262
+ loop do
263
+ begin
264
+ x = @com.responses[id].pop
265
+ rescue
266
+ print '.'
267
+ sleep(2)
268
+ print @com.responses[:error]
269
+ print @com.responses[:default]
270
+ end
271
+ unless x.nil?
272
+ x.each { |key, value| puts "#{key}=#{value}" }
273
+ else
274
+ print '.'
275
+ sleep(1)
276
+ puts @com.responses[:error]
277
+ print @com.responses[:default]
278
+ end
279
+ end
280
+ end
281
+
282
+ # Send disconnect message and close the socket
283
+ def close
284
+ @com.close
285
+ end
286
+
287
+ def killswitch
288
+ @com.send_packet "Shutdown\nEndMessage\n"
289
+ end
290
+ end
@@ -0,0 +1,18 @@
1
+ require 'securerandom'
2
+ require 'digest'
3
+ require 'base64'
4
+
5
+ class Utils
6
+ def filehash_maker(ident,filename,conid)
7
+ content = File.read(filename)
8
+ (Digest::SHA256.new << conid + "-#{ident}-" + content).base64digest
9
+ end
10
+
11
+ def packet_mangler(sash,header)
12
+ header +"\n"+ sash.map{|k,v| "#{k}=#{v}"}.join("\n") + "\nEndMessage\n"
13
+ end
14
+
15
+ def id_generate
16
+ SecureRandom.hex
17
+ end
18
+ end
data/lib/ruby-fcp.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'socket'
3
+ require 'digest'
4
+ require 'base64'
5
+
6
+ %w[ fcp_client communicator utils ].each do |file|
7
+ require "ruby-fcp/#{file}"
8
+ end
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-fcp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - hikiko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-31 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A gem interface for Freenet Client Protocol
14
+ email: kerben@i2pmail.org
15
+ executables:
16
+ - fget
17
+ - fput
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - bin/fget
22
+ - bin/fput
23
+ - lib/ruby-fcp.rb
24
+ - lib/ruby-fcp/communicator.rb
25
+ - lib/ruby-fcp/fcp_client.rb
26
+ - lib/ruby-fcp/utils.rb
27
+ homepage: https://github.com/kerben/ruby-fcp
28
+ licenses:
29
+ - Unlicense
30
+ metadata: {}
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 2.4.2
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: FCPClient
51
+ test_files: []
52
+ has_rdoc: