icaprb-server 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE +11 -0
- data/README.md +50 -0
- data/README.rdoc +34 -0
- data/Rakefile +17 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/start_server.rb +36 -0
- data/icaprb-server.gemspec +23 -0
- data/lib/icaprb/icapuri.rb +24 -0
- data/lib/icaprb/server/constants.rb +83 -0
- data/lib/icaprb/server/data_structures.rb +182 -0
- data/lib/icaprb/server/request_parser.rb +295 -0
- data/lib/icaprb/server/response.rb +168 -0
- data/lib/icaprb/server/services.rb +241 -0
- data/lib/icaprb/server/version.rb +6 -0
- data/lib/icaprb/server.rb +211 -0
- metadata +107 -0
@@ -0,0 +1,241 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
module ICAPrb
|
3
|
+
module Server
|
4
|
+
# this module contains the code, which is required to build an ICAP service
|
5
|
+
module Services
|
6
|
+
# Base class for ICAP services
|
7
|
+
class ServiceBase
|
8
|
+
# the name of the service which is used in the response header (Service-Name)
|
9
|
+
attr_accessor :service_name
|
10
|
+
# send the ttl via options header - the options are valid for the given time
|
11
|
+
attr_accessor :options_ttl
|
12
|
+
# the supported methods for this service. This must be an +Array+ which contains symbols.
|
13
|
+
# The values are:
|
14
|
+
#
|
15
|
+
# :request_mod:: request mod is supported
|
16
|
+
# :response_mod:: response mod is supported
|
17
|
+
#
|
18
|
+
# Do not add :options - this would be wrong here!
|
19
|
+
attr_accessor :supported_methods
|
20
|
+
# The preview size has to be set if the server supports previews. Otherwise use +nil+ here.
|
21
|
+
# If your service supports previews
|
22
|
+
attr_accessor :preview_size
|
23
|
+
# +Array+ of file extensions which should be always sent to the +ICAP+ server (no preview)
|
24
|
+
attr_accessor :transfer_complete
|
25
|
+
# +Array+ of file extensions which should not be sent to the +ICAP+ server (not even a preview)
|
26
|
+
attr_accessor :transfer_ignore
|
27
|
+
# +Array+ of file extensions which need a preview sent to the +ICAP+ server
|
28
|
+
# (do not send the full file in advance)
|
29
|
+
attr_accessor :transfer_preview
|
30
|
+
# Maximum amount of concurrent connections per IP. The server will not accept more connections and answers with
|
31
|
+
# an error
|
32
|
+
attr_accessor :max_connections
|
33
|
+
# The IS-Tag is a required header. If you change it, the cache of the proxy will be flushed.
|
34
|
+
# You usually do not need to change this header. You may want to add your service name here.
|
35
|
+
attr_accessor :is_tag
|
36
|
+
# If you want to send a service id header to the +ICAP+ client, you can set it here. Use nil to disable
|
37
|
+
# this header.
|
38
|
+
attr_accessor :service_id
|
39
|
+
# the counter is used to determine if too many connections are opened by the proxy.
|
40
|
+
# If this is the case, the the server answers with an error
|
41
|
+
attr_reader :counter
|
42
|
+
# timeout for the service
|
43
|
+
attr_accessor :timeout
|
44
|
+
|
45
|
+
# initialize a new service
|
46
|
+
def initialize(service_name,supported_methods = [], preview_size = nil, options_ttl = 60,
|
47
|
+
transfer_preview = nil, transfer_ignore = nil, transfer_complete = nil, max_connections = 100000) #TODO Work in progress; sort
|
48
|
+
@service_name = service_name
|
49
|
+
@options_ttl = options_ttl
|
50
|
+
@supported_methods = supported_methods
|
51
|
+
@preview_size = preview_size
|
52
|
+
|
53
|
+
@transfer_preview = transfer_preview
|
54
|
+
@transfer_ignore = transfer_ignore
|
55
|
+
@transfer_complete = transfer_complete
|
56
|
+
@max_connections = max_connections
|
57
|
+
@is_tag = nil
|
58
|
+
@service_id = nil
|
59
|
+
@timeout = nil
|
60
|
+
|
61
|
+
@counter = {}
|
62
|
+
end
|
63
|
+
|
64
|
+
# parameters:
|
65
|
+
# server:: reference to the icap server
|
66
|
+
# ip:: ip address of the peer
|
67
|
+
# socket:: socket to communicate
|
68
|
+
# data:: the parsed request
|
69
|
+
def process_request(_,_,_,_)
|
70
|
+
raise :not_implemented
|
71
|
+
end
|
72
|
+
|
73
|
+
# returns if this service supports previews which means it can request the rest of the data if they are
|
74
|
+
# required. If you do not override this method, this will return false so you will get the complete request.
|
75
|
+
def supports_preview?
|
76
|
+
return false if @preview_size.nil?
|
77
|
+
return preview_size >= 0
|
78
|
+
end
|
79
|
+
|
80
|
+
# include the ChunkedEncodingHelper for previews
|
81
|
+
include ::ICAPrb::Server::Parser::ChunkedEncodingHelper
|
82
|
+
|
83
|
+
# returns true if we already got all data or if we are in a preview.
|
84
|
+
# if we are not in a preview, the preview header is not present => outside of a preview
|
85
|
+
# and if the ieof is set, there is no data left - we have all data
|
86
|
+
# everything else means there is data left to request.
|
87
|
+
# NOTE: this will only work once! Do not request data after calling this method and call it again -
|
88
|
+
# you will get a false negative.
|
89
|
+
def got_all_data?(data)
|
90
|
+
return true unless data[:icap_data][:header]['Preview']
|
91
|
+
return true if data[:http_response_body].ieof
|
92
|
+
return false
|
93
|
+
end
|
94
|
+
|
95
|
+
# When we get a preview, we can answer it or request the rest of the data.
|
96
|
+
# This method will send the status "100 Continue" to request the rest of the data and
|
97
|
+
# it will then request all the data which is left and returns this data as a single string.
|
98
|
+
#
|
99
|
+
# You may want to concatenate it with the data you already got in the preview using the << operator.
|
100
|
+
#
|
101
|
+
# WARNING: DO NOT CALL THIS METHOD IF YOU ARE NOT IN A PREVIEW!
|
102
|
+
def get_the_rest_of_the_data(io)
|
103
|
+
data = ''
|
104
|
+
Response.continue(io)
|
105
|
+
until (line,_ = read_chunk(io); line) && line == :eof
|
106
|
+
data += line
|
107
|
+
end
|
108
|
+
return data
|
109
|
+
end
|
110
|
+
|
111
|
+
# this method is called by the server when it receives a new ICAP request
|
112
|
+
# it will increase the counter by one, call process_request and decreases the counter by one.
|
113
|
+
def do_process(server,ip,io,data)
|
114
|
+
begin
|
115
|
+
enter(ip)
|
116
|
+
rescue
|
117
|
+
Response.display_error_page(io,503,{'title' => 'ICAP Error',
|
118
|
+
'content' => 'Sorry, too much work for me',
|
119
|
+
:http_version => '1.1',
|
120
|
+
:http_status => 500})
|
121
|
+
return
|
122
|
+
end
|
123
|
+
|
124
|
+
begin
|
125
|
+
unless @supported_methods.include? data[:icap_data][:request_line][:icap_method]
|
126
|
+
Response.display_error_page(io,501,{'title' => 'Method not implemented',
|
127
|
+
'content' => 'I do not know what to do with that...',
|
128
|
+
:http_version => '1.1',
|
129
|
+
:http_status => 500})
|
130
|
+
return
|
131
|
+
end
|
132
|
+
if @timeout
|
133
|
+
begin
|
134
|
+
Timeout::timeout(@timeout) do
|
135
|
+
process_request(server,ip,io,data)
|
136
|
+
end
|
137
|
+
rescue Timeout::Error => e
|
138
|
+
# do not do a graceful shutdown of the connection as the client may fail
|
139
|
+
server.logger.error e
|
140
|
+
io.close
|
141
|
+
end
|
142
|
+
else
|
143
|
+
process_request(server,ip,io,data)
|
144
|
+
end
|
145
|
+
rescue
|
146
|
+
leave(ip)
|
147
|
+
raise
|
148
|
+
end
|
149
|
+
leave(ip)
|
150
|
+
end
|
151
|
+
|
152
|
+
# when the connection enters this method will increase the counter. If the counter exceeds the limit,
|
153
|
+
# the request will be rejected
|
154
|
+
def enter(ip)
|
155
|
+
if @counter[ip]
|
156
|
+
raise :connection_limit_exceeded unless (@counter[ip] < @max_connections) || @max_connections.nil?
|
157
|
+
@counter[ip] += 1
|
158
|
+
else
|
159
|
+
@counter[ip] = 1
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# when the request is answered we can allow the next one by decrementing the counter
|
164
|
+
def leave(ip)
|
165
|
+
@counter[ip] -= 1
|
166
|
+
end
|
167
|
+
|
168
|
+
# This method is called by the server when the client sends an options request which is not a
|
169
|
+
# mandatory upgrade.
|
170
|
+
#
|
171
|
+
# The data used here is set by the constructor and it should be configured when the Service
|
172
|
+
# is initialized.
|
173
|
+
#
|
174
|
+
# Parameters:
|
175
|
+
# +io+ the socket used to answer the request
|
176
|
+
def generate_options_response(io)
|
177
|
+
response = ::ICAPrb::Server::Response.new
|
178
|
+
response.components << ::ICAPrb::Server::NullBody.new
|
179
|
+
methods = []
|
180
|
+
methods << 'REQMOD' if @supported_methods.include? :request_mod
|
181
|
+
methods << 'RESPMOD' if @supported_methods.include? :response_mod
|
182
|
+
response.icap_header['Methods'] = methods.join(', ')
|
183
|
+
set_generic_icap_headers(response.icap_header)
|
184
|
+
response.icap_header['Max-Connections'] = @max_connections if @max_connections
|
185
|
+
response.icap_header['Options-TTL'] = @options_ttl if @options_ttl
|
186
|
+
response.icap_header['Preview'] = @preview_size if @preview_size
|
187
|
+
response.icap_header['Transfer-Ignore'] = @transfer_ignore.join(', ') if @transfer_ignore
|
188
|
+
response.icap_header['Transfer-Complete'] = @transfer_complete.join(', ') if @transfer_complete
|
189
|
+
response.icap_header['Transfer-Preview'] = @transfer_preview.join(', ') if @transfer_preview
|
190
|
+
response.icap_header['Allow'] = '204'
|
191
|
+
response.write_headers_to_socket io
|
192
|
+
end
|
193
|
+
|
194
|
+
# set headers independently from the response type
|
195
|
+
#
|
196
|
+
# parameters:
|
197
|
+
# +icap_header+:: The hash which holds the ICAP headers.
|
198
|
+
def set_generic_icap_headers(icap_header)
|
199
|
+
icap_header['Service-Name'] = @service_name
|
200
|
+
icap_header['ISTag'] = @is_tag if @is_tag
|
201
|
+
icap_header['Service-ID'] = @service_id if @service_id
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Sample Service to test the server
|
206
|
+
# it will echo the complete request to the client
|
207
|
+
class EchoService < ServiceBase
|
208
|
+
# initializes the EchoService - the name of the echo service is echo
|
209
|
+
def initialize
|
210
|
+
super('echo',[:request_mod, :response_mod],1024,60,nil,nil,nil,1000)
|
211
|
+
@timeout = nil
|
212
|
+
end
|
213
|
+
|
214
|
+
# return the request to the client
|
215
|
+
def process_request(icap_server,ip,socket,data)
|
216
|
+
logger = icap_server.logger
|
217
|
+
logger.debug 'Start processing data via echo service...'
|
218
|
+
response = ::ICAPrb::Server::Response.new
|
219
|
+
response.icap_status_code = 200
|
220
|
+
if data[:icap_data][:request_line][:icap_method] == :response_mod
|
221
|
+
http_resp_header = data[:http_response_header]
|
222
|
+
http_resp_body = data[:http_response_body]
|
223
|
+
else
|
224
|
+
http_resp_header = data[:http_request_header]
|
225
|
+
http_resp_body = data[:http_request_body]
|
226
|
+
end
|
227
|
+
|
228
|
+
http_resp_body << get_the_rest_of_the_data(socket) if http_resp_body && !(got_all_data? data)
|
229
|
+
response.components << http_resp_header
|
230
|
+
response.components << http_resp_body
|
231
|
+
response.write_headers_to_socket socket
|
232
|
+
if http_resp_body.instance_of? ResponseBody
|
233
|
+
socket.write(http_resp_body.to_chunk)
|
234
|
+
::ICAPrb::Server::Response.send_last_chunk(socket,false)
|
235
|
+
end
|
236
|
+
logger.debug 'Answered request in echo service'
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'icaprb/server/version'
|
2
|
+
require 'openssl'
|
3
|
+
require 'socket'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
require_relative './server/request_parser'
|
7
|
+
require_relative './server/response'
|
8
|
+
require_relative './server/services'
|
9
|
+
# nodoc
|
10
|
+
module ICAPrb
|
11
|
+
# The server code of our project.
|
12
|
+
module Server
|
13
|
+
# This class contains the network related stuff like waiting for connections.
|
14
|
+
# It is the main class of this project.
|
15
|
+
class ICAPServer
|
16
|
+
# supported ICAP versions
|
17
|
+
SUPPORTED_ICAP_VERSIONS = ['1.0']
|
18
|
+
# logger for the server; default level is Logger::WARN and it writes to STDOUT
|
19
|
+
attr_accessor :logger
|
20
|
+
# services registered on the server
|
21
|
+
attr_accessor :services
|
22
|
+
|
23
|
+
# Create a new ICAP server
|
24
|
+
#
|
25
|
+
# * <b>host</b> the host on which the socket should be bound to
|
26
|
+
# * <b>port</b> the port on which the socket should be bound to - this is usually 1344
|
27
|
+
# * <b>options</b> when you want to use TLS, you can pass a Hash containing the following information
|
28
|
+
# :secure:: true if TLS should be used
|
29
|
+
# :certificate:: the path of the certificate
|
30
|
+
# :key:: the path of the key file
|
31
|
+
def initialize(host = 'localhost', port = 1344, options = nil)
|
32
|
+
@host, @port = host,port
|
33
|
+
@secure = false
|
34
|
+
@certificate = nil
|
35
|
+
@key = nil
|
36
|
+
if (options.is_a? Hash) && (@secure = options[:secure])
|
37
|
+
@key = options[:key]
|
38
|
+
@certificate = options[:certificate]
|
39
|
+
end
|
40
|
+
|
41
|
+
if (options.is_a? Hash) && options[:logfile]
|
42
|
+
@logger = Logger.new(options[:logfile])
|
43
|
+
else
|
44
|
+
@logger = Logger.new(STDOUT)
|
45
|
+
end
|
46
|
+
|
47
|
+
if (options.is_a? Hash) && options[:log_level]
|
48
|
+
@logger.level = options[:log_level]
|
49
|
+
else
|
50
|
+
@logger.level = Logger::WARN
|
51
|
+
end
|
52
|
+
|
53
|
+
@services = {}
|
54
|
+
|
55
|
+
@enable_tls_1_1 = options[:enable_tls_1_1] unless options.nil?
|
56
|
+
|
57
|
+
@tls_socket = false
|
58
|
+
if (options.is_a? Hash) && options[:tls_socket]
|
59
|
+
@tls_socket = options[:tls_socket]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# this methods starts the server and passes the connection to the method handle_request
|
64
|
+
# as well as the ip and the port.
|
65
|
+
# It will log the information about the connection if the level is set to info or lower.
|
66
|
+
#
|
67
|
+
# this method will most likely never crash. It is blocking so you may want to run it in
|
68
|
+
# its own thread.
|
69
|
+
def run
|
70
|
+
# run the server
|
71
|
+
server = create_server
|
72
|
+
loop do
|
73
|
+
|
74
|
+
Thread.start(server.accept) do |connection|
|
75
|
+
|
76
|
+
if connection.is_a? OpenSSL::SSL::SSLSocket
|
77
|
+
port, ip = Socket.unpack_sockaddr_in(connection.io.getpeername)
|
78
|
+
else
|
79
|
+
port, ip = Socket.unpack_sockaddr_in(connection.getpeername)
|
80
|
+
end
|
81
|
+
@logger.info "[CONNECT] Client from #{ip}:#{port} connected to this server"
|
82
|
+
begin
|
83
|
+
until connection.closed? do
|
84
|
+
handle_request(connection,ip)
|
85
|
+
end
|
86
|
+
rescue Errno::ECONNRESET => e
|
87
|
+
@logger.error "[CONNECTION ERROR] Client #{ip}:#{port} got disconnected (CONNECTION RESET BY PEER): #{e}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# this method handles the connection to the client. It will call the parser and sends the request to the service.
|
95
|
+
# The service must return anything and handle the request. The important classes are in response.rb
|
96
|
+
# This method includes a lot of error handling. It will respond with an error page if
|
97
|
+
# * The ICAP version is not supported
|
98
|
+
# * It cannot read the header
|
99
|
+
# * The method is not supported by the service
|
100
|
+
# * The request has an upgrade header, which is not supported
|
101
|
+
# * the client requested an upgrade to tls, but the server has not been configured to use it
|
102
|
+
# * the client requested a service, which does not exist
|
103
|
+
def handle_request(connection, ip)
|
104
|
+
# handles the request
|
105
|
+
begin
|
106
|
+
parser = RequestParser.new(connection, ip, self)
|
107
|
+
parsed_data = parser.parse
|
108
|
+
rescue Exception => e
|
109
|
+
#puts $@
|
110
|
+
logger.error "[PARSER ERROR] Error while parsing request - Error Message is: #{e}"
|
111
|
+
Response.display_error_page(connection,400,
|
112
|
+
{http_version: '1.0',http_status: 400, 'title' => 'Invalid Request',
|
113
|
+
'content' => 'Your client sent a malformed request - please fix it and try it again.'})
|
114
|
+
return
|
115
|
+
end
|
116
|
+
|
117
|
+
unless SUPPORTED_ICAP_VERSIONS.include? parsed_data[:icap_data][:request_line][:version]
|
118
|
+
Response.display_error_page(connection,505,
|
119
|
+
{http_version: '1.0',
|
120
|
+
http_status: 500,
|
121
|
+
'title' => 'Unknown ICAP-version used',
|
122
|
+
'content' => 'We are sorry but your ICAP version is not known by this server.'})
|
123
|
+
end
|
124
|
+
|
125
|
+
# send the data to the service framework
|
126
|
+
path = parsed_data[:icap_data][:request_line][:uri].path
|
127
|
+
path = path[1...path.length] if path != '*'
|
128
|
+
if (service = @services[path])
|
129
|
+
icap_method = parsed_data[:icap_data][:request_line][:icap_method]
|
130
|
+
if icap_method == :options
|
131
|
+
return service.generate_options_response(connection)
|
132
|
+
else
|
133
|
+
if service.supported_methods.include? icap_method
|
134
|
+
service.do_process(self,ip,connection,parsed_data)
|
135
|
+
return
|
136
|
+
else
|
137
|
+
Response.display_error_page(connection,405,
|
138
|
+
{http_version: '1.0',http_status: 500, 'title' => 'ICAP Error',
|
139
|
+
'content' => 'Your client accessed the service with the wrong method.'})
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
elsif (path == '*') && (parsed_data[:icap_data][:request_line][:icap_method] == :options)
|
144
|
+
# check for an upgrade header
|
145
|
+
icap_data = parsed_data[:icap_data]
|
146
|
+
if icap_data[:header]['Connection'] == 'Upgrade' && connection.class == OpenSSL::SSL::SSLSocket
|
147
|
+
case icap_data[:header]['Upgrade']
|
148
|
+
when /^TLS\/[\d\.]+, ICAP\/[\d\.]+$/
|
149
|
+
response = Response.new
|
150
|
+
response.icap_status_code = 101
|
151
|
+
response.icap_header['Upgrade'] = "TLS/1.2, ICAP/#{icap_data[:request_line][:version]}"
|
152
|
+
response.write_headers_to_socket connection
|
153
|
+
connection.accept # upgrade connection to use tls
|
154
|
+
else
|
155
|
+
Response.display_error_page(connection,400,{'title' => 'ICAP Error',
|
156
|
+
'content' => 'Upgrade header is missing',
|
157
|
+
:http_version => '1.1',
|
158
|
+
:http_status => 500})
|
159
|
+
end
|
160
|
+
else
|
161
|
+
Response.display_error_page(connection,500,{'title' => 'ICAP Error',
|
162
|
+
'content' => 'This server has no TLS support.',
|
163
|
+
:http_version => '1.1',
|
164
|
+
:http_status => 500})
|
165
|
+
end
|
166
|
+
return
|
167
|
+
else
|
168
|
+
Response.display_error_page(connection,404,
|
169
|
+
{http_version: '1.0',http_status: 500, 'title' => 'Not Found',
|
170
|
+
'content' => 'Sorry, but the ICAP service does not exist.'})
|
171
|
+
return
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
# this method will create a server based on the information we got on initialisation.
|
178
|
+
# It will create an +TCPServer+ with the host and port given at initialisation.
|
179
|
+
# If @secure evaluates to true, a +SSLServer+ will be crated and wraps this +TCPServer+.
|
180
|
+
# By default, only TLS 1.2 is supported for security reasons but TLS 1.1 can be enabled
|
181
|
+
# as well when the option is set at initialization.
|
182
|
+
# For security reasons, the encryption algorithms +RC4+ and +DES+ are disabled as well as the
|
183
|
+
# digest algorithm +SHA1+.
|
184
|
+
# returns: An instance of TCPServer or SSLServer
|
185
|
+
def create_server
|
186
|
+
tcp_server = TCPServer.new(@host, @port)
|
187
|
+
if @secure
|
188
|
+
ctx = OpenSSL::SSL::SSLContext.new(:TLSv1_2_server)
|
189
|
+
ctx.cert = OpenSSL::X509::Certificate.new(File.read(@certificate))
|
190
|
+
ctx.key = OpenSSL::PKey::RSA.new(File.read(@key))
|
191
|
+
# secure OpenSSL
|
192
|
+
###############################
|
193
|
+
# do not allow ssl v2 or ssl v3
|
194
|
+
ctx.options |= (OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3 | OpenSSL::SSL::OP_NO_TLSv1)
|
195
|
+
# disable TLS 1.1 unless the user requests it
|
196
|
+
ctx.options |= OpenSSL::SSL::OP_NO_TLSv1_1 unless @enable_tls_1_1
|
197
|
+
|
198
|
+
# I do not want to have something encrypted with RC4 or with a DES variant and it should not use the digest
|
199
|
+
# algorithm SHA1
|
200
|
+
ctx.ciphers =
|
201
|
+
OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ciphers].split(':').select do |cipher_suite|
|
202
|
+
!((cipher_suite =~ /RC4|DES/) || (cipher_suite =~ /SHA$/))
|
203
|
+
end.join(':')
|
204
|
+
tcp_server = OpenSSL::SSL::SSLServer.new(tcp_server, ctx)
|
205
|
+
tcp_server.start_immediately = @tls_socket # requires accept call later
|
206
|
+
end
|
207
|
+
@tcp_server = tcp_server
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: icaprb-server
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Fabian Franz
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.11'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.11'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- fabian.franz@students.fh-hagenberg.at
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rspec"
|
64
|
+
- ".travis.yml"
|
65
|
+
- Gemfile
|
66
|
+
- LICENSE
|
67
|
+
- README.md
|
68
|
+
- README.rdoc
|
69
|
+
- Rakefile
|
70
|
+
- bin/console
|
71
|
+
- bin/setup
|
72
|
+
- bin/start_server.rb
|
73
|
+
- icaprb-server.gemspec
|
74
|
+
- lib/icaprb/icapuri.rb
|
75
|
+
- lib/icaprb/server.rb
|
76
|
+
- lib/icaprb/server/constants.rb
|
77
|
+
- lib/icaprb/server/data_structures.rb
|
78
|
+
- lib/icaprb/server/request_parser.rb
|
79
|
+
- lib/icaprb/server/response.rb
|
80
|
+
- lib/icaprb/server/services.rb
|
81
|
+
- lib/icaprb/server/version.rb
|
82
|
+
homepage: https://github.com/fabianfrz/ICAPrb-Server
|
83
|
+
licenses: []
|
84
|
+
metadata: {}
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 2.5.1
|
102
|
+
signing_key:
|
103
|
+
specification_version: 4
|
104
|
+
summary: This project includes an ICAP server fully implemented in Ruby but it does
|
105
|
+
not include services.
|
106
|
+
test_files: []
|
107
|
+
has_rdoc:
|