mu 5.7.8 → 5.7.9
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/mu.rb +1 -1
- data/lib/mu/command.rb +18 -2
- data/lib/mu/command/cmd_musl.rb +165 -0
- data/lib/mu/command/cmd_runscale.rb +12 -8
- data/lib/mu/command/help.rb +1 -0
- data/lib/mu/har.rb +449 -0
- data/lib/mu/libxml.rb +21 -0
- data/lib/mu/maker.rb +288 -0
- data/lib/mu/xmlizable.rb +559 -0
- metadata +78 -267
- data/rdoc/Gemfile.html +0 -194
- data/rdoc/LICENSE_txt.html +0 -201
- data/rdoc/Mu.html +0 -478
- data/rdoc/Mu/Client.html +0 -461
- data/rdoc/Mu/Command.html +0 -338
- data/rdoc/Mu/Command/API.html +0 -844
- data/rdoc/Mu/Command/Cmd_appid.html +0 -1341
- data/rdoc/Mu/Command/Cmd_cli.html +0 -800
- data/rdoc/Mu/Command/Cmd_ddt.html +0 -1612
- data/rdoc/Mu/Command/Cmd_homepage.html +0 -762
- data/rdoc/Mu/Command/Cmd_muapi.html +0 -1363
- data/rdoc/Mu/Command/Cmd_netconfig.html +0 -1077
- data/rdoc/Mu/Command/Cmd_runscale.html +0 -1441
- data/rdoc/Mu/Command/Cmd_runscenario.html +0 -787
- data/rdoc/Mu/Command/Cmd_runverify.html +0 -893
- data/rdoc/Mu/Command/Cmd_scale.html +0 -1323
- data/rdoc/Mu/Command/Cmd_system.html +0 -677
- data/rdoc/Mu/Command/Curl.html +0 -751
- data/rdoc/Mu/Command/Help.html +0 -305
- data/rdoc/Mu/Curl.html +0 -243
- data/rdoc/Mu/Curl/Error.html +0 -304
- data/rdoc/Mu/Curl/Error/Authorize.html +0 -355
- data/rdoc/Mu/Curl/Error/Connect.html +0 -286
- data/rdoc/Mu/Curl/Error/DNS.html +0 -236
- data/rdoc/Mu/Curl/Error/Region.html +0 -307
- data/rdoc/Mu/Curl/Error/Status.html +0 -286
- data/rdoc/Mu/Curl/Error/Timeout.html +0 -286
- data/rdoc/Mu/Curl/Verify.html +0 -472
- data/rdoc/Mu/Curl/Verify/Request.html +0 -424
- data/rdoc/Mu/Curl/Verify/Response.html +0 -372
- data/rdoc/Mu/Curl/Verify/Result.html +0 -373
- data/rdoc/Mu/Ddt.html +0 -1347
- data/rdoc/Mu/Helper.html +0 -517
- data/rdoc/Mu/Homepage.html +0 -719
- data/rdoc/Mu/HttpHelper.html +0 -890
- data/rdoc/Mu/Muapi.html +0 -1226
- data/rdoc/Mu/Netconfig.html +0 -1178
- data/rdoc/Mu/Scale.html +0 -1359
- data/rdoc/Mu/System.html +0 -504
- data/rdoc/Object.html +0 -285
- data/rdoc/README_rdoc.html +0 -892
- data/rdoc/Rakefile.html +0 -257
- data/rdoc/TCTestMu.html +0 -2583
- data/rdoc/Test.html +0 -235
- data/rdoc/Test/Unit.html +0 -235
- data/rdoc/Test/Unit/TestCase.html +0 -236
- data/rdoc/Test/helper_rb.html +0 -62
- data/rdoc/Test/tc_test_mu_rb.html +0 -60
- data/rdoc/VERSION.html +0 -179
- data/rdoc/bin/mu.html +0 -54
- data/rdoc/classes/Mu.html +0 -132
- data/rdoc/classes/Mu/Client.html +0 -214
- data/rdoc/classes/Mu/Command.html +0 -129
- data/rdoc/classes/Mu/Command/API.html +0 -362
- data/rdoc/classes/Mu/Command/Cmd_appid.html +0 -238
- data/rdoc/classes/Mu/Command/Cmd_cli.html +0 -273
- data/rdoc/classes/Mu/Command/Cmd_ddt.html +0 -639
- data/rdoc/classes/Mu/Command/Cmd_homepage.html +0 -276
- data/rdoc/classes/Mu/Command/Cmd_muapi.html +0 -527
- data/rdoc/classes/Mu/Command/Cmd_netconfig.html +0 -399
- data/rdoc/classes/Mu/Command/Cmd_runscale.html +0 -220
- data/rdoc/classes/Mu/Command/Cmd_runscenario.html +0 -197
- data/rdoc/classes/Mu/Command/Cmd_runverify.html +0 -196
- data/rdoc/classes/Mu/Command/Cmd_scale.html +0 -511
- data/rdoc/classes/Mu/Command/Cmd_system.html +0 -236
- data/rdoc/classes/Mu/Command/Curl.html +0 -182
- data/rdoc/classes/Mu/Command/Help.html +0 -137
- data/rdoc/classes/Mu/Curl.html +0 -116
- data/rdoc/classes/Mu/Curl/Error.html +0 -148
- data/rdoc/classes/Mu/Curl/Error/Authorize.html +0 -165
- data/rdoc/classes/Mu/Curl/Error/Connect.html +0 -139
- data/rdoc/classes/Mu/Curl/Error/DNS.html +0 -113
- data/rdoc/classes/Mu/Curl/Error/Region.html +0 -150
- data/rdoc/classes/Mu/Curl/Error/Status.html +0 -139
- data/rdoc/classes/Mu/Curl/Error/Timeout.html +0 -139
- data/rdoc/classes/Mu/Curl/Verify.html +0 -207
- data/rdoc/classes/Mu/Curl/Verify/Request.html +0 -187
- data/rdoc/classes/Mu/Curl/Verify/Response.html +0 -172
- data/rdoc/classes/Mu/Curl/Verify/Result.html +0 -172
- data/rdoc/classes/Mu/Ddt.html +0 -610
- data/rdoc/classes/Mu/Helper.html +0 -308
- data/rdoc/classes/Mu/Homepage.html +0 -306
- data/rdoc/classes/Mu/HttpHelper.html +0 -393
- data/rdoc/classes/Mu/Muapi.html +0 -549
- data/rdoc/classes/Mu/Netconfig.html +0 -478
- data/rdoc/classes/Mu/Scale.html +0 -580
- data/rdoc/classes/Mu/System.html +0 -232
- data/rdoc/classes/Object.html +0 -139
- data/rdoc/classes/TCTestMu.html +0 -948
- data/rdoc/classes/Test.html +0 -107
- data/rdoc/classes/Test/Unit.html +0 -107
- data/rdoc/classes/Test/Unit/TestCase.html +0 -113
- data/rdoc/created.rid +0 -36
- data/rdoc/files/lib/mu/api/ddt_rb.html +0 -101
- data/rdoc/files/lib/mu/api/homepage_rb.html +0 -101
- data/rdoc/files/lib/mu/api/muapi_rb.html +0 -101
- data/rdoc/files/lib/mu/api/netconfig_rb.html +0 -101
- data/rdoc/files/lib/mu/api/scale_rb.html +0 -101
- data/rdoc/files/lib/mu/api/system_rb.html +0 -101
- data/rdoc/files/lib/mu/client_rb.html +0 -101
- data/rdoc/files/lib/mu/command/api_rb.html +0 -101
- data/rdoc/files/lib/mu/command/cmd_appid_rb.html +0 -119
- data/rdoc/files/lib/mu/command/cmd_cli_rb.html +0 -108
- data/rdoc/files/lib/mu/command/cmd_ddt_rb.html +0 -116
- data/rdoc/files/lib/mu/command/cmd_homepage_rb.html +0 -115
- data/rdoc/files/lib/mu/command/cmd_muapi_rb.html +0 -115
- data/rdoc/files/lib/mu/command/cmd_netconfig_rb.html +0 -116
- data/rdoc/files/lib/mu/command/cmd_runscale_rb.html +0 -119
- data/rdoc/files/lib/mu/command/cmd_runscenario_rb.html +0 -115
- data/rdoc/files/lib/mu/command/cmd_runverify_rb.html +0 -118
- data/rdoc/files/lib/mu/command/cmd_scale_rb.html +0 -115
- data/rdoc/files/lib/mu/command/cmd_system_rb.html +0 -116
- data/rdoc/files/lib/mu/command/curl_rb.html +0 -101
- data/rdoc/files/lib/mu/command/help_rb.html +0 -101
- data/rdoc/files/lib/mu/command_rb.html +0 -107
- data/rdoc/files/lib/mu/curl/error_rb.html +0 -101
- data/rdoc/files/lib/mu/curl/verify_rb.html +0 -101
- data/rdoc/files/lib/mu/helper_rb.html +0 -101
- data/rdoc/files/lib/mu/http_helper_rb.html +0 -101
- data/rdoc/files/lib/mu_rb.html +0 -121
- data/rdoc/files/test/helper_rb.html +0 -112
- data/rdoc/files/test/tc_test_mu_rb.html +0 -111
- data/rdoc/fr_class_index.html +0 -34
- data/rdoc/fr_file_index.html +0 -31
- data/rdoc/fr_method_index.html +0 -112
- data/rdoc/images/brick.png +0 -0
- data/rdoc/images/brick_link.png +0 -0
- data/rdoc/images/bug.png +0 -0
- data/rdoc/images/bullet_black.png +0 -0
- data/rdoc/images/bullet_toggle_minus.png +0 -0
- data/rdoc/images/bullet_toggle_plus.png +0 -0
- data/rdoc/images/date.png +0 -0
- data/rdoc/images/find.png +0 -0
- data/rdoc/images/loadingAnimation.gif +0 -0
- data/rdoc/images/macFFBgHack.png +0 -0
- data/rdoc/images/package.png +0 -0
- data/rdoc/images/page_green.png +0 -0
- data/rdoc/images/page_white_text.png +0 -0
- data/rdoc/images/page_white_width.png +0 -0
- data/rdoc/images/plugin.png +0 -0
- data/rdoc/images/ruby.png +0 -0
- data/rdoc/images/tag_green.png +0 -0
- data/rdoc/images/wrench.png +0 -0
- data/rdoc/images/wrench_orange.png +0 -0
- data/rdoc/images/zoom.png +0 -0
- data/rdoc/index.html +0 -884
- data/rdoc/js/darkfish.js +0 -116
- data/rdoc/js/jquery.js +0 -32
- data/rdoc/js/quicksearch.js +0 -114
- data/rdoc/js/thickbox-compressed.js +0 -10
- data/rdoc/lib/mu/api/ddt_rb.html +0 -52
- data/rdoc/lib/mu/api/homepage_rb.html +0 -52
- data/rdoc/lib/mu/api/muapi_rb.html +0 -52
- data/rdoc/lib/mu/api/netconfig_rb.html +0 -52
- data/rdoc/lib/mu/api/scale_rb.html +0 -52
- data/rdoc/lib/mu/api/system_rb.html +0 -52
- data/rdoc/lib/mu/client_rb.html +0 -52
- data/rdoc/lib/mu/command/api_rb.html +0 -52
- data/rdoc/lib/mu/command/cmd_appid_rb.html +0 -62
- data/rdoc/lib/mu/command/cmd_cli_rb.html +0 -55
- data/rdoc/lib/mu/command/cmd_ddt_rb.html +0 -60
- data/rdoc/lib/mu/command/cmd_homepage_rb.html +0 -57
- data/rdoc/lib/mu/command/cmd_muapi_rb.html +0 -59
- data/rdoc/lib/mu/command/cmd_netconfig_rb.html +0 -58
- data/rdoc/lib/mu/command/cmd_runscale_rb.html +0 -62
- data/rdoc/lib/mu/command/cmd_runscenario_rb.html +0 -57
- data/rdoc/lib/mu/command/cmd_runverify_rb.html +0 -61
- data/rdoc/lib/mu/command/cmd_scale_rb.html +0 -58
- data/rdoc/lib/mu/command/cmd_system_rb.html +0 -59
- data/rdoc/lib/mu/command/curl_rb.html +0 -52
- data/rdoc/lib/mu/command/help_rb.html +0 -52
- data/rdoc/lib/mu/command_rb.html +0 -55
- data/rdoc/lib/mu/curl/error_rb.html +0 -52
- data/rdoc/lib/mu/curl/verify_rb.html +0 -52
- data/rdoc/lib/mu/helper_rb.html +0 -52
- data/rdoc/lib/mu/http_helper_rb.html +0 -52
- data/rdoc/lib/mu_rb.html +0 -80
- data/rdoc/rdoc-style.css +0 -208
- data/rdoc/rdoc.css +0 -706
- data/test/data/app_id_stats.csv +0 -1
- data/test/data/app_id_status.json +0 -156
- data/test/data/data_cgi.msl +0 -94
- data/test/data/data_cgi.xml +0 -322
- data/test/data/default_test.csv +0 -3
- data/test/data/ftp_with_channel.xml +0 -1643
- data/test/data/irc.xml +0 -3837
- data/test/data/scale.json +0 -25
- data/test/data/test_data_cgi_error.xml +0 -35
data/lib/mu.rb
CHANGED
data/lib/mu/command.rb
CHANGED
@@ -13,16 +13,32 @@ class Test::Unit::Assertions::AssertionMessage
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
=end
|
16
|
+
require 'optparse'
|
17
|
+
require 'ostruct'
|
16
18
|
|
17
19
|
class Mu
|
18
|
-
class Command
|
20
|
+
class Command
|
19
21
|
#include Test::Unit::Assertions
|
20
22
|
include Helper
|
23
|
+
|
24
|
+
attr_accessor :options, :opts
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
# Set defaults
|
28
|
+
@options = OpenStruct.new
|
29
|
+
@options.verbose = false
|
30
|
+
|
31
|
+
# TO DO - add additional defaults
|
32
|
+
@opts = OptionParser.new
|
33
|
+
@opts.on('-V', '--verbose') { @options.verbose = true }
|
34
|
+
|
35
|
+
end
|
21
36
|
|
22
37
|
@@mu_ip = ENV['MU_IP']
|
23
38
|
@@mu_admin_user = ENV['MU_ADMIN_USER']
|
24
39
|
@@mu_admin_pass = ENV['MU_ADMIN_PASS']
|
25
|
-
|
40
|
+
|
41
|
+
end # Command
|
26
42
|
end # Mu
|
27
43
|
|
28
44
|
Dir["#{File.dirname(__FILE__)}/command/*.rb"].each { |c| require c }
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# File manipulation and generation class for creating musl files from other file formats.
|
2
|
+
# Example: $mu cmd_musl:from_har --ignore-css --ignore-js --endpoint <path_to_har_file>
|
3
|
+
require 'mu/maker'
|
4
|
+
require 'mu/har'
|
5
|
+
|
6
|
+
class Mu
|
7
|
+
class Command
|
8
|
+
class Cmd_musl < Command
|
9
|
+
include MuSL
|
10
|
+
|
11
|
+
attr_accessor :arguments, :scenario, :ignores, :har
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
super
|
15
|
+
@options.strip_large_content = false
|
16
|
+
@options.large_content_size = nil
|
17
|
+
# TO-DO clean up variable usage and declaration
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
# displays command-line help
|
22
|
+
def cmd_help argv
|
23
|
+
setup argv
|
24
|
+
end
|
25
|
+
|
26
|
+
# checks and parses the command line arguments, displays help if missing
|
27
|
+
def setup argv
|
28
|
+
@arguments = argv
|
29
|
+
@ignores = Array.new
|
30
|
+
|
31
|
+
# If options are parsed and args are valid setup is good else show help
|
32
|
+
if parsed_options? && arguments_valid?
|
33
|
+
true
|
34
|
+
else
|
35
|
+
cmds = [
|
36
|
+
"mu cmd_musl:help",
|
37
|
+
"mu cmd_musl:from_har <options> <har_file>",
|
38
|
+
]
|
39
|
+
|
40
|
+
puts "#{@opts}"
|
41
|
+
puts
|
42
|
+
puts "Available Commands:"
|
43
|
+
cmds.each do | c |
|
44
|
+
puts c
|
45
|
+
end
|
46
|
+
puts
|
47
|
+
puts "Outputs:"
|
48
|
+
puts "Scenario file: <harfilename>.msl by default or filename from -s"
|
49
|
+
|
50
|
+
false
|
51
|
+
end
|
52
|
+
|
53
|
+
end # setup
|
54
|
+
|
55
|
+
# Turns a har file into a musl scenario file
|
56
|
+
def cmd_from_har argv
|
57
|
+
# Check if our command line arguments are valid
|
58
|
+
if setup argv
|
59
|
+
# Get har object and hosts
|
60
|
+
har_file = @options.har_files[0]
|
61
|
+
@har = Har.new har_file, @options, @ignores
|
62
|
+
msl_file = File.open(@options.scenario,'w')
|
63
|
+
created_musl = @har.generate msl_file
|
64
|
+
msl_file.write created_musl
|
65
|
+
msl_file.close
|
66
|
+
puts "You have successfully generated a scenario: #{@options.scenario}"
|
67
|
+
else
|
68
|
+
# Handle failure logic here
|
69
|
+
raise "Invalid Command Line Options"
|
70
|
+
end
|
71
|
+
end # end cmd_from_har
|
72
|
+
|
73
|
+
# Turns a pcap into a scenario file
|
74
|
+
def cmd_from_pcap argv
|
75
|
+
# TODO LATER
|
76
|
+
raise "This command isn't implemented yet"
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
private
|
83
|
+
# Show the help banner
|
84
|
+
def help
|
85
|
+
setup ARGV
|
86
|
+
end
|
87
|
+
|
88
|
+
# True if at least one har file has been entered
|
89
|
+
def arguments_valid?
|
90
|
+
# TO DO - implement better logic here
|
91
|
+
if(@arguments.length > 0)
|
92
|
+
true
|
93
|
+
else
|
94
|
+
false
|
95
|
+
end
|
96
|
+
end # arguments valid()
|
97
|
+
|
98
|
+
# True if options were parsed or not needed
|
99
|
+
def parsed_options?
|
100
|
+
# specify the command line options and their logic
|
101
|
+
|
102
|
+
# Set a banner, displayed at the top
|
103
|
+
# of the help screen.
|
104
|
+
@opts.banner = "Usage: mu cmd_musl:<command> <options> [FILE]"
|
105
|
+
|
106
|
+
# Define the options, and what they do
|
107
|
+
@opts.on( '--ignore-non-essentials', 'Ignore App-Unknown/JS/CSS/Image files' ) do
|
108
|
+
@options.ignore = true
|
109
|
+
@ignores.push('text/css','application/javascript','application/x-javascript','text/javascript','application/octet-stream','application/x-unknown-content-type','image/png','image/jpg','image/jpeg','image/gif')
|
110
|
+
end
|
111
|
+
# Define the options, and what they do
|
112
|
+
@opts.on( '--ignore-images', 'Ignore Image files' ) do
|
113
|
+
@options.ignore = true
|
114
|
+
@ignores.push('image/png','image/jpg','image/jpeg','image/gif')
|
115
|
+
end
|
116
|
+
# Define the options, and what they do
|
117
|
+
@opts.on( '--ignore-css', 'Ignore CSS files' ) do
|
118
|
+
@options.ignore = true
|
119
|
+
@ignores.push('text/css')
|
120
|
+
end
|
121
|
+
@opts.on( '--ignore-js', 'Ignore Javascript files' ) do
|
122
|
+
@options.ignore = true
|
123
|
+
@ignores.push('application/javascript','application/x-javascript','text/javascript')
|
124
|
+
end
|
125
|
+
@opts.on( '--ignore-payload', 'Ignore the content payload' ) do
|
126
|
+
@options.ignore_payload = true
|
127
|
+
end
|
128
|
+
@opts.on('--strip-large-content [SIZE]',
|
129
|
+
Integer,
|
130
|
+
'Replace large response content with a repeated field') do |size|
|
131
|
+
@options.strip_large_content = true
|
132
|
+
@options.large_content_size = size
|
133
|
+
end
|
134
|
+
@opts.on( '--endpoint', 'One way communication' ) do
|
135
|
+
@options.endpoint = true
|
136
|
+
end
|
137
|
+
@opts.on( '-s', '--scenario [FILENAME]', 'Specify the msl filename to be created' ) do |scenario|
|
138
|
+
@options.scenario = scenario
|
139
|
+
end
|
140
|
+
# Remove all options just leaving the harfiles
|
141
|
+
@opts.parse! @arguments rescue return false
|
142
|
+
@options.har_files = @arguments
|
143
|
+
unless @options.scenario
|
144
|
+
# Identical filename replacing the extension har with msl
|
145
|
+
begin
|
146
|
+
filename = @options.har_files[0].dup
|
147
|
+
@options.scenario = filename.gsub!(/\.har/,".msl")
|
148
|
+
rescue
|
149
|
+
# Filename is missing which means we'll show help string
|
150
|
+
return false
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
true
|
156
|
+
end # parsed_options()
|
157
|
+
|
158
|
+
|
159
|
+
|
160
|
+
|
161
|
+
|
162
|
+
end # Cmd_musl
|
163
|
+
end # Command
|
164
|
+
end # Mu
|
165
|
+
|
@@ -187,7 +187,10 @@ class Mu
|
|
187
187
|
raise "Failed to read in scenario #{scenario}"
|
188
188
|
end
|
189
189
|
|
190
|
-
|
190
|
+
if @testset.empty?
|
191
|
+
@api.configure("csv", "")
|
192
|
+
else
|
193
|
+
msg "using testset [#{@testset}]", Logger::INFO
|
191
194
|
@api.configure("csv", File.read(@testset))
|
192
195
|
end
|
193
196
|
|
@@ -339,7 +342,7 @@ class Mu
|
|
339
342
|
|
340
343
|
def dump_status(status, msl)
|
341
344
|
filename = "app_id_status.json"
|
342
|
-
msg "Update status to: #{File.
|
345
|
+
msg "Update status to: #{File.expand_path(filename)}", Logger::INFO
|
343
346
|
f = File.open(filename, "a")
|
344
347
|
status["filename"] = msl
|
345
348
|
str = JSON.pretty_generate(status)
|
@@ -350,7 +353,7 @@ class Mu
|
|
350
353
|
|
351
354
|
def output_csv(msl_file)
|
352
355
|
filename = "app_id_stats.csv"
|
353
|
-
msg "Update stats to: #{File.
|
356
|
+
msg "Update stats to: #{File.expand_path(filename)}", Logger::INFO
|
354
357
|
unless File.exists?(filename)
|
355
358
|
heading = "scenario,executed,errors,timeouts,client_tx_bytes,client_tx_msgs,client_rx_bytes,client_rx_msgs,server_tx_bytes,server_tx_msgs,server_rx_bytes,server_rx_msgs"
|
356
359
|
File.open(filename, 'a'){|f| f.puts(heading)}
|
@@ -361,9 +364,9 @@ class Mu
|
|
361
364
|
|
362
365
|
def output_verify_results(msl_file)
|
363
366
|
filename = "app_id_stats.csv"
|
364
|
-
msg "Update verify results to: #{File.
|
367
|
+
msg "Update verify results to: #{File.expand_path(filename)}", Logger::DEBUG
|
365
368
|
unless File.exists?(filename)
|
366
|
-
msg "Writting verify results to: #{File.
|
369
|
+
msg "Writting verify results to: #{File.expand_path(filename)}", Logger::INFO
|
367
370
|
heading = "scenario, status"
|
368
371
|
File.open(filename, 'a'){|f| f.puts(heading)}
|
369
372
|
end
|
@@ -372,7 +375,7 @@ class Mu
|
|
372
375
|
|
373
376
|
def parse_verify_response(response)
|
374
377
|
if response.nil? # || response.empty?
|
375
|
-
msg "*** error = no response received from /verify
|
378
|
+
msg "*** error = no response received from /verify ***", Logger::ERROR
|
376
379
|
@verify_response = "Error = No response from verify"
|
377
380
|
return @verify_response
|
378
381
|
end
|
@@ -382,7 +385,7 @@ class Mu
|
|
382
385
|
if response["status"]["error"] == true
|
383
386
|
@error = response["status"]["error"]
|
384
387
|
@reason = response["status"]["reason"]
|
385
|
-
msg "*** Error = #{@error}, reason = #{@reason}
|
388
|
+
msg "*** Error = #{@error}, reason = #{@reason} ***", Logger::ERROR
|
386
389
|
@verify_response = "Error = #{@error}, reason = #{@reason}"
|
387
390
|
return @verify_response
|
388
391
|
end
|
@@ -404,7 +407,7 @@ class Mu
|
|
404
407
|
if status["status"]["error"] == true
|
405
408
|
@error = status["status"]["error"]
|
406
409
|
@reason = status["status"]["reason"]
|
407
|
-
msg "*** Error = #{@error}, reason = #{@reason}
|
410
|
+
msg "*** Error = #{@error}, reason = #{@reason} ***"
|
408
411
|
return nil
|
409
412
|
end
|
410
413
|
end
|
@@ -527,6 +530,7 @@ class Mu
|
|
527
530
|
|
528
531
|
if [ '-o', '--output' ].member? k
|
529
532
|
$stdout.reopen(shift(k, argv), "w")
|
533
|
+
$stdout.sync = true
|
530
534
|
next
|
531
535
|
end
|
532
536
|
|
data/lib/mu/command/help.rb
CHANGED
@@ -11,6 +11,7 @@ class Help < Command
|
|
11
11
|
{ :cmd => 'mu cmd_ddt:help', :help => 'Show help on using the Studio Verify Api through the command-line' },
|
12
12
|
{ :cmd => 'mu cmd_homepage:help', :help => 'Show help on using the Homepage Api through the command-line' },
|
13
13
|
{ :cmd => 'mu cmd_muapi:help', :help => 'Show help on using the Mu Api for fuzzing, templates, backup and archive' },
|
14
|
+
{ :cmd => 'mu cmd_musl:help', :help => 'Show help on using the Musl Api through the command-line' },
|
14
15
|
{ :cmd => 'mu cmd_netconfig:help', :help => 'Show help on using the Netconfig Api through the command-line'},
|
15
16
|
{ :cmd => 'mu cmd_runscale:help', :help => 'Show help on running the Studio Scale app'},
|
16
17
|
{ :cmd => 'mu cmd_runscenario:help', :help => 'Show help on running the Scenario Editor Verify app' },
|
data/lib/mu/har.rb
ADDED
@@ -0,0 +1,449 @@
|
|
1
|
+
# To change this template, choose Tools | Templates
|
2
|
+
# and open the template in the editor.
|
3
|
+
require 'mu/maker'
|
4
|
+
require 'mu/xmlizable'
|
5
|
+
|
6
|
+
class Har
|
7
|
+
HTTP_CONTENT_LENGTH_HEADER = 'Content-Length'
|
8
|
+
HTTP_CONTENT_TYPE_HEADER = 'Content-Type'
|
9
|
+
HTTP_CONTENT_ENCODING_HEADER = 'Content-Encoding'
|
10
|
+
HTTP_TRANSFER_ENCODING_HEADER = 'Transfer-Encoding'
|
11
|
+
HTTP_CONTENT_TRANSFER_ENCODING_HEADER = 'Content-Transfer-Encoding'
|
12
|
+
|
13
|
+
HTTP_FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded'
|
14
|
+
HTTP_TEXT_PLAIN_CONTENT_TYPE = 'text/plain'
|
15
|
+
HTTP_TEXT_HTML_CONTENT_TYPE = 'text/html'
|
16
|
+
HTTP_TEXT_XML_CONTENT_TYPE = 'text/xml'
|
17
|
+
HTTP_APPLICATION_XML_CONTENT_TYPE = 'application/xml'
|
18
|
+
HTTP_TEXT_JSON_CONTENT_TYPE = 'text/json'
|
19
|
+
HTTP_APPLICATION_JSON_CONTENT_TYPE = 'application/json'
|
20
|
+
HTTP_TEXT_JAVASCRIPT_CONTENT_TYPE = 'text/javascript'
|
21
|
+
HTTP_APPLICATION_JAVASCRIPT_CONTENT_TYPE = 'application/x-javascript'
|
22
|
+
HTTP_TEXT_CSS_CONTENT_TYPE = 'text/css'
|
23
|
+
|
24
|
+
HTTP_TEXT_CONTENT_TYPES = [ HTTP_FORM_CONTENT_TYPE,
|
25
|
+
HTTP_TEXT_PLAIN_CONTENT_TYPE,
|
26
|
+
HTTP_TEXT_HTML_CONTENT_TYPE,
|
27
|
+
HTTP_TEXT_XML_CONTENT_TYPE,
|
28
|
+
HTTP_APPLICATION_XML_CONTENT_TYPE,
|
29
|
+
HTTP_TEXT_JSON_CONTENT_TYPE,
|
30
|
+
HTTP_APPLICATION_JSON_CONTENT_TYPE,
|
31
|
+
HTTP_TEXT_JAVASCRIPT_CONTENT_TYPE,
|
32
|
+
HTTP_APPLICATION_JAVASCRIPT_CONTENT_TYPE,
|
33
|
+
HTTP_TEXT_CSS_CONTENT_TYPE ]
|
34
|
+
|
35
|
+
HTTP_GZIP_CONTENT_ENCODING = 'gzip'
|
36
|
+
HTTP_DEFLATE_CONTENT_ENCODING = 'deflate'
|
37
|
+
|
38
|
+
HTTP_EMPTY_GZIP_BODY = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03\x03\x00\x00" +
|
39
|
+
"\x00\x00\x00\x00\x00\x00\x00"
|
40
|
+
|
41
|
+
HTTP_CHUNKED_TRANSFER_ENCODING = 'chunked'
|
42
|
+
|
43
|
+
HTTP_VIDEO_X_MS_WMV_CONTENT_TYPE = 'video/x-ms-wmv'
|
44
|
+
HTTP_VIDEO_X_MS_WMA_CONTENT_TYPE = 'audio/x-ms-wma'
|
45
|
+
|
46
|
+
HTTP_STREAMING_CONTENT_TYPES = [ HTTP_VIDEO_X_MS_WMV_CONTENT_TYPE,
|
47
|
+
HTTP_VIDEO_X_MS_WMA_CONTENT_TYPE ]
|
48
|
+
|
49
|
+
HTTP_CONTENT_SLICE_SIZE = 1024
|
50
|
+
|
51
|
+
MSL_ESCAPES = XMLizable::ESCAPES.dup
|
52
|
+
MSL_ESCAPES['"'.ord] = %q{\"}
|
53
|
+
MSL_ESCAPES["'".ord] = %q{\'}
|
54
|
+
|
55
|
+
|
56
|
+
attr_reader :har_file
|
57
|
+
attr_accessor :entries,:hosts,:har,:har_hosts
|
58
|
+
|
59
|
+
def initialize har_file, options, ignores
|
60
|
+
@har_file = har_file
|
61
|
+
@hosts = {}
|
62
|
+
@options = options
|
63
|
+
@ignores = ignores
|
64
|
+
begin
|
65
|
+
@har = JSON File.read(@har_file)
|
66
|
+
rescue Exception=>e
|
67
|
+
raise e
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
# Return a list of entries from the har file
|
73
|
+
def get_entries
|
74
|
+
@entries = @har['log']['entries']
|
75
|
+
end
|
76
|
+
|
77
|
+
def generate ios
|
78
|
+
# Attempt to create the scenario
|
79
|
+
generated = MuSL::Maker.create do |scenario|
|
80
|
+
# First declare all the hosts
|
81
|
+
scenario.hosts do |host|
|
82
|
+
self.build_hosts host
|
83
|
+
end
|
84
|
+
# Then build all the steps
|
85
|
+
scenario.steps do |step|
|
86
|
+
self.build_steps step, scenario
|
87
|
+
end # scenario.steps
|
88
|
+
end # Maker.create
|
89
|
+
end
|
90
|
+
|
91
|
+
# Return a list of hosts from the har file
|
92
|
+
def get_hosts ignores, options
|
93
|
+
|
94
|
+
# Regex to check if certain kinds of entries should be ignored, like css, js, images based on command line options
|
95
|
+
ignores_regex = ignores.join('|')
|
96
|
+
|
97
|
+
# Iterate through each har entry and parse out the important url pieces
|
98
|
+
begin
|
99
|
+
hosts_seen = Hash.new(0)
|
100
|
+
host_count = 0
|
101
|
+
@har["log"]["entries"].each_with_index do |entry, index|
|
102
|
+
# Check our command line exclusions
|
103
|
+
if(options.ignore)
|
104
|
+
next if (entry['response']['content']['mimeType'] =~ /#{ignores_regex}/o )
|
105
|
+
end
|
106
|
+
|
107
|
+
# Build the hosts list
|
108
|
+
host = nil
|
109
|
+
m = entry["musl"] = {}
|
110
|
+
m["url_object"] = URI.parse entry["request"]["url"]
|
111
|
+
m["url"] = {}
|
112
|
+
m['url']['port'] = m["url_object"].port || (m["url_object"].scheme === 'http' ? 80 : 443)
|
113
|
+
m['url']['pathname'] = m['url_object'].path || '/'
|
114
|
+
m["url"]["search"] = m["url_object"].query || ''
|
115
|
+
m["url"]["hash"] = m["url_object"].fragment || ''
|
116
|
+
|
117
|
+
# Create the host entry hash with underscores instead of dots for the host value
|
118
|
+
entry['request']['headers'].each do |header|
|
119
|
+
if (!host && header['name'].downcase === 'host')
|
120
|
+
host = header['value']
|
121
|
+
# Set the hosts_seen value to host_count and increment the host_count unless we have already seen the host
|
122
|
+
hosts_seen[host] += host_count && host_count += 1 unless hosts_seen.has_key?(host)
|
123
|
+
|
124
|
+
# OLD WAY - Substitute the dots with underscores for the musl host
|
125
|
+
# OLD WAY entry['musl'][host] = host.gsub(/[^a-zA-Z0-9_]/, '_')
|
126
|
+
entry['musl'][host] = hosts_seen[host].to_s
|
127
|
+
|
128
|
+
if entry['musl'][host].match /^[0-9]/
|
129
|
+
entry['musl'][host] = 'host_' + entry['musl'][host]
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
@hosts[host] = entry['musl'][host]
|
136
|
+
entry['musl']['host'] = entry['musl'][host]
|
137
|
+
|
138
|
+
host = nil
|
139
|
+
|
140
|
+
if entry['response']['cookies']
|
141
|
+
#p "Entry-response-cookies: #{entry['response']['cookies']}"
|
142
|
+
entry['response']['cookies'].each do |cookie|
|
143
|
+
# TODO: Need to mark the index of this entry against
|
144
|
+
# this cookie so we can search for it easily.
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
rescue Exception => e
|
151
|
+
puts e.message
|
152
|
+
puts e.backtrace.inspect
|
153
|
+
raise e
|
154
|
+
end
|
155
|
+
|
156
|
+
return @hosts
|
157
|
+
end # get_hosts
|
158
|
+
|
159
|
+
# Finds a specific cookie from the response cookies
|
160
|
+
def find_cookie name, step
|
161
|
+
if step === 0
|
162
|
+
return
|
163
|
+
end
|
164
|
+
|
165
|
+
(step-1).downto(0) {|i|
|
166
|
+
entry = @entries[i]
|
167
|
+
res = entry['response']
|
168
|
+
if (res['cookies'] && res['cookies'].length > 0)
|
169
|
+
for j in 0..res['cookies'].length do
|
170
|
+
cookie = res['cookies'][j]
|
171
|
+
unless cookie.nil?
|
172
|
+
if cookie['name'] === name
|
173
|
+
return i
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
}
|
179
|
+
|
180
|
+
return
|
181
|
+
end # find_cookie
|
182
|
+
|
183
|
+
# Takes the headers
|
184
|
+
def build_headers cs, headers, cookies, i
|
185
|
+
headers.each do |header|
|
186
|
+
# If this is a Cookie header, try and map the cookie
|
187
|
+
# value to a step variable preceding this step
|
188
|
+
if ('cookie' === header['name'].downcase)
|
189
|
+
#value = header['value']
|
190
|
+
value = XMLizable.escape(header['value'], MSL_ESCAPES)
|
191
|
+
for j in 0..cookies.length
|
192
|
+
cookie = cookies[j]
|
193
|
+
|
194
|
+
if defined? cookie['name']
|
195
|
+
step = self.find_cookie cookie['name'], i
|
196
|
+
# THE FOLLOWING LOGIC PROBABLY NEEDS FIXING FOR COOKIES
|
197
|
+
if (step != nil)
|
198
|
+
regex = /"(#{cookie['name']})" + "=([^;]*)"/
|
199
|
+
if value.match regex
|
200
|
+
raise "I KNOW THERE IS A PROBLE WITH THE code below"
|
201
|
+
# TODO: FIX THIS LOGIC
|
202
|
+
value = value.replace(regex, cookie['name'] + '=#{@cr' + step + '.' + cookie['name'].gsub(/[^a-zA-Z0-9_]/, '_') + '}')
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
cs.line(header['name'] + ': ' + value)
|
208
|
+
else
|
209
|
+
cs.line(header['name'] + ': ' + header['value'])
|
210
|
+
end
|
211
|
+
|
212
|
+
end #end headers.each
|
213
|
+
end # end build_headers
|
214
|
+
|
215
|
+
|
216
|
+
def build_postdata cs, req, entry_count, scenario
|
217
|
+
|
218
|
+
if req['postData']['params'].length > 0
|
219
|
+
cs.header('Content-Length') do
|
220
|
+
cs.length_string({'of' => "body_#{entry_count}"})
|
221
|
+
end
|
222
|
+
cs.line()
|
223
|
+
|
224
|
+
cs.block('body_' + "#{entry_count}" + ' = struct [', ']') do
|
225
|
+
#assert_equal(req['postData']['mimeType'], 'application/x-www-form-urlencoded', 'unsupported mime type')
|
226
|
+
cs.block('dsv(delimiter: "&") [',']') do
|
227
|
+
req['postData']['params'].each do |param|
|
228
|
+
cs.block('struct [',']') do
|
229
|
+
cs.string(param['name'] + '=')
|
230
|
+
cs.block('uri_percent_encode [',']') do
|
231
|
+
cs.string param['value']
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
else
|
238
|
+
unless(@options.ignore_payload)
|
239
|
+
cs.line()
|
240
|
+
self.build_payload(cs, entry_count, req, scenario)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end # end build_postdata
|
244
|
+
|
245
|
+
def build_hosts hosts
|
246
|
+
# TODO: Right now v4 is hard-coded, need to change this
|
247
|
+
hosts.create 'host_0', 'v4', 'browser'
|
248
|
+
|
249
|
+
har_hosts = self.get_hosts @ignores, @options
|
250
|
+
|
251
|
+
# Build the host entries into the scenario
|
252
|
+
har_hosts.each do |hhost,har_host_value|
|
253
|
+
hosts.create(har_hosts[hhost], 'v4', hhost)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def build_steps steps, scenario
|
258
|
+
# Create the steps
|
259
|
+
entries = self.get_entries
|
260
|
+
entry_count = 0
|
261
|
+
for i in 0..entries.length
|
262
|
+
entry = entries[i]
|
263
|
+
next unless defined? entry['musl']
|
264
|
+
|
265
|
+
m = entry['musl']
|
266
|
+
req = entry['request']
|
267
|
+
res = entry['response']
|
268
|
+
next if m.nil?
|
269
|
+
|
270
|
+
xopts = { 'src' => '&host_0', 'dst' => '&' + "#{m['host']}" }
|
271
|
+
xklass = m['url_object'].scheme === 'http' ? 'tcp' : 'ssl'
|
272
|
+
xopts['dst_port'] = m['url']['port']
|
273
|
+
# Create the xport options in the scenario
|
274
|
+
steps.xport("xport#{entry_count}", xklass, xopts)
|
275
|
+
|
276
|
+
# client_send comments for each entry
|
277
|
+
steps.comment(req["method"] + ' ' + m['url']['pathname'] + ' ' + req['httpVersion'])
|
278
|
+
|
279
|
+
# main client_send lines for each entry
|
280
|
+
steps.client_send("cs#{entry_count}","xport#{entry_count}") do |cs|
|
281
|
+
cs.line(req['method'] + ' ' + m['url']['pathname'] + m['url']['search'] + m['url']['hash'] + ' ' + req['httpVersion'])
|
282
|
+
# For each header
|
283
|
+
self.build_headers cs, req['headers'], req['cookies'], i
|
284
|
+
|
285
|
+
# For building the form post params block
|
286
|
+
if req.has_key?('postData')
|
287
|
+
self.build_postdata cs, req, entry_count, scenario
|
288
|
+
else
|
289
|
+
cs.line('Content-Length: 0')
|
290
|
+
end # end if req.has_key('postData')
|
291
|
+
cs.line()
|
292
|
+
|
293
|
+
# For building the client content payload block into the scenario
|
294
|
+
if req.has_key?('content')
|
295
|
+
self.build_payload(cs, entry_count, req, scenario)
|
296
|
+
end unless @options.ignore_payload
|
297
|
+
|
298
|
+
end # end steps.client_send
|
299
|
+
|
300
|
+
# Skip the server side if the command line option included --endpint
|
301
|
+
unless @options.endpoint
|
302
|
+
steps.server_receive("sr#{entry_count}", "cs#{entry_count}") do ||
|
303
|
+
#return nil
|
304
|
+
end
|
305
|
+
|
306
|
+
# For adding comment headers for the server side
|
307
|
+
steps.comment(res['httpVersion'] + ' ' + "#{res['status']}" + ' ' + res['statusText'])
|
308
|
+
|
309
|
+
# Build server_send portion
|
310
|
+
steps.server_send("ss#{entry_count}", "xport#{entry_count}") do |ss|
|
311
|
+
ss.line(res['httpVersion'] + ' ' + "#{res['status']}" + ' ' + res['statusText'])
|
312
|
+
res['headers'].each do |header|
|
313
|
+
ss.line(header['name'] + ': ' + header['value'])
|
314
|
+
end
|
315
|
+
ss.line()
|
316
|
+
|
317
|
+
# For building the server content payload block into the scenario
|
318
|
+
if res.has_key?('content')
|
319
|
+
build_payload ss, entry_count, res, scenario
|
320
|
+
end unless @options.ignore_payload
|
321
|
+
end # end server_send
|
322
|
+
end
|
323
|
+
|
324
|
+
# If the endpoint option is chosen only include cs 'client send' values instead of ss 'server send' values
|
325
|
+
receive_side = @options.endpoint ? 'cs' : 'ss'
|
326
|
+
|
327
|
+
# Build client_receive portion
|
328
|
+
steps.client_receive("cr#{entry_count}", "#{receive_side}#{entry_count}") do |cr|
|
329
|
+
cr.assertions do |as|
|
330
|
+
as.create('/^HTTP\\/1\\.1 (\\d+)/ == ' + "#{res['status']}")
|
331
|
+
end
|
332
|
+
|
333
|
+
if (res['cookies'] && res['cookies'].length > 0)
|
334
|
+
cr.variables do |vs|
|
335
|
+
res['cookies'].each do |cookie|
|
336
|
+
vs.create('@' + cookie['name'].gsub(/[^a-zA-Z0-9_]/, '_') + ' = ' + '/' + cookie['name'] + '=([^;]*)' + '/:1')
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
entry_count += 1
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# If the payload is to be included, handle the inclusion logic here
|
346
|
+
def build_payload(cs_send, entry_count, req_res, scenario)
|
347
|
+
# If it is coming from the client side it will be part of the postData
|
348
|
+
payload = req_res.has_key?('content') ? req_res['content']['text'] : req_res['postData']['text']
|
349
|
+
#raise "req_res #{req_res} PAYLOAD #{payload}"
|
350
|
+
return if payload.nil?
|
351
|
+
|
352
|
+
content_encoding = false
|
353
|
+
transfer_encoding = false
|
354
|
+
|
355
|
+
# In case we need encoding types different than gzip, and chunked later on
|
356
|
+
req_res['headers'].each do |header|
|
357
|
+
if(header['name'] === 'Transfer-Encoding')
|
358
|
+
# We can do chunked
|
359
|
+
if header['value'] == HTTP_CHUNKED_TRANSFER_ENCODING
|
360
|
+
transfer_encoding = true
|
361
|
+
else
|
362
|
+
raise NotImplementedError, "Transfer-Encoding: #{header['value']}"
|
363
|
+
end
|
364
|
+
|
365
|
+
elsif(header['name'] === 'Content-Encoding')
|
366
|
+
# Check out content encoding
|
367
|
+
# We can do GZIP
|
368
|
+
if header['value'] == HTTP_GZIP_CONTENT_ENCODING
|
369
|
+
content_encoding = true
|
370
|
+
else
|
371
|
+
raise NotImplementedError, "Content-Encoding: #{header['value']}"
|
372
|
+
end
|
373
|
+
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
# http chunked
|
378
|
+
if transfer_encoding
|
379
|
+
cs_send.literal_no_format("http_chunk_encode(chunk_size: #{req_res['content']['size']}) [")
|
380
|
+
end
|
381
|
+
|
382
|
+
# gzip only for now
|
383
|
+
if content_encoding
|
384
|
+
cs_send.literal_no_format("gzip_compress[")
|
385
|
+
end
|
386
|
+
|
387
|
+
# If we need to replace the content with a special repeated field
|
388
|
+
if @options.strip_large_content
|
389
|
+
body = []
|
390
|
+
|
391
|
+
if req_res['content']['size'] > @options.large_content_size
|
392
|
+
# Take first 1K bytes from the content and repeat
|
393
|
+
body << payload[0, HTTP_CONTENT_SLICE_SIZE]
|
394
|
+
else
|
395
|
+
# Set response body
|
396
|
+
body << payload
|
397
|
+
end
|
398
|
+
|
399
|
+
# Yes calculate the number of 1K chunks we need to inject
|
400
|
+
count = req_res['content']['size'] / HTTP_CONTENT_SLICE_SIZE
|
401
|
+
remainder = req_res['content']['size'] % HTTP_CONTENT_SLICE_SIZE
|
402
|
+
|
403
|
+
# TODO: If there is a reminder add one more repeat count for now
|
404
|
+
if remainder > 0
|
405
|
+
count += 1
|
406
|
+
end
|
407
|
+
|
408
|
+
# Open the field
|
409
|
+
cs_send.literal_no_format("repeat(count: %d) [" % [count])
|
410
|
+
# No, write the content as binary string
|
411
|
+
cs_send.literal_no_format("\"0h")
|
412
|
+
|
413
|
+
# Write all blocks
|
414
|
+
body.each do |block|
|
415
|
+
# Write each byte in the block
|
416
|
+
block.each_byte do |byte|
|
417
|
+
cs_send.literal_no_format("%02x" % byte)
|
418
|
+
end
|
419
|
+
end
|
420
|
+
cs_send.literal_no_format("\"");
|
421
|
+
else
|
422
|
+
cs_send.literal_no_format("\"" + XMLizable.escape(payload, MSL_ESCAPES) + "\"")
|
423
|
+
end
|
424
|
+
|
425
|
+
|
426
|
+
# Close repeat field
|
427
|
+
if @options.strip_large_content
|
428
|
+
cs_send.literal_no_format("]")
|
429
|
+
end
|
430
|
+
|
431
|
+
# Close content encoding, if needed
|
432
|
+
if content_encoding
|
433
|
+
cs_send.literal_no_format("]")
|
434
|
+
end
|
435
|
+
|
436
|
+
# Close transfer encoding, if needed
|
437
|
+
if transfer_encoding
|
438
|
+
cs_send.literal_no_format("]")
|
439
|
+
end
|
440
|
+
|
441
|
+
end # build_payload()
|
442
|
+
|
443
|
+
# TO-DO: Implement this check
|
444
|
+
def text_body?
|
445
|
+
HTTP_TEXT_CONTENT_TYPES.include?(@content_type) and
|
446
|
+
not @content_transfer_encoding.to_s == 'binary' and
|
447
|
+
not @mu_content_transfer_encoding.to_s == 'binary'
|
448
|
+
end
|
449
|
+
end
|