rcs-common 9.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +1 -0
- data/Rakefile +27 -0
- data/lib/rcs-common.rb +21 -0
- data/lib/rcs-common/binary.rb +64 -0
- data/lib/rcs-common/cgi.rb +7 -0
- data/lib/rcs-common/component.rb +87 -0
- data/lib/rcs-common/crypt.rb +71 -0
- data/lib/rcs-common/deploy.rb +96 -0
- data/lib/rcs-common/diagnosticable.rb +136 -0
- data/lib/rcs-common/evidence.rb +261 -0
- data/lib/rcs-common/evidence/addressbook.rb +173 -0
- data/lib/rcs-common/evidence/application.rb +59 -0
- data/lib/rcs-common/evidence/calendar.rb +62 -0
- data/lib/rcs-common/evidence/call.rb +185 -0
- data/lib/rcs-common/evidence/camera.rb +25 -0
- data/lib/rcs-common/evidence/chat.rb +272 -0
- data/lib/rcs-common/evidence/clibpoard.rb +58 -0
- data/lib/rcs-common/evidence/command.rb +50 -0
- data/lib/rcs-common/evidence/common.rb +78 -0
- data/lib/rcs-common/evidence/content/camera/001.jpg +0 -0
- data/lib/rcs-common/evidence/content/coin/wallet_bit.dat +0 -0
- data/lib/rcs-common/evidence/content/coin/wallet_lite.dat +0 -0
- data/lib/rcs-common/evidence/content/file/Einstein.docx +0 -0
- data/lib/rcs-common/evidence/content/file/arabic.docx +0 -0
- data/lib/rcs-common/evidence/content/mouse/001.jpg +0 -0
- data/lib/rcs-common/evidence/content/mouse/002.jpg +0 -0
- data/lib/rcs-common/evidence/content/mouse/003.jpg +0 -0
- data/lib/rcs-common/evidence/content/mouse/004.jpg +0 -0
- data/lib/rcs-common/evidence/content/print/001.jpg +0 -0
- data/lib/rcs-common/evidence/content/screenshot/001.jpg +0 -0
- data/lib/rcs-common/evidence/content/screenshot/002.jpg +0 -0
- data/lib/rcs-common/evidence/content/screenshot/003.jpg +0 -0
- data/lib/rcs-common/evidence/content/url/001.jpg +0 -0
- data/lib/rcs-common/evidence/content/url/002.jpg +0 -0
- data/lib/rcs-common/evidence/content/url/003.jpg +0 -0
- data/lib/rcs-common/evidence/device.rb +23 -0
- data/lib/rcs-common/evidence/download.rb +54 -0
- data/lib/rcs-common/evidence/exec.rb +0 -0
- data/lib/rcs-common/evidence/file.rb +129 -0
- data/lib/rcs-common/evidence/filesystem.rb +71 -0
- data/lib/rcs-common/evidence/info.rb +24 -0
- data/lib/rcs-common/evidence/keylog.rb +84 -0
- data/lib/rcs-common/evidence/mail.rb +237 -0
- data/lib/rcs-common/evidence/mic.rb +39 -0
- data/lib/rcs-common/evidence/mms.rb +36 -0
- data/lib/rcs-common/evidence/money.rb +676 -0
- data/lib/rcs-common/evidence/mouse.rb +62 -0
- data/lib/rcs-common/evidence/password.rb +60 -0
- data/lib/rcs-common/evidence/photo.rb +80 -0
- data/lib/rcs-common/evidence/position.rb +303 -0
- data/lib/rcs-common/evidence/print.rb +50 -0
- data/lib/rcs-common/evidence/screenshot.rb +53 -0
- data/lib/rcs-common/evidence/sms.rb +91 -0
- data/lib/rcs-common/evidence/url.rb +133 -0
- data/lib/rcs-common/fixnum.rb +48 -0
- data/lib/rcs-common/gridfs.rb +294 -0
- data/lib/rcs-common/heartbeat.rb +96 -0
- data/lib/rcs-common/keywords.rb +50 -0
- data/lib/rcs-common/mime.rb +65 -0
- data/lib/rcs-common/mongoid.rb +19 -0
- data/lib/rcs-common/pascalize.rb +62 -0
- data/lib/rcs-common/path_utils.rb +67 -0
- data/lib/rcs-common/resolver.rb +40 -0
- data/lib/rcs-common/rest.rb +17 -0
- data/lib/rcs-common/sanitize.rb +42 -0
- data/lib/rcs-common/serializer.rb +404 -0
- data/lib/rcs-common/signature.rb +141 -0
- data/lib/rcs-common/stats.rb +94 -0
- data/lib/rcs-common/symbolize.rb +10 -0
- data/lib/rcs-common/systemstatus.rb +136 -0
- data/lib/rcs-common/temporary.rb +13 -0
- data/lib/rcs-common/time.rb +24 -0
- data/lib/rcs-common/trace.rb +138 -0
- data/lib/rcs-common/trace.yaml +42 -0
- data/lib/rcs-common/updater/client.rb +354 -0
- data/lib/rcs-common/updater/dsl.rb +178 -0
- data/lib/rcs-common/updater/payload.rb +79 -0
- data/lib/rcs-common/updater/server.rb +126 -0
- data/lib/rcs-common/updater/shared_key.rb +55 -0
- data/lib/rcs-common/updater/tmp_dir.rb +13 -0
- data/lib/rcs-common/utf16le.rb +83 -0
- data/lib/rcs-common/version.rb +5 -0
- data/lib/rcs-common/winfirewall.rb +235 -0
- data/rcs-common.gemspec +64 -0
- data/spec/gridfs_spec.rb +637 -0
- data/spec/mongoid.yaml +6 -0
- data/spec/signature_spec.rb +105 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/updater_spec.rb +80 -0
- data/tasks/deploy.rake +21 -0
- data/tasks/protect.rake +90 -0
- data/test/helper.rb +17 -0
- data/test/test_binary.rb +107 -0
- data/test/test_cgi.rb +14 -0
- data/test/test_crypt.rb +125 -0
- data/test/test_evidence.rb +52 -0
- data/test/test_evidence_manager.rb +119 -0
- data/test/test_fixnum.rb +35 -0
- data/test/test_keywords.rb +137 -0
- data/test/test_mime.rb +49 -0
- data/test/test_pascalize.rb +100 -0
- data/test/test_path_utils.rb +24 -0
- data/test/test_rcs-common.rb +7 -0
- data/test/test_sanitize.rb +40 -0
- data/test/test_serialization.rb +20 -0
- data/test/test_stats.rb +90 -0
- data/test/test_symbolize.rb +20 -0
- data/test/test_systemstatus.rb +35 -0
- data/test/test_time.rb +56 -0
- data/test/test_trace.rb +25 -0
- data/test/test_utf16le.rb +71 -0
- data/test/test_winfirewall.rb +68 -0
- metadata +423 -0
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'rcs-common/trace'
|
2
|
+
require 'rcs-common/systemstatus'
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module RCS::HeartBeat
|
6
|
+
class Base
|
7
|
+
extend RCS::Tracer
|
8
|
+
include RCS::Tracer
|
9
|
+
include RCS::SystemStatusCodes
|
10
|
+
|
11
|
+
# Declare the component
|
12
|
+
# To use as an helper in the subclasses
|
13
|
+
def self.component(component_name, component_fullname = nil)
|
14
|
+
@component_name = component_name
|
15
|
+
@component_fullname = component_fullname
|
16
|
+
end
|
17
|
+
|
18
|
+
# This method is called from outside
|
19
|
+
def self.perform
|
20
|
+
heartbeat = new(@component_name, component_fullname: @component_fullname)
|
21
|
+
status, message = *heartbeat.perform
|
22
|
+
if status
|
23
|
+
heartbeat.update(status, message)
|
24
|
+
else
|
25
|
+
# trace(:warn, "heartbeat prevented")
|
26
|
+
end
|
27
|
+
rescue Interrupt
|
28
|
+
trace :fatal, "Heartbeat was interrupted because of a term signal"
|
29
|
+
rescue Exception => ex
|
30
|
+
trace :fatal, "Cannot perform heartbeat: #{ex.message}"
|
31
|
+
trace :fatal, ex.backtrace
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def initialize(component_name, component_fullname: nil)
|
36
|
+
raise("Undefined component version") unless version
|
37
|
+
raise("Undefined component name") unless component_name
|
38
|
+
|
39
|
+
@component_name = component_name.to_s.downcase
|
40
|
+
@component_fullname = component_fullname || "RCS::#{component_name.to_s.capitalize}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def update(status, message)
|
44
|
+
system_status, system_message = *system_status_and_message
|
45
|
+
|
46
|
+
if status == OK and system_status != OK
|
47
|
+
status, message = system_status, system_message
|
48
|
+
end
|
49
|
+
|
50
|
+
attributes = [@component_fullname, hostname, status, message, machine_stats, @component_name, version]
|
51
|
+
|
52
|
+
if defined?(::Status)
|
53
|
+
# Db
|
54
|
+
::Status.status_update(*attributes)
|
55
|
+
else
|
56
|
+
# Collector
|
57
|
+
db_class = Object.const_get(@component_fullname)::DB
|
58
|
+
attributes[1] = ''
|
59
|
+
db_class.instance.update_status(*attributes)
|
60
|
+
end
|
61
|
+
ensure
|
62
|
+
reset_system_status_and_message
|
63
|
+
end
|
64
|
+
|
65
|
+
# Override this method
|
66
|
+
def perform
|
67
|
+
system_status_and_message
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def hostname
|
73
|
+
@hostname ||= Socket.gethostname rescue 'unknown'
|
74
|
+
end
|
75
|
+
|
76
|
+
def reset_system_status_and_message
|
77
|
+
RCS::SystemStatus.reset
|
78
|
+
end
|
79
|
+
|
80
|
+
def system_status_and_message
|
81
|
+
[RCS::SystemStatus.status, RCS::SystemStatus.message]
|
82
|
+
end
|
83
|
+
|
84
|
+
def version
|
85
|
+
$version
|
86
|
+
end
|
87
|
+
|
88
|
+
def machine_stats
|
89
|
+
{
|
90
|
+
disk: RCS::SystemStatus.disk_free,
|
91
|
+
cpu: RCS::SystemStatus.cpu_load,
|
92
|
+
pcpu: RCS::SystemStatus.my_cpu_load(@component_fullname)
|
93
|
+
}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# here we are re-opening the ruby String class,
|
4
|
+
# the namespace must not be specified
|
5
|
+
|
6
|
+
require_relative 'sanitize'
|
7
|
+
|
8
|
+
class String
|
9
|
+
|
10
|
+
def keywords
|
11
|
+
|
12
|
+
# make a copy of itself to preserve the original
|
13
|
+
keywords = self.dup
|
14
|
+
|
15
|
+
# sanitize the input UTF-8
|
16
|
+
keywords.force_utf8!
|
17
|
+
|
18
|
+
# remove everything that is not alphanumeric
|
19
|
+
keywords.gsub!(/([^[:alnum:]])+/u, ' ')
|
20
|
+
#keywords.gsub!(/[(,%&@_":;!\#\-\*\[\]\{\}\?\\\+\'\.\/)]/, ' ')
|
21
|
+
|
22
|
+
# returns a copy of str with leading and trailing whitespace removed.
|
23
|
+
keywords.strip!
|
24
|
+
|
25
|
+
# convert to lowercase
|
26
|
+
keywords.downcase!
|
27
|
+
|
28
|
+
# split on spaces
|
29
|
+
keywords = keywords.split " "
|
30
|
+
|
31
|
+
# remove too long words
|
32
|
+
# it is with a very high probability a meaningless word (like encoded or something)
|
33
|
+
keywords.delete_if {|w| w.size > 25}
|
34
|
+
|
35
|
+
# remove duplicate words
|
36
|
+
keywords.uniq!
|
37
|
+
|
38
|
+
# sort the array
|
39
|
+
keywords.sort!
|
40
|
+
|
41
|
+
keywords
|
42
|
+
rescue Exception => e
|
43
|
+
#puts e.message if debug
|
44
|
+
#puts e.backtrace.first if debug
|
45
|
+
# fallback case
|
46
|
+
[]
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
#
|
2
|
+
# Mime Types handling module
|
3
|
+
#
|
4
|
+
|
5
|
+
# system
|
6
|
+
require 'mime/types'
|
7
|
+
|
8
|
+
# reopen the original class in order to add our specific mime types
|
9
|
+
module MIME
|
10
|
+
class Types
|
11
|
+
# rename the original method to be used in our wrapper
|
12
|
+
alias mime_type_for type_for
|
13
|
+
|
14
|
+
# our wrapper which adds our types
|
15
|
+
def type_for(filename, platform = false)
|
16
|
+
# call the original method
|
17
|
+
type = mime_type_for(filename, platform)
|
18
|
+
|
19
|
+
if type.empty? then
|
20
|
+
case File.extname(filename)
|
21
|
+
when '.cod'
|
22
|
+
type = MIME::Type.from_array('application/vnd.rim.cod', 'cod', '8bit', 'linux')
|
23
|
+
when '.apk'
|
24
|
+
type = MIME::Type.from_array('application/vnd.android.package-archive', 'apk', '8bit', 'linux')
|
25
|
+
when '.aetx'
|
26
|
+
type = MIME::Type.from_array('application/x-aetx', 'aetx', '8bit', 'linux')
|
27
|
+
when '.xap'
|
28
|
+
type = MIME::Type.from_array('application/x-silverlight-app', 'xap', '8bit', 'linux')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
return type
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module RCS
|
37
|
+
|
38
|
+
class MimeType
|
39
|
+
|
40
|
+
def self.get(file)
|
41
|
+
begin
|
42
|
+
# ask for the mime type
|
43
|
+
type = MIME::Types.type_for(file)
|
44
|
+
|
45
|
+
# if there are multiple choices, get the first one
|
46
|
+
type = type.first if type.is_a?(Array)
|
47
|
+
rescue Exception => e
|
48
|
+
type = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
# special case for IE mobile not understanding this
|
52
|
+
if File.extname(file) == '.cab' then
|
53
|
+
type = MIME::Type.new('binary/octet-stream')
|
54
|
+
end
|
55
|
+
|
56
|
+
# default if none is found
|
57
|
+
type = MIME::Type.new('binary/octet-stream') if type.nil?
|
58
|
+
|
59
|
+
# convert from MIME::Type to String
|
60
|
+
return type.to_s
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end #RCS::
|
65
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'mongoid'
|
2
|
+
|
3
|
+
# Monkey path serialization of BSON::ObjectId
|
4
|
+
module BSON
|
5
|
+
class ObjectId
|
6
|
+
def as_json(*args)
|
7
|
+
to_s
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Fix #symbolize_key called on BSON::Document
|
13
|
+
module BSON
|
14
|
+
class Document < ::Hash
|
15
|
+
def symbolize_keys
|
16
|
+
to_h.symbolize_keys
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# here we are re-opening the ruby String class,
|
2
|
+
# the namespace must not be specified
|
3
|
+
|
4
|
+
class String
|
5
|
+
|
6
|
+
# returns a string encoded into a pascalized form
|
7
|
+
def pascalize
|
8
|
+
# the pascalized version is composed as follow:
|
9
|
+
# - 4 bytes len in front
|
10
|
+
# - UTF-16LE encoded string
|
11
|
+
# - UTF-16LE null terminator
|
12
|
+
pascalized = [self.encode('UTF-16LE').bytesize + 2].pack('I')
|
13
|
+
pascalized += self.encode('UTF-16LE').unpack('H*').pack('H*')
|
14
|
+
pascalized += "\x00\x00"
|
15
|
+
|
16
|
+
# BINARY is an alias for ASCII-8BIT
|
17
|
+
return pascalized.encode!('ASCII-8BIT')
|
18
|
+
end
|
19
|
+
|
20
|
+
# returns a string decoded from its pascalized form
|
21
|
+
def unpascalize
|
22
|
+
begin
|
23
|
+
# get the len (unsigned int 4 bytes)
|
24
|
+
len = self.unpack('I')
|
25
|
+
# sanity check to avoid
|
26
|
+
return nil unless len.first <= self.length - 4
|
27
|
+
# get the string
|
28
|
+
unpascalized = self.slice(4, len.first).force_encoding('UTF-16LE')
|
29
|
+
# convert to UTF-8
|
30
|
+
unpascalized.encode!('UTF-8')
|
31
|
+
# remove the trailing zero
|
32
|
+
unpascalized.chop!
|
33
|
+
|
34
|
+
return unpascalized
|
35
|
+
rescue
|
36
|
+
return nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# returns an array containing all the concatenated pascalized strings
|
41
|
+
def unpascalize_ary
|
42
|
+
many = []
|
43
|
+
buffer = self
|
44
|
+
len = 0
|
45
|
+
|
46
|
+
begin
|
47
|
+
# len of the current token
|
48
|
+
len += buffer.unpack('I').first + 4
|
49
|
+
# unpascalize the token
|
50
|
+
str = buffer.unpascalize
|
51
|
+
# add to the result array
|
52
|
+
many << str unless str.nil?
|
53
|
+
# move the pointer after the token
|
54
|
+
buffer = self.slice(len, self.length)
|
55
|
+
# sanity check
|
56
|
+
break if buffer.nil?
|
57
|
+
end while buffer.length != 0
|
58
|
+
|
59
|
+
return many
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module RCS
|
2
|
+
module Common
|
3
|
+
module PathUtils
|
4
|
+
# Requires and rcs module. Sarch for a folder named rcs-NAME, where NAME is
|
5
|
+
# the given name, and requires a script named NAME.rb
|
6
|
+
#
|
7
|
+
# @note The current directory is changed (chdir command)
|
8
|
+
def require_component(name, opts = {})
|
9
|
+
$invocation_directory = Dir.pwd
|
10
|
+
$invocation_directory = ENV['CWD'] if ENV['CWD']
|
11
|
+
|
12
|
+
init_script = caller[0].scan(/^(.+)\:\d+\:.+$/)[0][0]
|
13
|
+
|
14
|
+
if init_script !~ /(bin|lib)\/rcs\-#{name}/
|
15
|
+
raise "Invalid execution directory"
|
16
|
+
end
|
17
|
+
|
18
|
+
$execution_directory = File.expand_path('../..', init_script)
|
19
|
+
|
20
|
+
#puts "WARN: chdir to #{$execution_directory}"
|
21
|
+
Dir.chdir($execution_directory)
|
22
|
+
|
23
|
+
require_release("#{$execution_directory}/lib/rcs-#{name}-release/#{name}.rb", warn: true)
|
24
|
+
|
25
|
+
rescue LoadError => error
|
26
|
+
puts "FATAL: cannot load component rcs-#{name}: #{error.message}"
|
27
|
+
puts error.backtrace.join(", ") if error.backtrace.respond_to?(:join)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Requires an encrypted ruby script (rcs-XXX-releases folders) when
|
31
|
+
# available, otherwise requires the clean version of it (rcs-XXX folders)
|
32
|
+
def require_release(path, warn: false, required: true)
|
33
|
+
if path.include?("-release")
|
34
|
+
new_path = path
|
35
|
+
else
|
36
|
+
new_path = path.gsub(/(.*)rcs-([^\/]+)/, '\1rcs-\2-release')
|
37
|
+
end
|
38
|
+
|
39
|
+
if warn and !new_path.include?('-release') and File.exists?(new_path)
|
40
|
+
puts "WARNING: Executing clear text code... (debug only)"
|
41
|
+
end
|
42
|
+
|
43
|
+
begin
|
44
|
+
require(new_path)
|
45
|
+
return
|
46
|
+
rescue LoadError => error
|
47
|
+
# In this case, raise the LoadError only if it's caused
|
48
|
+
# by another #require inside the required script, otherwise
|
49
|
+
# go on and try to require the clean version
|
50
|
+
raise(error) if error.path != new_path
|
51
|
+
end
|
52
|
+
|
53
|
+
begin
|
54
|
+
new_path.gsub!('-release', '')
|
55
|
+
require(new_path)
|
56
|
+
rescue LoadError => error
|
57
|
+
raise(error) if required
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
unless Kernel.respond_to?(:require_release)
|
65
|
+
Kernel.__send__(:include, RCS::Common::PathUtils)
|
66
|
+
Object.__send__(:include, Kernel)
|
67
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'resolv'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module RCS
|
5
|
+
module Resolver
|
6
|
+
def resolved_dns_cache
|
7
|
+
@@dns_cache ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def resolve_dns(dns, use_cache: false)
|
11
|
+
if use_cache and resolved_dns_cache[dns]
|
12
|
+
return resolved_dns_cache[dns]
|
13
|
+
end
|
14
|
+
|
15
|
+
ip = nil
|
16
|
+
|
17
|
+
Timeout::timeout(8) do
|
18
|
+
ip = Resolv.getaddress(dns).to_s rescue nil
|
19
|
+
end
|
20
|
+
|
21
|
+
if ip.nil? and defined?(Win32)
|
22
|
+
Timeout::timeout(10) do
|
23
|
+
info = Win32::Resolv.get_resolv_info
|
24
|
+
resolver = Resolv::DNS.new(nameserver: info[1], search: info[0])
|
25
|
+
ip = resolver.getaddress(dns).to_s rescue nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
raise("Cannot resolve DNS #{dns.inspect}: unknown host") unless ip
|
30
|
+
|
31
|
+
resolved_dns_cache[dns] = ip
|
32
|
+
|
33
|
+
return ip
|
34
|
+
rescue Timeout::Error
|
35
|
+
raise("Cannot resolve DNS #{dns.inspect}: timeout")
|
36
|
+
rescue Exception => ex
|
37
|
+
raise("Cannot resolve DNS #{dns.inspect}: #{ex.message}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rcs-common/trace'
|
2
|
+
|
3
|
+
module RCS
|
4
|
+
module Common
|
5
|
+
module Rest
|
6
|
+
STATUS_OK = 200
|
7
|
+
STATUS_REDIRECT = 302
|
8
|
+
STATUS_BAD_REQUEST = 400
|
9
|
+
STATUS_AUTH_REQUIRED = 401
|
10
|
+
STATUS_NOT_FOUND = 404
|
11
|
+
STATUS_NOT_AUTHORIZED = 403
|
12
|
+
STATUS_METHOD_NOT_ALLOWED = 405
|
13
|
+
STATUS_CONFLICT = 409
|
14
|
+
STATUS_SERVER_ERROR = 500
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# here we are re-opening the ruby String class,
|
4
|
+
# the namespace must not be specified
|
5
|
+
|
6
|
+
class String
|
7
|
+
REMOVE_INVALID_CHARS_REGEXP = Regexp.new(/([^[:alnum:][:graph:]\n\r])+/u)
|
8
|
+
|
9
|
+
def remove_invalid_chars
|
10
|
+
self.force_utf8.gsub(REMOVE_INVALID_CHARS_REGEXP, ' ')
|
11
|
+
end
|
12
|
+
|
13
|
+
def force_utf8(modify_self = false)
|
14
|
+
src_encoding = valid_encoding? ? encoding.to_s : 'BINARY'
|
15
|
+
dst_encoding = 'UTF-8'
|
16
|
+
|
17
|
+
args = [dst_encoding, src_encoding, {:invalid => :replace, :undef => :replace, replace: ''}]
|
18
|
+
|
19
|
+
modify_self ? encode!(*args) : encode(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def force_utf8!
|
23
|
+
force_utf8(true)
|
24
|
+
end
|
25
|
+
|
26
|
+
def strip_html_tags
|
27
|
+
copy = self.dup
|
28
|
+
|
29
|
+
# Strip HTML tags
|
30
|
+
copy.gsub!(/<[^>]*>/, '')
|
31
|
+
|
32
|
+
# Strip encoded & repetitively encoded HTML tags
|
33
|
+
copy.gsub!(/&(amp;)*lt;.*?&(amp;)*gt;/im, '')
|
34
|
+
|
35
|
+
# Strip HTML entities and repetitively encoded entities
|
36
|
+
# Or decode with http://htmlentities.rubyforge.org/
|
37
|
+
copy.gsub!(/&(amp;)*((#x?)?[a-f0-9]+|[a-z]+);/i, ' ')
|
38
|
+
|
39
|
+
copy
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|