agentx 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|