agentx 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/README +31 -0
- data/Rakefile +2 -0
- data/agentx.gemspec +30 -0
- data/bin/agentx +106 -0
- data/config.example +16 -0
- data/lib/agentx.rb +53 -0
- data/lib/agentx/cache.rb +124 -0
- data/lib/agentx/console.rb +31 -0
- data/lib/agentx/history.rb +49 -0
- data/lib/agentx/html.rb +82 -0
- data/lib/agentx/request.rb +288 -0
- data/lib/agentx/response.rb +261 -0
- data/lib/agentx/session.rb +94 -0
- data/lib/agentx/version.rb +5 -0
- data/lib/agentx/xml.rb +65 -0
- metadata +173 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7bbc2d4ab57f2dfc3344a0d733bcd9620c395a51
|
4
|
+
data.tar.gz: a8503dd2073f8e0412fa7206ce46a44a8f85537b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a9895c44cab27796c8b73116515af66b5eee193014d9521dfb2e20255de82800b6dda4975949a5e1ef85bdd379305165083b8d400e70d8bcbf01d817ab7ae32d
|
7
|
+
data.tar.gz: b6d57a674c4deec1450dc41d5a57de5db7b3079ff22abb86196b5f1960a402959ab2b244b09b8c27d39235b48c46801bcab0577aec6706e1bae506b32fc7f901
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/Gemfile
ADDED
data/README
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# AgentX
|
2
|
+
|
3
|
+
This is still under development. The idea is to be a programmer-friendly tool
|
4
|
+
for doing things on the internet.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'agentx'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install agentx
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
TODO: Write some documentation!
|
23
|
+
|
24
|
+
## Contributing
|
25
|
+
|
26
|
+
1. Fork it ( https://github.com/eki/agentx/fork )
|
27
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
28
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
29
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
30
|
+
5. Create a new Pull Request
|
31
|
+
|
data/Rakefile
ADDED
data/agentx.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'agentx/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "agentx"
|
8
|
+
spec.version = AgentX::VERSION
|
9
|
+
spec.authors = ["Eric K Idema"]
|
10
|
+
spec.email = ["eki@vying.org"]
|
11
|
+
spec.summary = %q{A tool for doing things on the Internet.}
|
12
|
+
spec.description = %q{A tool for doing things on the Internet.}
|
13
|
+
spec.homepage = "https://github.com/eki/agentx"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
21
|
+
spec.add_development_dependency "rake"
|
22
|
+
|
23
|
+
spec.add_dependency 'ethon'
|
24
|
+
spec.add_dependency 'http-cookie'
|
25
|
+
spec.add_dependency 'nokogiri'
|
26
|
+
spec.add_dependency 'listen'
|
27
|
+
spec.add_dependency 'oj'
|
28
|
+
spec.add_dependency 'sqlite3'
|
29
|
+
end
|
30
|
+
|
data/bin/agentx
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'agentx'
|
4
|
+
require 'agentx/console'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'listen'
|
7
|
+
require 'optparse'
|
8
|
+
|
9
|
+
options={}
|
10
|
+
|
11
|
+
option_parser = OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: agentx [OPTION] [FILE]"
|
13
|
+
|
14
|
+
opts.on('-h', '--help', 'This help message') do
|
15
|
+
puts opts
|
16
|
+
puts <<-eos
|
17
|
+
|
18
|
+
If no FILE is provided, an interactive session will be started. The
|
19
|
+
--interactive flag is only needed if you would like to execute a file and then
|
20
|
+
enter an interactive session.
|
21
|
+
|
22
|
+
All file arguments should be valid Ruby. Files will be executed in the order
|
23
|
+
provided at the command-line.
|
24
|
+
|
25
|
+
By default a config file is located at ~/.agentx/config and should be valid
|
26
|
+
Ruby. This config file will be executed before any FILE arguements. The
|
27
|
+
default config is an empty file.
|
28
|
+
|
29
|
+
eos
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on('-c', '--config [FILE]', 'Use given config file.') do |v|
|
34
|
+
options[:config] = v
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on('-i', '--interactive', 'Run an interactive console.') do
|
38
|
+
options[:interactive] = true
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on('-v', '--version', 'Print the version and exit.') do
|
42
|
+
puts "agentx version #{AgentX::VERSION}"
|
43
|
+
exit
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
option_parser.parse!
|
48
|
+
|
49
|
+
console = AgentX::Console.new(options)
|
50
|
+
console.load_config
|
51
|
+
|
52
|
+
files = ARGV.map { |f| File.expand_path(f) }.select { |f| File.exists?(f) }
|
53
|
+
|
54
|
+
if files.any?
|
55
|
+
files.each { |f| AgentX::Console.load(f) }
|
56
|
+
else
|
57
|
+
options[:interactive] = true
|
58
|
+
end
|
59
|
+
|
60
|
+
if options[:interactive]
|
61
|
+
config_dir = File.dirname(options[:config])
|
62
|
+
config_file = File.basename(options[:config])
|
63
|
+
|
64
|
+
listener = Listen.to(config_dir, only: /^#{config_file}$/) do |m,a,r|
|
65
|
+
console.load_config
|
66
|
+
end
|
67
|
+
|
68
|
+
listener.start
|
69
|
+
|
70
|
+
ARGV.clear
|
71
|
+
|
72
|
+
require 'irb'
|
73
|
+
|
74
|
+
IRB.init_config(nil)
|
75
|
+
|
76
|
+
IRB.conf[:AUTO_INDENT] = true
|
77
|
+
IRB.conf[:PROMPT_MODE] = :SIMPLE
|
78
|
+
|
79
|
+
IRB.conf[:SAVE_HISTORY] = 100
|
80
|
+
IRB.conf[:HISTORY_FILE] = "#{AgentX.root}/console.log"
|
81
|
+
IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new.context
|
82
|
+
|
83
|
+
IRB.load_modules
|
84
|
+
|
85
|
+
require 'irb/ext/save-history'
|
86
|
+
require 'irb/completion'
|
87
|
+
|
88
|
+
workspace = IRB::WorkSpace.new(console)
|
89
|
+
irb = IRB::Irb.new(workspace)
|
90
|
+
|
91
|
+
trap("SIGINT") do
|
92
|
+
catch(:IRB_EXIT) do
|
93
|
+
irb.signal_handle
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
begin
|
98
|
+
catch(:IRB_EXIT) do
|
99
|
+
irb.eval_input
|
100
|
+
end
|
101
|
+
ensure
|
102
|
+
IRB.irb_at_exit
|
103
|
+
listener.stop
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
data/config.example
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# AgentX Example Config
|
2
|
+
|
3
|
+
# This file is plain Ruby. Any methods you may define are available at the
|
4
|
+
# top-level of the console and are loaded before any file you may execute.
|
5
|
+
# Already defined:
|
6
|
+
#
|
7
|
+
# load_config - (Re)loads this config file, if it's been changed.
|
8
|
+
# options - The command-line options agentx was run with.
|
9
|
+
#
|
10
|
+
|
11
|
+
# Example of defining a session for quick use:
|
12
|
+
|
13
|
+
# def session
|
14
|
+
# @session ||= AgentX::Session.new('https://www.google.com')
|
15
|
+
# end
|
16
|
+
|
data/lib/agentx.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
require 'agentx/version'
|
3
|
+
|
4
|
+
require 'digest/md5'
|
5
|
+
require 'time'
|
6
|
+
require 'uri'
|
7
|
+
|
8
|
+
require 'ethon'
|
9
|
+
require 'http-cookie'
|
10
|
+
require 'nokogiri'
|
11
|
+
require 'oj'
|
12
|
+
require 'sqlite3'
|
13
|
+
|
14
|
+
require 'agentx/history'
|
15
|
+
require 'agentx/html'
|
16
|
+
require 'agentx/xml'
|
17
|
+
require 'agentx/request'
|
18
|
+
require 'agentx/response'
|
19
|
+
require 'agentx/cache'
|
20
|
+
require 'agentx/session'
|
21
|
+
|
22
|
+
module AgentX
|
23
|
+
def self.root
|
24
|
+
return @root if @root
|
25
|
+
|
26
|
+
@root = File.expand_path('~/.agentx')
|
27
|
+
|
28
|
+
Dir.mkdir(@root) unless Dir.exists?(@root)
|
29
|
+
|
30
|
+
@root
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.session
|
34
|
+
@session ||= Session.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.[](*args)
|
38
|
+
session[*args]
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.logger
|
42
|
+
return @logger if @logger
|
43
|
+
|
44
|
+
@logger = Logger.new(File.join(root, 'request.log'))
|
45
|
+
|
46
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
47
|
+
"#{datetime} | #{msg}\n"
|
48
|
+
end
|
49
|
+
|
50
|
+
@logger
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
data/lib/agentx/cache.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
|
2
|
+
module AgentX
|
3
|
+
class Cache
|
4
|
+
def self.store_path
|
5
|
+
return @store_path if @store_path
|
6
|
+
|
7
|
+
@store_path = File.join(AgentX.root, 'cache')
|
8
|
+
|
9
|
+
unless Dir.exists?(@store_path)
|
10
|
+
Dir.mkdir(@store_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
@store_path
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.path(request)
|
17
|
+
File.join(store_path, "#{request.cache_key}.json")
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.db
|
21
|
+
@db ||= Database.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.write(request, response)
|
25
|
+
db.write(
|
26
|
+
request_cache_key: request.cache_key,
|
27
|
+
request_host: request.host,
|
28
|
+
request_base_url: request.base_url,
|
29
|
+
response_code: response.code,
|
30
|
+
response_headers: Oj.dump(response.headers.to_hash),
|
31
|
+
response_content_length: response.body.length,
|
32
|
+
response_expires_at: response.expires_at,
|
33
|
+
response_body: response.body)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.read(request)
|
37
|
+
if h = db.read(request.cache_key)
|
38
|
+
Response.new(
|
39
|
+
h['response_code'],
|
40
|
+
h['response_body'],
|
41
|
+
Oj.load(h['response_headers']))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Database
|
46
|
+
attr_reader :filename
|
47
|
+
|
48
|
+
def initialize(filename=File.join(AgentX.root, 'cache.sqlite3'))
|
49
|
+
@filename = filename
|
50
|
+
|
51
|
+
create
|
52
|
+
end
|
53
|
+
|
54
|
+
def create
|
55
|
+
if File.exists?(filename)
|
56
|
+
return @db = SQLite3::Database.new(filename)
|
57
|
+
end
|
58
|
+
|
59
|
+
@db = SQLite3::Database.new(filename)
|
60
|
+
|
61
|
+
@db.execute(<<-SQL)
|
62
|
+
CREATE TABLE responses (
|
63
|
+
request_cache_key STRING PRIMARY KEY,
|
64
|
+
request_host STRING,
|
65
|
+
request_base_url STRING,
|
66
|
+
response_code INTEGER,
|
67
|
+
response_headers TEXT,
|
68
|
+
response_content_length INTEGER,
|
69
|
+
response_expires_at INTEGER,
|
70
|
+
response_body BLOB)
|
71
|
+
SQL
|
72
|
+
|
73
|
+
@db.execute(<<-SQL)
|
74
|
+
CREATE INDEX responses_expires_at ON responses (response_expires_at)
|
75
|
+
SQL
|
76
|
+
|
77
|
+
@db.execute(<<-SQL)
|
78
|
+
CREATE INDEX responses_host ON responses (request_host)
|
79
|
+
SQL
|
80
|
+
|
81
|
+
@db
|
82
|
+
end
|
83
|
+
|
84
|
+
INSERT_SQL = <<-SQL
|
85
|
+
INSERT OR REPLACE INTO responses (
|
86
|
+
request_cache_key,
|
87
|
+
request_host,
|
88
|
+
request_base_url,
|
89
|
+
response_code,
|
90
|
+
response_headers,
|
91
|
+
response_content_length,
|
92
|
+
response_expires_at,
|
93
|
+
response_body) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
94
|
+
SQL
|
95
|
+
|
96
|
+
def write(opts={})
|
97
|
+
@prepared_write ||= @db.prepare(INSERT_SQL)
|
98
|
+
|
99
|
+
@prepared_write.execute(
|
100
|
+
opts[:request_cache_key],
|
101
|
+
opts[:request_host],
|
102
|
+
opts[:request_base_url],
|
103
|
+
opts[:response_code],
|
104
|
+
opts[:response_headers],
|
105
|
+
opts[:response_content_length],
|
106
|
+
opts[:response_expires_at].to_i,
|
107
|
+
opts[:response_body])
|
108
|
+
end
|
109
|
+
|
110
|
+
SELECT_BY_CACHE_KEY_SQL = <<-SQL
|
111
|
+
SELECT * FROM responses WHERE request_cache_key = ?
|
112
|
+
SQL
|
113
|
+
|
114
|
+
def read(cache_key)
|
115
|
+
@prepared_read ||= @db.prepare(SELECT_BY_CACHE_KEY_SQL)
|
116
|
+
|
117
|
+
rs = @prepared_read.execute(cache_key)
|
118
|
+
rs.next_hash
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
module AgentX
|
3
|
+
class Console
|
4
|
+
attr_reader :options
|
5
|
+
|
6
|
+
def initialize(options={})
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def load_config
|
11
|
+
unless options[:config]
|
12
|
+
options[:config] = "#{AgentX.root}/config"
|
13
|
+
|
14
|
+
unless File.exists?(options[:config])
|
15
|
+
example =
|
16
|
+
File.expand_path('../../config.example', File.dirname(__FILE__))
|
17
|
+
FileUtils.cp(example, options[:config])
|
18
|
+
FileUtils.chmod(0600, options[:config])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
self.class.load(options[:config])
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.load(filename)
|
26
|
+
class_eval(File.read(filename))
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
module AgentX
|
3
|
+
|
4
|
+
class History
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@entries = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(request, response)
|
12
|
+
@entries << Entry.new(request, response)
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](n)
|
16
|
+
@entries[n]
|
17
|
+
end
|
18
|
+
|
19
|
+
def first
|
20
|
+
@entries.first
|
21
|
+
end
|
22
|
+
|
23
|
+
def last
|
24
|
+
@entries.last
|
25
|
+
end
|
26
|
+
|
27
|
+
def length
|
28
|
+
@entries.length
|
29
|
+
end
|
30
|
+
|
31
|
+
def each(&block)
|
32
|
+
@entries.each(&block)
|
33
|
+
end
|
34
|
+
|
35
|
+
class Entry
|
36
|
+
attr_reader :request, :response
|
37
|
+
|
38
|
+
def initialize(request, response)
|
39
|
+
@request, @response = request, response
|
40
|
+
end
|
41
|
+
|
42
|
+
def inspect
|
43
|
+
[request, response].inspect
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|