icaprb-server 0.0.1
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 +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:
|