Module: PluginTool
- Defined in:
- lib/auth.rb,
lib/misc.rb,
lib/check.rb,
lib/custom.rb,
lib/plugin.rb,
lib/server.rb,
lib/packing.rb,
lib/manifest.rb,
lib/minimise.rb,
lib/watchers.rb,
lib/debuggers.rb,
lib/new_plugin.rb,
lib/notifications.rb,
lib/syntax_checking.rb,
lib/i18n_extract_text.rb,
lib/schema_requirements.rb
Defined Under Namespace
Classes: CustomBehaviour, Minimiser, Plugin, ProfilerFormatter, ProfilerFormatterRaw, ProfilerFormatterTree, SchemaRequirements, WatcherPoll
Constant Summary collapse
- LOCAL_CUSTOM_BEHAVIOUR_FILENAME =
"server.behaviour.rb"- TRUSTED_CODE_DIGESTS_FILENAME =
Digests of trusted code are stored outside the source code repo, so it can’t be written by the repo contents
"~/.haplo-plugin-tool-trusted.json"- PACKING_EXCLUDE =
['developer.json', 'readme.txt']
- PLUGIN_ACCEPTABLE_FILENAME =
/\A(js|static|template|test|file|i18n)\/([A-Za-z0-9_\.-]+\/)*[A-Za-z0-9_\.-]+\.[A-Za-z0-9]+\z/- PLUGIN_ACCEPTABLE_FILENAME_EXCEPTIONS =
['plugin.json', 'requirements.schema', 'global.js', 'certificates-temp-http-api.pem', 'developer.json', 'readme.txt']
- SYNTAX_CHECK_REGEXP =
/\.(js|hsvt|json)\z/i- Context =
Java::OrgMozillaJavascript::Context
- PARSER_CONFIG =
TemplateParserConfiguration.new
- @@keys_pathname =
"#{Dir.getwd}/.server.json"- @@custom =
CustomBehaviour.new
- @@http =
nil- @@profile_output =
nil- @@notification_options =
nil- @@notification_have_suppressed_first_system_audit_entry =
false- @@notification_announce_reconnect =
false- @@syntax_check_queue =
[]
- @@syntax_check_queue_lock =
Mutex.new
- @@syntax_check_queue_semaphore =
Mutex.new
- @@syntax_check_queue_semaphore_var =
ConditionVariable.new
Class Method Summary collapse
- .beep ⇒ Object
- .check_file_result(plugin, name, result) ⇒ Object
- .check_for_certificate_file ⇒ Object
- .check_plugin(plugin, max_plugin_name_length) ⇒ Object
- .check_plugins(plugins) ⇒ Object
-
.cmd_auth(options) ⇒ Object
———————————————————————————————————.
-
.cmd_server(options) ⇒ Object
———————————————————————————————————.
- .custom_behaviour ⇒ Object
- .decode_and_handle_notifications(encoded) ⇒ Object
- .determine_manifest_changes(from, to) ⇒ Object
- .do_notifications ⇒ Object
- .do_syntax_checking ⇒ Object
- .finish_with_connection ⇒ Object
- .generate_manifest(directory) ⇒ Object
- .get(path) ⇒ Object
- .get_application_info ⇒ Object
- .get_http ⇒ Object
- .get_server_hostname ⇒ Object
- .get_with_json_response(path) ⇒ Object
- .handle_notification(type, data) ⇒ Object
- .i18n_extract_text(plugins) ⇒ Object
- .init_syntax_checking ⇒ Object
-
.load_keys_file ⇒ Object
———————————————————————————————————.
- .make_http_connection ⇒ Object
- .make_new_plugin(plugin_name) ⇒ Object
- .make_watcher(dirs) ⇒ Object
- .pack_plugin(plugin, output_directory, restrict_to_app_id, errors = []) ⇒ Object
-
.parse_hostname_with_port(hostname_with_port) ⇒ Object
———————————————————————————————————.
- .plugin_filename_allowed?(filename) ⇒ Boolean
- .post(path, params = nil, files = nil) ⇒ Object
- .post_with_json_response(path, params = nil, files = nil) ⇒ Object
- .profiler_handle_report(report) ⇒ Object
- .report_errors_from_server(r) ⇒ Object
-
.request_coverage(options) ⇒ Object
————————————————————————-.
- .request_profile(options) ⇒ Object
- .save_keys_file(keys) ⇒ Object
- .select_server(keys, substring) ⇒ Object
- .set_custom_behaviour(custom) ⇒ Object
- .set_server(hostname, port, key) ⇒ Object
-
.setup_auth(options) ⇒ Object
———————————————————————————————————.
- .setup_request(req) ⇒ Object
- .start_notifications(options) ⇒ Object
- .start_syntax_check ⇒ Object
- .syntax_check(plugin, filename) ⇒ Object
- .syntax_check_haplo_template(plugin, file) ⇒ Object
- .syntax_check_json_file(plugin, file) ⇒ Object
- .syntax_check_one_file(plugin, file) ⇒ Object
- .try_load_custom ⇒ Object
Class Method Details
.beep ⇒ Object
10 11 12 |
# File 'lib/misc.rb', line 10 def self.beep $stdout.write("\07") end |
.check_file_result(plugin, name, result) ⇒ Object
58 59 60 61 62 63 64 |
# File 'lib/check.rb', line 58 def self.check_file_result(plugin, name, result) unless result == :OK @@check_report << " #{plugin.plugin_dir}/#{name}: #{result}\n" end @@check_ok = false if result == :FAIL @@check_warn = true if result == :WARN end |
.check_for_certificate_file ⇒ Object
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/server.rb', line 21 def self.check_for_certificate_file @@server_ca = nil hostname_parts = (@@server_hostname || '').split('.') search_list = (0..hostname_parts.length).map do |n| filename = "server" search_parts = hostname_parts[n..hostname_parts.length] filename << '.' unless search_parts.empty? filename << search_parts.join('.') filename << ".crt" end @@server_ca = search_list.find { |f| File.file?(f) } if @@server_ca puts "NOTICE: Using alternative CAs for SSL from #{@@server_ca}" else # Use build in certificate bundle @@server_ca = "#{File.dirname(__FILE__)}/CertificateBundle.pem" end end |
.check_plugin(plugin, max_plugin_name_length) ⇒ Object
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/check.rb', line 33 def self.check_plugin(plugin, max_plugin_name_length) plugin_dir = plugin.plugin_dir STDOUT.write(sprintf("%#{max_plugin_name_length.to_i}s ", plugin.name)) Dir.glob("#{plugin_dir}/**/*").each do |pathname| next unless File.file?(pathname) next if plugin.exclude_files_from_syntax_check.include?(pathname) plugin_relative_name = pathname[plugin_dir.length+1, pathname.length] if pathname =~ SYNTAX_CHECK_REGEXP STDOUT.write("."); STDOUT.flush # Check JavaScript report = syntax_check_one_file(plugin, plugin_relative_name) if report == nil check_file_result(plugin, plugin_relative_name, :OK) else puts "\n**** #{plugin_relative_name} has errors:\n#{report}\n" check_file_result(plugin, plugin_relative_name, (plugin_relative_name =~ /\A(js|template|file)\//) ? :FAIL : :WARN) end else # TODO: Checks for other file types, including the plugin.json check_file_result(plugin, plugin_relative_name, :OK) end end STDOUT.write("\n"); STDOUT.flush end |
.check_plugins(plugins) ⇒ Object
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/check.rb', line 10 def self.check_plugins(plugins) init_syntax_checking @@check_report = '' @@check_ok = true @@check_warn = false max_plugin_name_length = plugins.map { |p| p.name.length } .max plugins.each { |p| check_plugin(p, max_plugin_name_length) } if @@check_warn || !(@@check_ok) puts "\nFILES WITH ERRORS" puts @@check_report end # Output the verdict puts if @@check_ok && !@@check_warn puts "PASSED" elsif @@check_warn puts "PASSED WITH WARNINGS" else puts "FAILED" exit 1 end end |
.cmd_auth(options) ⇒ Object
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/auth.rb', line 45 def self.cmd_auth() end_on_error "No hostname given on command line" if .args.empty? hostname, port, url_base, server_name = parse_hostname_with_port(.args.first) keys = load_keys_file() if keys['keys'].has_key?(server_name) && !.force puts puts "Already authorised with #{server_name}" puts "Use the --force argument to reauthorise with the server." return end set_server(hostname, port, nil) check_for_certificate_file() http = get_http() this_hostname = java.net.InetAddress.getLocalHost().getHostName() || "unknown" puts "Requesting token from #{url_base} ..." start_auth_path = "/api/plugin-tool-auth/start-auth?name=#{URI.encode(this_hostname)}" request = Net::HTTP::Get.new(start_auth_path) setup_request(request) token = nil begin response = http.request(request) end_on_error "Server returned an error. Check hostname and port." unless response.code == "200" parsed_json = JSON.parse(response.body) token = parsed_json['token'] end_on_error "Server doesn't look like a Haplo server with plugin debugging enabled" unless ((parsed_json['Haplo'] == 'plugin-tool-auth') || (parsed_json['ONEIS'] == 'plugin-tool-auth')) && token rescue => e end_on_error "Failed to start authorisation process. Check hostname and port." end # Check token looks OK so we don't form dodgy URLs end_on_error "Bad token" unless token =~ /\A[a-z0-9A-Z_-]+\z/ user_url = "#{url_base}/do/plugin-tool-auth/create/#{token}" poll_path = "/api/plugin-tool-auth/poll/#{token}" puts if java.lang.System.getProperty("os.name") == 'Mac OS X' puts "Attempting to open the following URL in your browser." puts "If the browser does not open, please visit this URL in your browser." system "open #{user_url}" else puts "Please visit this URL in your browser, and authenticate if necessary." end puts " #{user_url}" puts # Poll for a few minutes, waiting for the user to authenticate puts "Waiting for server to authorise..." poll_count = 0 key = nil while poll_count < 60 && !key delay = if poll_count < 10 2 elsif poll_count < 20 4 else 8 end sleep delay begin request = Net::HTTP::Get.new(poll_path) setup_request(request) response = http.request(request) parsed_json = JSON.parse(response.body) case parsed_json['status'] when 'wait' # poll again when 'available' key = parsed_json['key'] else end_on_error "Authorisation process failed." end rescue => e end_on_error "Error communicating with server" end end finish_with_connection() end_on_error "Didn't managed to authorise with server." unless key puts "Successfully authorised with server." keys['default'] = server_name keys['keys'][server_name] = key save_keys_file(keys) puts puts "Key stored in #{@@keys_pathname}" puts "#{server_name} selected as default server." end |
.cmd_server(options) ⇒ Object
33 34 35 36 37 38 39 40 41 |
# File 'lib/auth.rb', line 33 def self.cmd_server() end_on_error "No server name substring given on command line" if .args.empty? keys = load_keys_file() server = select_server(keys, .args.first) end_on_error "No server found for substring '#{.args.first}" unless server keys['default'] = server puts "Selected server #{server}" save_keys_file(keys) end |
.custom_behaviour ⇒ Object
28 29 30 |
# File 'lib/custom.rb', line 28 def self.custom_behaviour @@custom end |
.decode_and_handle_notifications(encoded) ⇒ Object
55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/notifications.rb', line 55 def self.decode_and_handle_notifications(encoded) size = encoded.length pos = 0 while pos < (size - 12) type = encoded[pos, 4] data_size = encoded[pos + 4, 8].to_i(16) data = encoded[pos + 12, data_size] pos += 12 + data_size handle_notification(type, data) end end |
.determine_manifest_changes(from, to) ⇒ Object
37 38 39 40 41 42 43 44 45 46 |
# File 'lib/manifest.rb', line 37 def self.determine_manifest_changes(from, to) changes = [] from.each_key do |name| changes << [name, :delete] unless to.has_key?(name) end to.each do |name,hash| changes << [name, hash] unless from[name] == hash end changes end |
.do_notifications ⇒ Object
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/notifications.rb', line 30 def self.do_notifications http = make_http_connection puts "NOTICE: Notification connection established." if @@notification_announce_reconnect while true sleep(0.25) # small throttle of requests path = '/api/development-plugin-loader/get-notifications' path << "?queue=#{@@notification_queue_name}" if @@notification_queue_name request = Net::HTTP::Get.new(path) setup_request(request) # Server uses long-polling, and will respond after a long timeout, or when a notification # has been queued for sending to this process. response = http.request(request) if response.kind_of?(Net::HTTPOK) @@notification_queue_name = response['X-Queue-Name'] begin decode_and_handle_notifications response.body rescue puts "NOTICE: Error handling notification from server." end else raise "Bad response" end end end |
.do_syntax_checking ⇒ Object
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/syntax_checking.rb', line 119 def self.do_syntax_checking init_syntax_checking while(true) plugin, file = @@syntax_check_queue_lock.synchronize do @@syntax_check_queue.shift end if file == nil # Wait for another file @@syntax_check_queue_semaphore.synchronize { @@syntax_check_queue_semaphore_var.wait(@@syntax_check_queue_semaphore) } else report = syntax_check_one_file(plugin, file) if report != nil puts "\n#{plugin.plugin_dir}/#{file} has syntax errors:\n#{report}\n\n" beep end end end end |
.finish_with_connection ⇒ Object
64 65 66 67 68 69 |
# File 'lib/server.rb', line 64 def self.finish_with_connection if @@http != nil @@http.finish @@http = nil end end |
.generate_manifest(directory) ⇒ Object
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/manifest.rb', line 16 def self.generate_manifest(directory) manifest = Hash.new Dir.glob("#{directory}/**/*").sort.each do |pathname| # Ignore directories next unless File.file? pathname # Ignore temporary files next if pathname =~ /\~/ # Check file filename = pathname.slice(directory.length + 1, pathname.length) unless plugin_filename_allowed?(filename) puts "WARNING: Ignoring #{filename}" next end # Get hash of file digest = File.open(pathname, "rb") { |f| Digest::SHA256.hexdigest(f.read) } # And add to manifest manifest[filename] = digest end manifest end |
.get(path) ⇒ Object
76 77 78 79 80 81 82 83 |
# File 'lib/server.rb', line 76 def self.get(path) http = get_http request = Net::HTTP::Get.new(path) setup_request(request) @@request_mutex.synchronize do http.request(request).body end end |
.get_application_info ⇒ Object
151 152 153 |
# File 'lib/server.rb', line 151 def self.get_application_info @@server_application_info ||= JSON.parse(get("/api/development-plugin-loader/application-info")) end |
.get_http ⇒ Object
60 61 62 |
# File 'lib/server.rb', line 60 def self.get_http @@http ||= make_http_connection end |
.get_server_hostname ⇒ Object
17 18 19 |
# File 'lib/server.rb', line 17 def self.get_server_hostname @@server_hostname end |
.get_with_json_response(path) ⇒ Object
85 86 87 |
# File 'lib/server.rb', line 85 def self.get_with_json_response(path) report_errors_from_server(JSON.parse(get(path))) end |
.handle_notification(type, data) ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/notifications.rb', line 67 def self.handle_notification(type, data) case type when 'log ' # Output from console.log() puts "LOG:#{data}" DebugAdapterProtocolTunnel.(data) when 'DAP1' DebugAdapterProtocolTunnel.(data) when 'prof' # Profiler report PluginTool.profiler_handle_report(data) when 'audt' decoded = JSON.parse(data) kind = decoded.find { |name,value| name == 'auditEntryType' }.last if kind =~ /\A[A-Z\-]+\z/ # System audit entry - suppressed by default unless @@notification_options.show_system_audit unless @@notification_have_suppressed_first_system_audit_entry @@notification_have_suppressed_first_system_audit_entry = true puts "NOTICE: System audit trail entries are not being shown. Run with --show-system-audit to display." end return end end puts "AUDIT -------------------------------------------" decoded.each do |key, value| puts sprintf("%22s: %s", key, value.to_s) end else puts "WARNING: Unknown notification received from server. Upgrade the plugin tool using 'jgem update haplo'." sleep(5) # throttle problematic responses end end |
.i18n_extract_text(plugins) ⇒ Object
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/i18n_extract_text.rb', line 4 def self.i18n_extract_text(plugins) text = {} # Get text from templates. This is exact, because each template is parsed. parser_config = TemplateParserConfiguration.new plugins.each do |plugin| Dir.glob("#{plugin.plugin_dir}/template/**/*.hsvt").sort.each do |template| template = Java::OrgHaploTemplateHtml::Parser.new(File.read(template), "extract", parser_config).parse() template.extractTranslatedStrings().each do |string| text[string] = string end end end # Get text from JS files, which isn't exact, because it just relies on convention and hopes for the best. plugins.each do |plugin| Dir.glob("#{plugin.plugin_dir}/js/**/*.js").sort.each do |js_file| js = File.read(js_file) [/\bi\['([^']+)'\]/, /\bi\["([^"]+)"\]/].each do |regexp| js.scan(regexp) do text[$1] = $1 end end end end # Last, add in any of the default locale's text, so where text is looked up by symbol, the translation is included. plugins.each do |plugin| ['global','local'].each do |scope| maybe_strings = "#{plugin.plugin_dir}/i18n/#{scope}/#{plugin.default_locale_id}.template.json" if File.exist?(maybe_strings) strings = JSON.parse(File.read(maybe_strings)) strings.each do |k,v| text[k] = v end end end end puts JSON.pretty_generate(text) end |
.init_syntax_checking ⇒ Object
30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/syntax_checking.rb', line 30 def self.init_syntax_checking # Set up interpreter and get a syntax checker function raise "Another JS Context is active" unless nil == Context.getCurrentContext() @@cx = Context.enter(); @@cx.setLanguageVersion(Context::VERSION_ES6) @@javascript_scope = @@cx.initStandardObjects(); jshint = File.open("#{File.dirname(__FILE__)}/jshint.js") { |f| f.read } @@cx.evaluateString(@@javascript_scope, jshint, "<jshint.js>", 1, nil); testerfn = File.open("#{File.dirname(__FILE__)}/js_syntax_test.js") { |f| f.read } @@cx.evaluateString(@@javascript_scope, testerfn, "<js_syntax_test.js>", 1, nil); @@syntax_tester = @@javascript_scope.get("syntax_tester", @@javascript_scope); end |
.load_keys_file ⇒ Object
155 156 157 158 159 160 161 |
# File 'lib/auth.rb', line 155 def self.load_keys_file if File.exist?(@@keys_pathname) File.open(@@keys_pathname) { |f| JSON.parse(f.read) } else {"_" => "Contains server keys. DO NOT COMMIT TO SOURCE CONTROL.", "default" => nil, "keys" => {}} end end |
.make_http_connection ⇒ Object
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/server.rb', line 40 def self.make_http_connection http = Net::HTTP.new(@@server_hostname, @@server_port) ssl_ca = OpenSSL::X509::Store.new unless ssl_ca.respond_to? :add_file puts puts "jruby-openssl gem is not installed or bouncy castle crypto not available on CLASSPATH." puts "See installation instructions." puts exit 1 end ssl_ca.add_file(@@server_ca) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_PEER http.cert_store = ssl_ca http.read_timeout = 3600 # 1 hour, in case a test runs for a very long time http.start http end |
.make_new_plugin(plugin_name) ⇒ Object
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/new_plugin.rb', line 10 def self.make_new_plugin(plugin_name) unless plugin_name =~ /\A[a-z0-9_]+\z/ && plugin_name.length > 8 end_on_error "Bad plugin name - must use a-z0-9_ only, and more than 8 characters." end if File.exist?(plugin_name) end_on_error "File or directory #{plugin_name} already exists" end FileUtils.mkdir(plugin_name) ['js', 'static', 'template', 'test', 'file/form'].each do |dir| FileUtils.mkdir_p("#{plugin_name}/#{dir}") end random = java.security.SecureRandom.new() rbytes = Java::byte[20].new random.nextBytes(rbytes) install_secret = String.from_java_bytes(rbytes).unpack('H*').join plugin_url_fragment = plugin_name.gsub('_','-') File.open("#{plugin_name}/plugin.json",'w') do |file| file.write(<<__E) { "pluginName": "#{plugin_name}", "pluginAuthor": "TODO Your Company", "pluginVersion": 1, "displayName": "#{plugin_name.split('_').map {|e| e.capitalize} .join(' ')}", "displayDescription": "TODO Longer description of plugin", "installSecret": "#{install_secret}", "apiVersion": 4, "load": [ "js/#{plugin_name}.js" ], "respond": ["/do/#{plugin_url_fragment}"] } __E end File.open("#{plugin_name}/js/#{plugin_name}.js",'w') do |file| file.write(<<__E) P.respond("GET", "/do/#{plugin_url_fragment}/example", [ ], function(E) { E.render({ // view goes here: http://docs.haplo.org/dev/plugin/request-handling }); }); __E end File.open("#{plugin_name}/template/example.hsvt",'w') do |file| file.write(<<__E) // HSVT documentation: http://docs.haplo.org/dev/plugin/templates pageTitle("Example page") <p class="example"> "This is an example template." </p> __E end File.open("#{plugin_name}/test/#{plugin_name}_test1.js",'w') do |file| file.write(<<__E) t.test(function() { // For documentation, see // http://docs.haplo.org/dev/plugin/tests t.assert(true); }); __E end File.open("#{plugin_name}/requirements.schema",'w') do |file| file.write("\n\n\n") end puts <<__E Plugin #{plugin_name} has been created. Run haplo-plugin -p #{plugin_name} to upload it to the server, then visit https://<HOSTNAME>/do/#{plugin_url_fragment}/example to see a sample page. See http://docs.haplo.org/dev/plugin for more information. __E end |
.make_watcher(dirs) ⇒ Object
38 39 40 41 42 43 |
# File 'lib/watchers.rb', line 38 def self.make_watcher(dirs) # TODO: Option to use external watcher task # pipe = IO.popen(watcher_cmd) # wait with pipe.read WatcherPoll.new(dirs) end |
.pack_plugin(plugin, output_directory, restrict_to_app_id, errors = []) ⇒ Object
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/packing.rb', line 12 def self.pack_plugin(plugin, output_directory, restrict_to_app_id, errors = []) STDOUT.write("#{plugin.name}: ") raise "Bad plugin name when packing" unless plugin.name =~ /\A[a-zA-Z0-9_]+\z/ # Get filenames and sort plugin_dir = plugin.plugin_dir files = Dir.glob("#{plugin_dir}/**/*").map do |filename| if File.file? filename filename[plugin_dir.length+1, filename.length] else nil end end .compact.select do |filename| ok = plugin_filename_allowed?(filename) && !PACKING_EXCLUDE.include?(filename) unless ok || PACKING_EXCLUDE.include?(filename) STDOUT.write("!") errors.push("IGNORED: #{plugin_dir}/#{filename}") end ok end .sort # Clean output directory output_plugin_dir = "#{output_directory}/#{restrict_to_app_id ? "#{restrict_to_app_id}." : ''}#{plugin.name}" if File.exist? output_plugin_dir FileUtils.rm_r(output_plugin_dir) end # Make file structure FileUtils.mkdir(output_plugin_dir) # Process each file, building a manifest manifest = '' minimiser = PluginTool::Minimiser.new files.each do |filename| STDOUT.write("."); STDOUT.flush mode = (filename =~ /\.(js|json|html|hsvt|css)\Z/i) ? "rb:UTF-8" : "rb" data = File.open("#{plugin_dir}/#{filename}", mode) { |f| f.read } # Rewrite plugin.json? if filename == 'plugin.json' && restrict_to_app_id plugin_json = JSON.parse(data) plugin_json['restrictToApplicationId'] = restrict_to_app_id data = JSON.pretty_generate(plugin_json) end # Minimise file? unless filename =~ /\A(js|test)\// # Is this file explicitly excluded from minimisation? unless plugin.exclude_files_from_minimisation.include?(filename) data = minimiser.process(data, filename) end end hash = Digest::SHA256.hexdigest(data) # Make sure output directory exists, write file output_pathname = "#{output_plugin_dir}/#{filename}" output_directory = File.dirname(output_pathname) FileUtils.mkdir_p(output_directory) unless File.directory?(output_directory) File.open(output_pathname, "w") { |f| f.write data } # Filename entry in Manifest manifest << "F #{hash} #{filename}\n" end STDOUT.write("\n") minimiser.finish # Write manifest and version File.open("#{output_plugin_dir}/manifest", "w") { |f| f.write manifest } version = Digest::SHA256.hexdigest(manifest) File.open("#{output_plugin_dir}/version", "w") { |f| f.write "#{version}\n" } end |
.parse_hostname_with_port(hostname_with_port) ⇒ Object
142 143 144 145 146 147 148 149 150 151 |
# File 'lib/auth.rb', line 142 def self.parse_hostname_with_port(hostname_with_port) hostname_with_port = hostname_with_port.downcase.strip unless hostname_with_port =~ /\A(https?:\/\/)?([a-z0-9\.-]+)(:(\d+))?/i end_on_error "Bad hostname #{hostname_with_port}" end hostname = $2 port = $4 ? $4.to_i : 443 server_name = "#{hostname}#{(port != 443) ? ":#{port}" : ''}" [hostname, port, "https://#{server_name}", server_name] end |
.plugin_filename_allowed?(filename) ⇒ Boolean
12 13 14 |
# File 'lib/manifest.rb', line 12 def self.plugin_filename_allowed?(filename) (filename =~ PLUGIN_ACCEPTABLE_FILENAME) || (PLUGIN_ACCEPTABLE_FILENAME_EXCEPTIONS.include?(filename)) end |
.post(path, params = nil, files = nil) ⇒ Object
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/server.rb', line 89 def self.post(path, params = nil, files = nil) http = get_http request = Net::HTTP::Post.new(path) setup_request(request) if files == nil request.set_form_data(params) if params != nil else boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1" request["Content-Type"] = "multipart/form-data; boundary=#{boundary}" body = '' if params != nil params.each do |key,value| body << <<-EOF --#{boundary}\r Content-Disposition: form-data; name="#{key}"\r \r #{value}\r EOF end end files.each do |key,info| pathname, data = info body << <<-EOF --#{boundary}\r Content-Disposition: form-data; name="#{key}"; filename="#{pathname}"\r Content-Type: application/octet-stream\r Content-Length: #{data.length}\r \r #{data}\r EOF end body << "--#{boundary}--\r" request.body = body end @@request_mutex.synchronize do http.request(request).body end end |
.post_with_json_response(path, params = nil, files = nil) ⇒ Object
128 129 130 |
# File 'lib/server.rb', line 128 def self.post_with_json_response(path, params = nil, files = nil) report_errors_from_server(JSON.parse(post(path, params, files))) end |
.profiler_handle_report(report) ⇒ Object
77 78 79 80 81 |
# File 'lib/debuggers.rb', line 77 def self.profiler_handle_report(report) if @@profile_output @@profile_output.format(report) end end |
.report_errors_from_server(r) ⇒ Object
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/server.rb', line 132 def self.report_errors_from_server(r) unless r.kind_of? Hash r = { "result" => 'error', "protocol_error" => true, "message" => "Unknown error. Either the server is not enabled for development, or the credentials in #{SERVER_INFO} are not valid." } end if r["result"] != 'success' && r.has_key?("message") puts "\n\n**************************************************************" puts " ERROR REPORTED BY SERVER" puts "**************************************************************\n\n" puts r["message"] puts "\n**************************************************************\n\n" beep end r end |
.request_coverage(options) ⇒ Object
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/debuggers.rb', line 85 def self.request_coverage() format = .coverage_format || 'raw' if format != 'raw' puts "Unknown coverage format: #{format}" exit 1 end if 'OK' == PluginTool.post("/api/development-plugin-loader/debugger-coverage-start") puts "Coverage capture started." else puts "Error starting coverage capture." exit 1 end at_exit do coverage = PluginTool.post("/api/development-plugin-loader/debugger-coverage-stop") # TODO: Check errors File.open(.coverage_file, "w") { |f| f.write coverage } end end |
.request_profile(options) ⇒ Object
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/debuggers.rb', line 12 def self.request_profile() output_file = $stdout close_output_file = false if .profile_file output_file = File.open(.profile_file, "a") close_output_file = true end formatter = nil case .profile_format || 'tree' when 'tree' @@profile_output = ProfilerFormatterTree.new(output_file) when 'raw' @@profile_output = ProfilerFormatterRaw.new(output_file) else puts "Unknown profiler format: #{.profile_format}" exit 1 end if 'OK' == PluginTool.post("/api/development-plugin-loader/debugger-profile-start", {:min => .profile}) puts "JavaScript profiler started." else puts "Error starting JavaScript profiler." exit 1 end at_exit do puts puts "Disabling JavaScript profiler..." PluginTool.post("/api/development-plugin-loader/debugger-profile-stop") puts "JavaScript profiler disabled." output_file.close if close_output_file end end |
.save_keys_file(keys) ⇒ Object
163 164 165 166 167 |
# File 'lib/auth.rb', line 163 def self.save_keys_file(keys) pn = "#{@@keys_pathname}.n" File.open(pn, "w") { |f| f.write(JSON.pretty_generate(keys)) } File.rename(pn, @@keys_pathname) end |
.select_server(keys, substring) ⇒ Object
169 170 171 172 |
# File 'lib/auth.rb', line 169 def self.select_server(keys, substring) s = substring.downcase.strip keys['keys'].keys.sort { |a,b| (a.length == b.length) ? (a <=> b) : (a.length <=> b.length) } .find { |a| a.include? s } end |
.set_custom_behaviour(custom) ⇒ Object
24 25 26 |
# File 'lib/custom.rb', line 24 def self.set_custom_behaviour(custom) @@custom = custom end |
.set_server(hostname, port, key) ⇒ Object
10 11 12 13 14 15 |
# File 'lib/server.rb', line 10 def self.set_server(hostname, port, key) @@server_hostname = hostname @@server_port = port @@server_key = key @@request_mutex = Mutex.new end |
.setup_auth(options) ⇒ Object
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/auth.rb', line 14 def self.setup_auth() keys = load_keys_file() server = keys['default'] if .server_substring server = select_server(keys, .server_substring) end_on_error "No server found for substring '#{.server_substring}'" unless server end key = keys['keys'][server] if key hostname, port, url_base = parse_hostname_with_port(server) set_server(hostname, port, key) puts "Application: #{url_base}" else end_on_error "No server authorised. Run haplo-plugin auth SERVER_NAME" end end |
.setup_request(req) ⇒ Object
71 72 73 74 |
# File 'lib/server.rb', line 71 def self.setup_request(req) req['User-Agent'] = 'plugin-tool' req['X-ONEIS-Key'] = @@server_key if @@server_key end |
.start_notifications(options) ⇒ Object
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# File 'lib/notifications.rb', line 14 def self.start_notifications() @@notification_options = @@notification_queue_name = nil Thread.new do while true begin self.do_notifications rescue => e puts "NOTICE: Lost notification connection, will attempt to reconnect soon." @@notification_announce_reconnect = true sleep(5) # throttle errors end end end end |
.start_syntax_check ⇒ Object
19 20 21 22 23 24 25 26 27 28 |
# File 'lib/syntax_checking.rb', line 19 def self.start_syntax_check Thread.new do begin do_syntax_checking rescue => e puts "EXCEPTION IN SYNTAX CHECKER\n#{e.inspect}" exit 1 end end end |
.syntax_check(plugin, filename) ⇒ Object
138 139 140 141 142 143 144 |
# File 'lib/syntax_checking.rb', line 138 def self.syntax_check(plugin, filename) @@syntax_check_queue_lock.synchronize do @@syntax_check_queue << [plugin, filename] @@syntax_check_queue.uniq! end @@syntax_check_queue_semaphore.synchronize { @@syntax_check_queue_semaphore_var.signal } end |
.syntax_check_haplo_template(plugin, file) ⇒ Object
100 101 102 103 104 105 106 107 |
# File 'lib/syntax_checking.rb', line 100 def self.syntax_check_haplo_template(plugin, file) begin Java::OrgHaploTemplateHtml::Parser.new(File.read("#{plugin.plugin_dir}/#{file}"), file, PARSER_CONFIG).parse() nil rescue => e " #{e.}" end end |
.syntax_check_json_file(plugin, file) ⇒ Object
110 111 112 113 114 115 116 117 |
# File 'lib/syntax_checking.rb', line 110 def self.syntax_check_json_file(plugin, file) begin JSON.parse(File.read("#{plugin.plugin_dir}/#{file}")) nil rescue => e " #{e.}" end end |
.syntax_check_one_file(plugin, file) ⇒ Object
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/syntax_checking.rb', line 43 def self.syntax_check_one_file(plugin, file) # Is this file excluded from syntax checking? return nil if plugin.exclude_files_from_syntax_check.include?(file) # Load the plugin.json file plugin_dir = plugin.plugin_dir plugin_json = File.open("#{plugin_dir}/plugin.json") { |f| JSON.parse(f.read) } api_version = (plugin_json["apiVersion"] || '0').to_i # Determine kind of file return syntax_check_haplo_template(plugin, file) if file =~ /\.hsvt\z/i return syntax_check_json_file(plugin, file) if file =~ /\.json\z/i kind = (file =~ /\A(\w+)\//) ? $1 : file # Is this file referenced? report = nil unless kind != 'js' || plugin_json['load'].include?(file) puts "\nWARNING: #{plugin_dir}/plugin.json doesn't mention #{file} in the load directive.\n" report = "Couldn't check file - not mentioned in plugin.json" else schema_requirements = (api_version < 4) ? nil : SchemaRequirements.new("#{plugin_dir}/requirements.schema") extra_globals = schema_requirements ? schema_requirements.locals.dup : [] # Load the file js = File.open("#{plugin_dir}/#{file}") { |f| f.read } if api_version < 3 # If it's not the first file, need to tell JSHint about the extra var unless plugin_json['load'].first == file extra_globals << plugin_json["pluginName"]; end else if kind == 'js' || kind == 'test' extra_globals << plugin_json["pluginName"] extra_globals << 'P' locals = plugin_json["locals"] if locals locals.each_key { |local| extra_globals << local } end end if kind == 'test' extra_globals << 't' end # global.js requires server side syntax checking, but doesn't have any extra globals, not even the plugin name. kind = 'js' if kind == 'global.js' end # Add extra symbols from developer.json? extra_globals.concat( case kind when 'js', 'test'; plugin.developer_json['syntaxCheckGlobalsServer'] when 'static'; plugin.developer_json['syntaxCheckGlobalsBrowser'] else; [] end || []) # Do syntax checking lint_report = @@syntax_tester.call(@@cx, @@javascript_scope, @@javascript_scope, [js, kind, JSON.generate(extra_globals)]) schema_report = schema_requirements ? schema_requirements.report_usage(js) : nil report = [lint_report, schema_report].compact.join("\n\n") report = nil if report.empty? end report end |
.try_load_custom ⇒ Object
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/custom.rb', line 32 def self.try_load_custom return unless File.exist?(LOCAL_CUSTOM_BEHAVIOUR_FILENAME) trusted_code = nil untrusted_code = File.open(LOCAL_CUSTOM_BEHAVIOUR_FILENAME) { |f| f.read } untrusted_code_digest = Digest::SHA256.hexdigest(untrusted_code) trusted_code_digests = {"trust" => []} trusted_code_filename = File.(TRUSTED_CODE_DIGESTS_FILENAME) if File.exist?(trusted_code_filename) trusted_code_digests = JSON.parse(File.open(trusted_code_filename) { |f| f.read }) end unless trusted_code_digests["trust"].include?(untrusted_code_digest) # Make sure the user wants to run this code. Otherwise running the plugin tool in a repo you've just # downloaded could unexpectedly execute code on your local machine. if ARGV.length == 2 && ARGV[0] == 'trust' && ARGV[1] =~ /\A[0-9a-z]{64}\z/ && ARGV[1] == untrusted_code_digest trusted_code_digests["trust"].push(untrusted_code_digest) File.open(trusted_code_filename,"w") { |f| f.write JSON.pretty_generate(trusted_code_digests) } puts "Stored trust for #{LOCAL_CUSTOM_BEHAVIOUR_FILENAME} with contents #{untrusted_code_digest}." exit 0 else puts puts "-------------------------------------------------------------------------------------------" puts " Do you trust the code in #{LOCAL_CUSTOM_BEHAVIOUR_FILENAME} to be run every time you run the" puts " plugin tool? If yes, run" puts " haplo-plugin trust #{untrusted_code_digest}" puts " to permanently trust this version of #{LOCAL_CUSTOM_BEHAVIOUR_FILENAME}" puts "-------------------------------------------------------------------------------------------" puts PluginTool.beep exit 1 end end if ARGV.length > 0 && ARGV[0] == "trust" puts "Unexpected trust command." exit 1 end # User trusts the code, run it # There is a race condition here, but we're trying to protect against code in repositories, not # against software running on the local machine. load LOCAL_CUSTOM_BEHAVIOUR_FILENAME end |