blackstack-core 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/functions.rb ADDED
@@ -0,0 +1,839 @@
1
+
2
+ module BlackStack
3
+
4
+ # -----------------------------------------------------------------------------------------
5
+ # PRY Supporting Functions
6
+ # -----------------------------------------------------------------------------------------
7
+ module Debugging
8
+ @@allow_breakpoints = false
9
+ @@verbose = false
10
+
11
+ # return true if breakpoints are allowed
12
+ def self.allow_breakpoints
13
+ @@allow_breakpoints
14
+ end
15
+
16
+ # set breakpoints allowed if the hash contains a key :allow_breakpoints with a value of true
17
+ def self.set(h)
18
+ @@allow_breakpoints = h[:allow_breakpoints] if h[:allow_breakpoints].is_a?(TrueClass)
19
+ @@verbose = h[:verbose] if h[:verbose].is_a?(TrueClass)
20
+
21
+ if !@@allow_breakpoints
22
+ # monkey patching the pry method to not break on breakpoints
23
+ new_pry = lambda do
24
+ print "Breakpoint are not allowed" if @@verbose
25
+ end
26
+
27
+ Binding.class_eval do
28
+ alias_method :old_pry, :pry
29
+ define_method :pry, new_pry
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ # -----------------------------------------------------------------------------------------
36
+ # OCRA Supporting Functions
37
+ # -----------------------------------------------------------------------------------------
38
+ module OCRA
39
+ # OCRA files run into a temp folder, where the script is unpacked.
40
+ #
41
+ # This function is useful to require a configuration file when the
42
+ # script is running inside an OCRA temp folder, since the local folder
43
+ # of the running command is not the filder where the exe file is hosted.
44
+ #
45
+ # More information:
46
+ # * https://stackoverflow.com/questions/1937743/how-to-get-the-current-working-directorys-absolute-path-from-irb
47
+ # * https://stackoverflow.com/questions/8577223/ruby-get-the-file-being-executed
48
+ # * https://stackoverflow.com/questions/7399882/ruby-getting-path-from-pathfilename/7400057
49
+ #
50
+ def self.require_in_working_path(filename, path, show_path_info=false)
51
+ puts '' if show_path_info
52
+ path = File.expand_path File.dirname(path)
53
+ #path = Dir.pwd
54
+ puts "require_in_working_path.path:#{path}:." if show_path_info
55
+ file = "#{path}/#{filename}"
56
+ puts "require_in_working_path.file:#{file}:." if show_path_info
57
+ require file
58
+ end
59
+ end # module OCRA
60
+
61
+ # -----------------------------------------------------------------------------------------
62
+ # DateTime Functions
63
+ # -----------------------------------------------------------------------------------------
64
+ module DateTime
65
+ # -----------------------------------------------------------------------------------------
66
+ # Encoding
67
+ # -----------------------------------------------------------------------------------------
68
+ module Encoding
69
+ # Convierte un objeto date-time a un string con formato sql-datetime (yyyy-mm-dd hh:mm:ss).
70
+ def self.datetime_to_sql(o)
71
+ return o.strftime("%Y-%m-%d %H:%M:%S")
72
+ end
73
+ end # module Encode
74
+
75
+ # -----------------------------------------------------------------------------------------
76
+ # Miscelaneous
77
+ # -----------------------------------------------------------------------------------------
78
+ module Misc
79
+ def self.datetime_values_check(year,month,day,hour,minute,second)
80
+ if (year.to_i<1900 || year.to_i>=2100)
81
+ return false
82
+ end
83
+
84
+ if (month.to_i<1 || month.to_i>12)
85
+ return false
86
+ end
87
+
88
+ # TODO: Considerar la cantidad de dias de cada mes, y los anios biciestos. Buscar alguna funcion existente.
89
+ if (day.to_i<1 || day.to_i>31)
90
+ return false
91
+ end
92
+
93
+ if (hour.to_i<0 || hour.to_i>23)
94
+ return false
95
+ end
96
+
97
+ if (minute.to_i<0 || minute.to_i>59)
98
+ return false
99
+ end
100
+
101
+ if (second.to_i<0 || second.to_i>59)
102
+ return false
103
+ end
104
+
105
+ return true
106
+ end # datetime_values_check
107
+ end # module Misc
108
+ end # module DateTime
109
+
110
+ # -----------------------------------------------------------------------------------------
111
+ # Numeric Functions
112
+ # -----------------------------------------------------------------------------------------
113
+ module Number
114
+ # -----------------------------------------------------------------------------------------
115
+ # Encoding
116
+ # -----------------------------------------------------------------------------------------
117
+ module Encoding
118
+ # Converts number to a string with a format like xx,xxx,xxx.xxxx
119
+ # number: it may be int or float
120
+ def self.format_with_separator(number)
121
+ whole_part, decimal_part = number.to_s.split('.')
122
+ [whole_part.gsub(/(\d)(?=\d{3}+$)/, '\1,'), decimal_part].compact.join('.')
123
+ end
124
+
125
+ # Convierte una cantidad de minutos a una leyenda legible por el usuario.
126
+ # Ejemplo: "2 days, 5 hours"
127
+ # Ejemplo: "4 hours, 30 minutes"
128
+ # Ejemplo: "3 days, 4 hour"
129
+ def self.encode_minutes(n)
130
+ # TODO: validar que n sea un entero mayor a 0
131
+ if (n<0)
132
+ return "?"
133
+ end
134
+ if (n<60)
135
+ return "#{n} minutes"
136
+ elsif (n<24*60)
137
+ return "#{(n/60).to_i} hours, #{n-60*(n/60).to_i} minutes"
138
+ else
139
+ return "#{(n/(24*60)).to_i} days, #{((n-24*60*(n/(24*60)).to_i)/60).to_i} hours"
140
+ end
141
+ end
142
+ end # module Encode
143
+ end # module Number
144
+
145
+ # -----------------------------------------------------------------------------------------
146
+ # String Functions
147
+ # -----------------------------------------------------------------------------------------
148
+ module Strings
149
+
150
+ GUID_SIZE = 36
151
+ MATCH_PASSWORD = /(?=.*[a-zA-Z])(?=.*[0-9]).{6,}/
152
+ MATCH_GUID = /{?[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]\-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]\-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]\-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]}?/
153
+ MATCH_FILENAME = /[\w\-\_\.]+/
154
+ MATCH_EMAIL = /[A-Z0-9._%a-z\-]+@(?:[A-Z0-9a-z\-]+\.)+[A-Za-z]{1,25}/
155
+ MATCH_DOMAIN = /(?:[A-Z0-9a-z\-]+\.)+[A-Za-z]{2,10}/
156
+ MATCH_DATE_STANDARD = /\d{4}\-(0?[1-9]|1[012])\-(0?[1-9]|[12][0-9]|3[01])/
157
+ MATCH_PHONE = /(?:\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}/
158
+
159
+ # Note: MATCH_URL gets the URL up to '?', but it doesn't retrieves the parameters.
160
+ # Exmaple:
161
+ # https://foo.com/bar?param1=value1&param2=value2 --> https://foo.com/bar?
162
+ # https://foo.com/bar/?param1=value1&param2=value2 --> https://foo.com/bar/?
163
+ MATCH_URL = /(https?:\/\/)?([\da-z\.-]+)([\.\:])([\da-z]{2,6})([\/[\da-z\.\-]+]*[\da-z])(\/)?(\?)?/i
164
+
165
+ MATCH_LINKEDIN_COMPANY_URL = /(https?:\/\/)?(www\\.)?linkedin\.com\/company\//
166
+ MATCH_FIXNUM = /[0-9]+/
167
+ MATCH_CONTENT_SPINNING = /{[^}]+}/
168
+ MATCH_SPINNED_TEXT = /code me/ # TODO: define this regex for the issue #1226
169
+
170
+ # -----------------------------------------------------------------------------------------
171
+ # Fuzzy String Comparsion Functions: How similar are 2 strings that are not exactly equal.
172
+ # -----------------------------------------------------------------------------------------
173
+ module SQL
174
+ def self.string_to_sql_string(s)
175
+ #return s.force_encoding("UTF-8").gsub("'", "''").to_s
176
+ return s.gsub("'", "''").to_s
177
+ end
178
+ end
179
+
180
+ # -----------------------------------------------------------------------------------------
181
+ # Fuzzy String Comparsion Functions: How similar are 2 strings that are not exactly equal.
182
+ # -----------------------------------------------------------------------------------------
183
+ module Comparing
184
+ # retorna 0 si los strings son iguales
185
+ # https://stackoverflow.com/questions/16323571/measure-the-distance-between-two-strings-with-ruby
186
+ def self.levenshtein_distance(s, t)
187
+ s.downcase!
188
+ t.downcase!
189
+
190
+ m = s.length
191
+ n = t.length
192
+ return m if n == 0
193
+ return n if m == 0
194
+ d = Array.new(m+1) {Array.new(n+1)}
195
+
196
+ (0..m).each {|i| d[i][0] = i}
197
+ (0..n).each {|j| d[0][j] = j}
198
+ (1..n).each do |j|
199
+ (1..m).each do |i|
200
+ d[i][j] = if s[i-1] == t[j-1] # adjust index into string
201
+ d[i-1][j-1] # no operation required
202
+ else
203
+ [ d[i-1][j]+1, # deletion
204
+ d[i][j-1]+1, # insertion
205
+ d[i-1][j-1]+1, # substitution
206
+ ].min
207
+ end
208
+ end
209
+ end
210
+ d[m][n]
211
+ end
212
+
213
+ # retorna la cantidad de palabras con mas de 3 caracteres que se encuentran en el parametro s
214
+ def self.max_sardi_distance(s)
215
+ s.downcase!
216
+ s.gsub!(/-/,' ')
217
+ ss = s.scan(/\b([a-z]+)\b/)
218
+ n = 0
219
+ ss.each { |x|
220
+ x = x[0]
221
+ if (x.size > 3) # para evitar keywords triviales como 'and'
222
+ n += 1
223
+ end
224
+ }
225
+ n
226
+ end
227
+
228
+ # retorna la cantidad de palabras con mas de 3 caracteres del parametro s que se encuentran en el parametro t
229
+ def self.sardi_distance(s, t)
230
+ s.downcase!
231
+ t.downcase!
232
+ s.gsub!(/-/,' ')
233
+ t.gsub!(/-/,' ')
234
+ max_distance = max_sardi_distance(s)
235
+ ss = s.scan(/\b([a-z]+)\b/)
236
+ tt = t.scan(/\b([a-z]+)\b/)
237
+ n = 0
238
+ ss.each { |x|
239
+ x = x[0]
240
+ if (x.size > 3) # para evitar keywords triviales como 'and'
241
+ if ( tt.select { |y| y[0] == x }.size > 0 )
242
+ n += 1
243
+ end
244
+ end
245
+ }
246
+ return max_distance - n
247
+ end
248
+ end # module Comparing
249
+
250
+ # -----------------------------------------------------------------------------------------
251
+ # Encoding: Make a string nice to be shown into an HTML string.
252
+ # -----------------------------------------------------------------------------------------
253
+ module Encoding
254
+ # Then it makes it compatible with UTF-8.
255
+ # More details here: https://bitbucket.org/leandro_sardi/blackstack/issues/961
256
+ def self.encode_string(s)
257
+ s.encode("UTF-8")
258
+ end
259
+
260
+ # Escape the string to be shown into an HTML screen.
261
+ # Then it makes it compatible with UTF-8.
262
+ # More details here: https://bitbucket.org/leandro_sardi/blackstack/issues/961
263
+ def self.encode_html(s)
264
+ encode_string(CGI.escapeHTML(s.to_s))
265
+ end
266
+
267
+ # Generates a description string from an exception object.
268
+ # Eescapes the string to be shown into an HTML screen.
269
+ # Makes it compatible with UTF-8.
270
+ # More details here: https://bitbucket.org/leandro_sardi/blackstack/issues/961
271
+ def self.encode_exception(e, include_backtrace=true)
272
+ ret = encode_html(e.to_s)
273
+ if (include_backtrace == true)
274
+ e.backtrace.each { |s|
275
+ ret += "<br/>" + encode_html(s)
276
+ } # e.backtrace.each
277
+ end # if
278
+ ret
279
+ end
280
+
281
+ # Returns a string with a description of a period of time, to be shown in the screen.
282
+ # period: it may be 'H', 'D', 'W', 'M', 'Y'
283
+ # units: it is a positive integer
284
+ def self.encode_period(period, units)
285
+ s = "Last "
286
+ s += units.to_i.to_s + " " if units.to_i > 1
287
+ s += "Hours" if period.upcase == "H" && units.to_i != 1
288
+ s += "Days" if period.upcase == "D" && units.to_i != 1
289
+ s += "Weeks" if period.upcase == "W" && units.to_i != 1
290
+ s += "Months" if period.upcase == "M" && units.to_i != 1
291
+ s += "Years" if period.upcase == "Y" && units.to_i != 1
292
+ s += "Hour" if period.upcase == "H" && units.to_i == 1
293
+ s += "Day" if period.upcase == "D" && units.to_i == 1
294
+ s += "Week" if period.upcase == "W" && units.to_i == 1
295
+ s += "Month" if period.upcase == "M" && units.to_i == 1
296
+ s += "Year" if period.upcase == "Y" && units.to_i == 1
297
+ s
298
+ end
299
+
300
+ #
301
+ def self.encode_guid(s)
302
+ return s.gsub('{',"").gsub('}',"").upcase
303
+ end
304
+
305
+ #
306
+ def self.encode_javascript(s)
307
+ s.to_s.gsub("'", "\\\\'").gsub("\r", "' + String.fromCharCode(13) + '").gsub("\n", "' + String.fromCharCode(10) + '")
308
+ end
309
+
310
+ end # module Encoding
311
+
312
+ # -----------------------------------------------------------------------------------------
313
+ # DateTime
314
+ # -----------------------------------------------------------------------------------------
315
+ module DateTime
316
+ # Check the string has the format yyyymmddhhmmss.
317
+ # => Return true if success. Otherwise, return false.
318
+ # => Year cannot be lower than 1900.
319
+ # => Year cannot be higher or equal than 2100.
320
+ def self.datetime_api_check(s)
321
+ return false if (s.size!=14)
322
+ year = s[0..3]
323
+ month = s[4..5]
324
+ day = s[6..7]
325
+ hour = s[8..9]
326
+ minute = s[10..11]
327
+ second = s[12..13]
328
+ BlackStack::DateTime::Misc::datetime_values_check(year,month,day,hour,minute,second)
329
+ end # def datetime_api_check
330
+
331
+ # Check the string has the format yyyy-mm-dd hh:mm:ss.
332
+ # => Return true if success. Otherwise, return false.
333
+ # => Year cannot be lower than 1900.
334
+ # => Year cannot be higher or equal than 2100.
335
+ def self.datetime_sql_check(s)
336
+ return false if (s.size!=19)
337
+ year = s[0..3]
338
+ month = s[5..6]
339
+ day = s[8..9]
340
+ hour = s[11..12]
341
+ minute = s[14..15]
342
+ second = s[17..18]
343
+ BlackStack::DateTime::Misc::datetime_values_check(year,month,day,hour,minute,second)
344
+ end # def datetime_sql_check
345
+
346
+ # Convierte un string con formato api-datatime (yyyymmddhhmmss) a un string con formato sql-datetime (yyyy-mm-dd hh:mm:ss).
347
+ def self.datetime_api_to_sql(s)
348
+ raise "Wrong Api DataTime Format." if (datetime_api_check(s)==false)
349
+ year = s[0..3]
350
+ month = s[4..5]
351
+ day = s[6..7]
352
+ hour = s[8..9]
353
+ minute = s[10..11]
354
+ second = s[12..13]
355
+ ret = "#{year}-#{month}-#{day} #{hour}:#{minute}:#{second}"
356
+ return ret
357
+ end # def datetime_api_to_sql
358
+
359
+ # Convierte un string con formato sql-datatime a un string con formato sql-datetime.
360
+ def self.datetime_sql_to_api(s)
361
+ raise "Wrong SQL DataTime Format." if (datetime_sql_check(s)==false)
362
+ year = s[0..3]
363
+ month = s[5..6]
364
+ day = s[8..9]
365
+ hour = s[11..12]
366
+ minute = s[14..15]
367
+ second = s[17..18]
368
+ ret = "#{year}#{month}#{day}#{hour}#{minute}#{second}"
369
+ return ret
370
+ end # def datetime_sql_to_api
371
+ end # module DateTime
372
+
373
+
374
+ # -----------------------------------------------------------------------------------------
375
+ # Spinning
376
+ # -----------------------------------------------------------------------------------------
377
+ module Spinning
378
+ # Esta funcion retorna una variacion al azar del texto que se pasa.
379
+ # Esta funcion se ocupa de dividir el texto en partes, para eviar el error "too big to product" que arroja la libraría.
380
+ def self.random_spinning_variation(text)
381
+ ret = text
382
+
383
+ text.scan(MATCH_CONTENT_SPINNING).each { |s|
384
+ a = ContentSpinning.new(s).spin
385
+ rep = a[rand(a.size)]
386
+ ret = ret.gsub(s, rep)
387
+ a = nil
388
+ }
389
+
390
+ return ret
391
+ end
392
+
393
+ # retorna true si la sintaxis del texto spineado es correcta
394
+ # caso contrario retorna false
395
+ # no soporta spinnings anidados. ejemplo: {my|our|{a car of mine}}
396
+ def self.valid_spinning_syntax?(s)
397
+ # valido que exste
398
+ n = 0
399
+ s.split('').each { |c|
400
+ n+=1 if c=='{'
401
+ n-=1 if c=='}'
402
+ if n!=0 && n!=1
403
+ #raise "Closing spining char '}' with not previous opening spining char '{'." if n<0
404
+ #raise "Opening spining char '{' inside another spining block." if n>1
405
+ return false if n<0 # Closing spining char '}' with not previous opening spining char '{'.
406
+ return false if n>1 # Opening spining char '{' inside another spining block.
407
+ end
408
+ }
409
+
410
+ return false if n!=0
411
+
412
+ # obtengo cada uno de los spinnings
413
+ s.scan(MATCH_CONTENT_SPINNING).each { |x|
414
+ a = x.split('|')
415
+ raise "No variations delimited by '|' inside spinning block." if a.size <= 1
416
+ }
417
+
418
+ true
419
+ end
420
+
421
+ # returns true if the text is spinned.
422
+ # otherwise, returns false.
423
+ def self.spintax?(s)
424
+ s.scan(MATCH_CONTENT_SPINNING).size > 0
425
+ end
426
+ end # module Spinning
427
+
428
+
429
+ # -----------------------------------------------------------------------------------------
430
+ # Miscelaneus
431
+ # -----------------------------------------------------------------------------------------
432
+ module Misc
433
+ # make a Ruby string safe for a filesystem.
434
+ # References:
435
+ # => https://stackoverflow.com/questions/1939333/how-to-make-a-ruby-string-safe-for-a-filesystem
436
+ # => http://devblog.muziboo.com/2008/06/17/attachment-fu-sanitize-filename-regex-and-unicode-gotcha/
437
+ def self.sanitize_filename(filename)
438
+ ret = filename.strip do |name|
439
+ # NOTE: File.basename doesn't work right with Windows paths on Unix
440
+ # get only the filename, not the whole path
441
+ name.gsub!(/^.*(\\|\/)/, '')
442
+
443
+ # Strip out the non-ascii character
444
+ name.gsub!(/[^0-9A-Za-z.\-]/, '_')
445
+ end
446
+ return ret
447
+ end
448
+ end # module Misc
449
+
450
+
451
+ # -----------------------------------------------------------------------------------------
452
+ # Email Appending Functions
453
+ # -----------------------------------------------------------------------------------------
454
+ module Appending
455
+ APPEND_PATTERN_FNAME_DOT_LNAME = 0
456
+ APPEND_PATTERN_FNAME = 1
457
+ APPEND_PATTERN_LNAME = 2
458
+ APPEND_PATTERN_F_LNAME = 3
459
+ APPEND_PATTERN_F_DOT_LNAME = 4
460
+
461
+ #
462
+ def self.name_pattern(pattern, fname, lname)
463
+ if (pattern==APPEND_PATTERN_FNAME_DOT_LNAME)
464
+ return "#{fname}.#{lname}"
465
+ elsif (pattern==APPEND_PATTERN_FNAME)
466
+ return "#{fname}"
467
+ elsif (pattern==APPEND_PATTERN_LNAME)
468
+ return "#{lname}"
469
+ elsif (pattern==APPEND_PATTERN_F_LNAME)
470
+ return "#{fname[0]}#{lname}"
471
+ elsif (pattern==APPEND_PATTERN_F_DOT_LNAME)
472
+ return "#{fname[0]}.#{lname}"
473
+ else
474
+ raise "getNamePattern: Unknown pattern code."
475
+ end
476
+ end
477
+
478
+ #
479
+ def self.get_email_variations(first_name, last_name, domain, is_a_big_company)
480
+ variations = Array.new
481
+ variations << first_name + "." + last_name + "@" + domain
482
+ variations << first_name[0] + last_name + "@" + domain
483
+ variations << first_name + "_" + last_name + "@" + domain
484
+ variations << first_name[0] + "." + last_name + "@" + domain
485
+ if (is_a_big_company == false)
486
+ variations << last_name + "@" + domain
487
+ variations << first_name + "@" + domain
488
+ end
489
+ #variations << first_name + "." + last_name + "@" + domain
490
+ #variations << first_name + "_" + last_name + "@" + domain
491
+ #variations << last_name + "." + first_name + "@" + domain
492
+ #variations << last_name + "_" + first_name + "@" + domain
493
+ #variations << first_name[0] + "." + last_name + "@" + domain
494
+ #variations << first_name + "." + last_name[0] + "@" + domain
495
+ #variations << last_name[0] + "." + first_name + "@" + domain
496
+ #variations << last_name + "." + first_name[0] + "@" + domain
497
+ #variations << first_name[0] + last_name + "@" + domain
498
+ #variations << first_name + last_name[0] + "@" + domain
499
+ #variations << last_name[0] + first_name + "@" + domain
500
+ #variations << last_name + first_name[0] + "@" + domain
501
+ #variations << first_name + "@" + domain
502
+ #variations << last_name + "@" + domain
503
+ return variations
504
+ end
505
+ end # module Appending
506
+ end # module String
507
+
508
+ # -----------------------------------------------------------------------------------------
509
+ # Network
510
+ # -----------------------------------------------------------------------------------------
511
+ module Netting
512
+ CALL_METHOD_GET = 'get'
513
+ CALL_METHOD_POST = 'post'
514
+ DEFAULT_SSL_VERIFY_MODE = OpenSSL::SSL::VERIFY_NONE
515
+ SUCCESS = 'success'
516
+
517
+ @@lockfiles = []
518
+
519
+ @@max_api_call_channels = 0 # 0 means infinite
520
+
521
+ def self.max_api_call_channels()
522
+ @@max_api_call_channels
523
+ end
524
+
525
+ def self.lockfiles()
526
+ @@lockfiles
527
+ end
528
+
529
+ def self.set(h)
530
+ @@max_api_call_channels = h[:max_api_call_channels]
531
+ @@lockfiles = []
532
+
533
+ i = 0
534
+ while i<@@max_api_call_channels
535
+ @@lockfiles << File.open("./apicall.channel_#{i.to_s}.lock", "w")
536
+ i+=1
537
+ end
538
+ end
539
+
540
+
541
+ class ApiCallException < StandardError
542
+ attr_accessor :description
543
+
544
+ def initialize(s)
545
+ self.description = s
546
+ end
547
+
548
+ def to_s
549
+ self.description
550
+ end
551
+ end
552
+
553
+ # New call_get
554
+ def self.call_get(url, params = {}, ssl_verify_mode=BlackStack::Netting::DEFAULT_SSL_VERIFY_MODE, support_redirections=true)
555
+ uri = URI(url)
556
+ uri.query = URI.encode_www_form(params)
557
+ Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https', :verify_mode => ssl_verify_mode) do |http|
558
+ req = Net::HTTP::Get.new uri
559
+ #req.body = body if !body.nil?
560
+ res = http.request req
561
+ case res
562
+ when Net::HTTPSuccess then res
563
+ when Net::HTTPRedirection then BlackStack::Netting::call_get(URI(res['location']), params, false) if support_redirections
564
+ else
565
+ res.error!
566
+ end
567
+ end
568
+ end
569
+
570
+ # Call the API and return th result.
571
+ # url: valid internet address
572
+ # params: hash of params to attach in the call
573
+ # ssl_verify_mode: you can disabele SSL verification here.
574
+ # max_channels: this method use lockfiles to prevent an excesive number of API calls from each datacenter. There is not allowed more simultaneous calls than max_channels.
575
+ # TODO: setup max_simultaneus_calls in the configurtion file.
576
+ # TODO: parameter support_redirections has been deprecated.
577
+ def self.call_post(url, params = {}, ssl_verify_mode=BlackStack::Netting::DEFAULT_SSL_VERIFY_MODE, support_redirections=true)
578
+ =begin
579
+ # build the lockfile name
580
+ x = 0
581
+ if BlackStack::Netting.max_api_call_channels.to_i > 0
582
+ raise "Max Channels cannot be higher than #{BlackStack::Netting.lockfiles.size.to_s}" if BlackStack::Netting.max_api_call_channels > BlackStack::Netting.lockfiles.size
583
+ x = rand(BlackStack::Netting.max_api_call_channels)
584
+ # lock the file
585
+ BlackStack::Netting.lockfiles[x].flock(File::LOCK_EX) if use_lockfile
586
+ end
587
+ =end
588
+ begin
589
+ #puts
590
+ #puts "call_post:#{url}:."
591
+ # do the call
592
+ uri = URI(url)
593
+ ret = Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https', :verify_mode => ssl_verify_mode) do |http|
594
+ req = Net::HTTP::Post.new(uri)
595
+ req['Content-Type'] = 'application/json'
596
+ req.set_form_data(params)
597
+ #req.body = body if !body.nil?
598
+ res = http.request req
599
+ case res
600
+ when Net::HTTPSuccess then res
601
+ #when Net::HTTPRedirection then BlackStack::Netting::call_post(URI(res['location']), params, BlackStack::Netting::DEFAULT_SSL_VERIFY_MODE, false) if support_redirections
602
+ else
603
+ res.error!
604
+ end
605
+ end
606
+
607
+ # release the file
608
+ # BlackStack::Netting.lockfiles[x].flock(File::LOCK_UN) if use_lockfile && BlackStack::Netting.max_api_call_channels.to_i > 0
609
+ rescue => e
610
+ # release the file
611
+ # BlackStack::Netting.lockfiles[x].flock(File::LOCK_UN) if use_lockfile && BlackStack::Netting.max_api_call_channels.to_i > 0
612
+
613
+ # elevo la excepcion
614
+ raise e
615
+ ensure
616
+ # release the file
617
+ # BlackStack::Netting.lockfiles[x].flock(File::LOCK_UN) if use_lockfile && BlackStack::Netting.max_api_call_channels.to_i > 0
618
+ end
619
+
620
+ # return
621
+ ret
622
+ end
623
+
624
+ #
625
+ def self.api_call(url, params={}, method=BlackStack::Netting::CALL_METHOD_POST, ssl_verify_mode=BlackStack::Netting::DEFAULT_SSL_VERIFY_MODE, max_retries=5)
626
+ nTries = 0
627
+ bSuccess = false
628
+ parsed = nil
629
+ sError = ""
630
+ while (nTries < max_retries && bSuccess == false)
631
+ begin
632
+ nTries = nTries + 1
633
+ uri = URI(url)
634
+ res = BlackStack::Netting::call_post(uri, params, ssl_verify_mode) if method==BlackStack::Netting::CALL_METHOD_POST
635
+ res = BlackStack::Netting::call_get(uri, params, ssl_verify_mode) if method==BlackStack::Netting::CALL_METHOD_GET
636
+ parsed = JSON.parse(res.body)
637
+ if (parsed['status']==BlackStack::Netting::SUCCESS)
638
+ bSuccess = true
639
+ else
640
+ sError = "Status: #{parsed['status'].to_s}. Description: #{parsed['value'].to_s}."
641
+ end
642
+ rescue Errno::ECONNREFUSED => e
643
+ sError = "Errno::ECONNREFUSED:" + e.to_console
644
+ rescue => e2
645
+ sError = "Exception:" + e2.to_console
646
+ end
647
+ end # while
648
+
649
+ if (bSuccess==false)
650
+ raise "#{sError}"
651
+ end
652
+ end # apicall
653
+
654
+ # Download a file from an url to a local folder.
655
+ # url: must be somedomain.net instead of somedomain.net/, otherwise, it will throw exception.
656
+ # to: must be a valid path to a folder.
657
+ def self.download(url, to)
658
+ uri = URI(url)
659
+ domain = uri.host.start_with?('www.') ? uri.host[4..-1] : uri.host
660
+ path = uri.path
661
+ filename = path.split("/").last
662
+ Net::HTTP.start(domain) do |http|
663
+ resp = http.get(path)
664
+ open(to, "wb") do |file|
665
+ file.write(resp.body)
666
+ end
667
+ end
668
+ end
669
+
670
+ # Return the extension of the last path into an URL.
671
+ # Example: get_url_extension("http://connect.data.com/sitemap_index.xml?foo_param=foo_value") => ".xml"
672
+ def self.get_url_extension(url)
673
+ return File.extname(URI.parse(url).path.to_s)
674
+ end
675
+
676
+ # Removes the 'www.' from an URL.
677
+ def self.get_host_without_www(url)
678
+ url = "http://#{url}" if URI.parse(url).scheme.nil?
679
+ host = URI.parse(url).host.downcase
680
+ host.start_with?('www.') ? host[4..-1] : host
681
+ end
682
+
683
+ # Get the final URL if a web page is redirecting.
684
+ def self.get_redirect(url)
685
+ uri = URI.parse(url)
686
+ protocol = uri.scheme
687
+ host = uri.host.downcase
688
+ res = Net::HTTP.get_response(uri)
689
+ "#{protocol}://#{host}#{res['location']}"
690
+ end
691
+
692
+ # returns the age in days of the given file
693
+ def self.file_age(filename)
694
+ (Time.now - File.ctime(filename))/(24*3600)
695
+ end
696
+
697
+
698
+ # TODO: Is not guaranteed this function works with 100% of the redirect-urls. This problem requires analysis and development of a general purpose algorith
699
+ # This function gets the final url from a redirect url.
700
+ # Not all the redirect-urls works the same way.
701
+ # Below are 3 examples. Each one works with 1 of the 2 strategies applied by this funcion.
702
+ # => url = "https://www.google.com.ar/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0CB0QFjAAahUKEwjCg8zMsNvGAhXMMj4KHWBfA50&url=https%3A%2F%2Fwww.linkedin.com%2Fpub%2Fdavid-bell%2F5%2F76a%2F12&ei=IGalVcLzFMzl-AHgvo3oCQ&usg=AFQjCNGMbF2vRIOWsRjF-bjjoG6Nl1wg_g&sig2=ZP6ZbZxpmTHw82rIP7YYew&bvm=bv.97653015,d.cWw"
703
+ # => url = "https://www.google.com.ar/url?q=https://www.linkedin.com/pub/mark-greene/2/bb8/b59&sa=U&ved=0CDoQFjAIahUKEwiqivi5sdvGAhWJg5AKHSzkB5o&usg=AFQjCNGE09H9hf92mfvwPVnComssDjBBCw"
704
+ # If the url is not a redirect-url, this function returns the same url.
705
+ =begin
706
+ def get_redirect(url)
707
+ begin
708
+ res = nil
709
+ httpc = HTTPClient.new
710
+ resp = httpc.get(url)
711
+ res = resp.header['Location']
712
+
713
+ if res.size == 0
714
+ uri = URI.parse(url)
715
+ uri_params = CGI.parse(uri.query)
716
+ redirected_url = uri_params['url'][0]
717
+
718
+ if ( redirected_url != nil )
719
+ res = redirected_url
720
+ else
721
+ res = url
722
+ end
723
+ else
724
+ res = res[0]
725
+ end
726
+ rescue
727
+ res = url
728
+ end
729
+ return res
730
+ end
731
+ =end
732
+ # returns a hash with the parametes in the url
733
+ def self.params(url)
734
+ # TODO: Corregir este parche:
735
+ # => El codigo de abajo usa la URL de una busqueda en google. Esta url generara una excepcion cuando se intenta parsear sus parametros.
736
+ # => Ejecutar las 2 lineas de abajo para verificar.
737
+ # => url = "https://www.google.com/webhp#q=[lead+generation]+%22John%22+%22Greater+New+York+City+Area+*+Financial+Services%22+site:linkedin.com%2Fpub+-site:linkedin.com%2Fpub%2Fdir"
738
+ # => p = CGI::parse(URI.parse(url).query)
739
+ # => La linea de abajo hace un gsbub que hace que esta url siga funcionando como busqueda de google, y ademas se posible parsearla.
740
+ url = url.gsub("webhp#q=", "webhp?q=")
741
+
742
+ return CGI::parse(URI.parse(url).query)
743
+ end
744
+
745
+ # Add a parameter to the url. It doesn't validate if the param already exists.
746
+ def self.add_param(url, param_name, param_value)
747
+ uri = URI(url)
748
+ params = URI.decode_www_form(uri.query || '')
749
+
750
+ if (params.size==0)
751
+ params << [param_name, param_value]
752
+ uri.query = URI.encode_www_form(params)
753
+ return uri.to_s
754
+ else
755
+ uri.query = URI.encode_www_form(params)
756
+ return uri.to_s + "&" + param_name + "=" + param_value
757
+ end
758
+ end
759
+
760
+ # Changes the value of a parameter in the url. It doesn't validate if the param already exists.
761
+ def self.change_param(url, param_name, param_value)
762
+ uri = URI(url)
763
+ # params = URI.decode_www_form(uri.query || [])
764
+ params = CGI.parse(uri.query)
765
+ params["start"] = param_value
766
+ uri.query = URI.encode_www_form(params)
767
+ uri.to_s
768
+ end
769
+
770
+ # Change or add the value of a parameter in the url, depending if the parameter already exists or not.
771
+ def self.set_param(url, param_name, param_value)
772
+ params = BlackStack::Netting::params(url)
773
+ if ( params.has_key?(param_name) == true )
774
+ newurl = BlackStack::Netting::change_param(url, param_name, param_value)
775
+ else
776
+ newurl = BlackStack::Netting::add_param(url, param_name, param_value)
777
+ end
778
+ return newurl
779
+ end
780
+
781
+ # get the domain from any url
782
+ def self.getDomainFromUrl(url)
783
+ if (url !~ /^http:\/\//i && url !~ /^https:\/\//i)
784
+ url = "http://#{url}"
785
+ end
786
+
787
+ if (URI.parse(url).host == nil)
788
+ raise "Cannot get domain for #{url}"
789
+ end
790
+
791
+ if (url.to_s.length>0)
792
+ return URI.parse(url).host.sub(/^www\./, '')
793
+ else
794
+ return nil
795
+ end
796
+ end
797
+
798
+ def self.getDomainFromEmail(email)
799
+ if email.email?
800
+ return email.split("@").last
801
+ else
802
+ raise "getDomainFromEmail: Wrong email format."
803
+ end
804
+ end
805
+
806
+ def self.getWhoisDomains(domain, allow_heuristic_to_avoid_hosting_companies=false)
807
+ a = Array.new
808
+ c = Whois::Client.new
809
+ r = c.lookup(domain)
810
+
811
+ res = r.to_s.scan(/Registrant Email: (#{BlackStack::Strings::MATCH_EMAIL})/).first
812
+ if (res!=nil)
813
+ a << BlackStack::Netting::getDomainFromEmail(res[0].downcase)
814
+ end
815
+
816
+ res = r.to_s.scan(/Admin Email: (#{BlackStack::Strings::MATCH_EMAIL})/).first
817
+ if (res!=nil)
818
+ a << BlackStack::Netting::getDomainFromEmail(res[0].downcase)
819
+ end
820
+
821
+ res = r.to_s.scan(/Tech Email: (#{BlackStack::Strings::MATCH_EMAIL})/).first
822
+ if (res!=nil)
823
+ a << BlackStack::Netting::getDomainFromEmail(res[0].downcase)
824
+ end
825
+
826
+ # remover duplicados
827
+ a = a.uniq
828
+
829
+ #
830
+ if (allow_heuristic_to_avoid_hosting_companies==true)
831
+ # TODO: develop this feature
832
+ end
833
+
834
+ return a
835
+ end
836
+
837
+ end # module Netting
838
+
839
+ end # module BlackStack