simple-gnupg-keyserver 1.3.0 → 2.0.0

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.
@@ -0,0 +1,239 @@
1
+
2
+ module SimpleHKP
3
+
4
+ module Keys
5
+
6
+ def saveLastKey(lastKey)
7
+ lastKey['colonData'].gsub!(/\\x3a/,':') if
8
+ lastKey.has_key?('colonData')
9
+ pp lastKey if @debug
10
+ @keyLookupData[lastKey['keyID']] = lastKey unless
11
+ lastKey.empty? || lastKey['keyID'].empty?
12
+ end
13
+
14
+ def loadKeys
15
+ @keyLookupData = Hash.new
16
+ puts "SimpleHKP: loading keys" if @debug
17
+ gpg2cmd = "gpg2 --homedir #{@keyDir} --with-fingerprint --with-colons --list-sigs"
18
+ puts gpg2cmd if @debug
19
+ lastKey = Hash.new
20
+ IO.popen(gpg2cmd, 'r').readlines.each do | aLine |
21
+ next if aLine =~ /^tru/
22
+ if aLine =~ /^pub/ then
23
+ saveLastKey(lastKey)
24
+ lastKey = Hash.new
25
+ keyData = aLine.split(/:/)
26
+ lastKey['userID'] = keyData[9].gsub(/\\x3a/,':') # user ID
27
+ lastKey['keyID'] = keyData[4] # key ID
28
+ lastKey['algorithm'] = keyData[3] # key type (algorithm)
29
+ lastKey['len'] = keyData[2] # key length
30
+ lastKey['created'] = keyData[5] # creation date
31
+ lastKey['expires'] = keyData[6] # expiration date
32
+ lastKey['flags'] = keyData[1] # flags
33
+ lastKey['colonData'] = aLine
34
+ next
35
+ end
36
+ lastKey['userID'] = aLine.split(/:/)[9].gsub(/\\x3a/,':') if
37
+ aLine =~ /^uid/ && lastKey['userID'].empty?
38
+ lastKey['colonData'] << aLine
39
+ end
40
+ saveLastKey(lastKey)
41
+ @keyLookupData.each_pair do | key, value |
42
+ value['humanData'] = `gpg2 --list-sig #{key}`
43
+ end
44
+ pp @keyLookupData if @debug
45
+ puts "SimpleHKP: finished loading keys" if @debug
46
+ end
47
+
48
+ def storeKey(env)
49
+ #
50
+ # decode the post data
51
+ #
52
+ keys = URI.decode_www_form(env['rack.input'].read)
53
+ #
54
+ # ensure we are being sent a key
55
+ #
56
+ return replyBadRequest("No keytext field in post data") unless
57
+ keys[0][0] =~ /keytext/
58
+ pp keys[0][1] if @debug
59
+ #
60
+ # send the key data through gpg2 to import it
61
+ #
62
+ gpgStdErr = ""
63
+ Open3.popen3("gpg2 --homedir #{@keyDir} --import") do | stdin, stdout, stderr, wait_thread |
64
+ stdin.write(keys[0][1])
65
+ stdin.close
66
+ gpgStdErr = stderr.read
67
+ end
68
+ #
69
+ # check to see if the key has already been imported and/or changed
70
+ #
71
+ puts "[[#{gpgStdErr}]]" if @debug
72
+ keyChanged = "changed"
73
+ if gpgStdErr =~ /not\s+changed/ then
74
+ keyChanged = "unchanged"
75
+ else
76
+ # something has changed to re-load the keys
77
+ loadKeys
78
+ end
79
+ #
80
+ # send the key data through gpg2 (again ;-( to extract the keyID
81
+ #
82
+ gpgKeyData = ""
83
+ IO.popen('gpg2 --with-fingerprint --with-colons -', 'r+') do | pipe |
84
+ puts 'gpg2 --with-fingerprint --with-colons -' if @debug
85
+ pipe.write(keys[0][1])
86
+ pipe.close_write
87
+ gpgKeyData = pipe.read
88
+ end
89
+ puts gpgKeyData if @debug
90
+ #
91
+ # look for the keyID of the public key
92
+ #
93
+ keyID = nil
94
+ gpgKeyData.each_line do | aLine |
95
+ keyData = aLine.split(/:/)
96
+ next unless keyData[0] =~ /pub/
97
+ keyID = keyData[4]
98
+ break
99
+ end
100
+ return replyBadRequest("No keyID found in gpg2 result using uploaded keytext data") if keyID.nil?
101
+ #
102
+ # return OK
103
+ #
104
+ @statusCode = 200
105
+ @body << @headerHtml
106
+ @body << "<h1 class=\"simpleHKP-storedKey\">Stored key: [#{keyID}]</h1>"
107
+ @body << "<p>key #{keyChanged}</p>"
108
+ @body << "<h2 class=\"simpleHKP-keyDataKeyID\">Key data for key: [#{keyID}]</h2>"
109
+ @body << '<pre class="simpleHKP-keyData">'+@keyLookupData[keyID]['colonData']+"</pre>\n"
110
+ @body << '<pre class="simpleHKP-keyData">'+@keyLookupData[keyID]['humanData']+"</pre>\n"
111
+ @body << @footer
112
+ end
113
+
114
+ def lookUpKey(queryString)
115
+ return replyBadRequest("No search field in queryString") unless
116
+ queryString.has_key?('search')
117
+ #
118
+ # normalize the search string
119
+ #
120
+ searchString = queryString['search']
121
+ searchString.gsub!(/0x/,'')
122
+ searchRegexp = Regexp.new(searchString,
123
+ Regexp::IGNORECASE | Regexp::MULTILINE)
124
+ puts searchRegexp if @debug
125
+ #
126
+ # (linearly) look through the hash of known keys
127
+ # looking for the FIRST match
128
+ #
129
+ keyID = nil
130
+ @keyLookupData.each_pair do | key, keyData |
131
+ next unless keyData['colonData'] =~ searchRegexp
132
+ puts "FOUND #{key} (#{keyData})" if @debug
133
+ keyID = key
134
+ break
135
+ end
136
+ return replyNotFound if keyID.nil?
137
+
138
+ if queryString.has_key?('options') &&
139
+ queryString['options'] == 'mr' then
140
+ #
141
+ # return the key data in machine readable format
142
+ #
143
+ @header = {
144
+ 'Content-Type' => 'application/pgp-keys; charset=utf-8',
145
+ 'Content-Disposition' =>
146
+ 'attachment; filename=' + keyID
147
+ }
148
+ puts @header if @debug
149
+ @body << `gpg2 --homedir #{@keyDir} --armor --export #{keyID}`
150
+ else
151
+ #
152
+ # return the key data for a human to read
153
+ #
154
+ @body << @headerHtml
155
+ @body << @lookupKeysForm
156
+ @body << '<h1 class="simpleHKP-keyAsckeyId">Key: '+keyID+'</h1>'
157
+ @body << '<h2 class="simpleHKP-keyDataTitle">Key data:</h2>'
158
+ @body << '<pre class="simpleHKP-keyData">'
159
+ @body << @keyLookupData[keyID]['humanData']
160
+ @body << '</pre>'
161
+ @body << '<h2 class="simpleHKP-keyAscTitle">Key contents:</h2>'
162
+ @body << '<pre class="simpleHKP-keyAsc">'
163
+ @body << `gpg2 --homedir #{@keyDir} --armor --export #{keyID}`
164
+ @body << '</pre>'
165
+ @body << @footer
166
+ end
167
+ end
168
+
169
+ def indexKeys(queryString)
170
+ return replyBadRequest("No search field in queryString") unless
171
+ queryString.has_key?('search')
172
+ #
173
+ # normalize the search string
174
+ #
175
+ searchString = queryString['search']
176
+ searchString.gsub!(/0x/,'')
177
+ searchRegexp = Regexp.new(searchString,
178
+ Regexp::IGNORECASE | Regexp::MULTILINE)
179
+ puts searchRegexp if @debug
180
+ #
181
+ # accumulate the keyIDs of any keys that match the serach
182
+ #
183
+ keys = Array.new
184
+ @keyLookupData.each_pair do | key, keyData |
185
+ next unless keyData['colonData'] =~ searchRegexp
186
+ puts "FOUND #{key} (#{keyData})" if @debug
187
+ keys.push(key)
188
+ end
189
+
190
+ if queryString.has_key?('options') &&
191
+ queryString['options'] == 'mr' then
192
+ #
193
+ # return a machine readable list of the keys found
194
+ #
195
+ @header = { 'Content-Type' => 'text/plain' }
196
+ @body << "info:1:#{keys.size}\n"
197
+ keys.each do | aKey |
198
+ next unless @keyLookupData.has_key?(aKey)
199
+ keyData = @keyLookupData[aKey]
200
+ pubData = [ "pub" ]
201
+ pubData << aKey
202
+ pubData << keyData['algorithm']
203
+ pubData << keyData['len']
204
+ pubData << keyData['created']
205
+ pubData << keyData['expires']
206
+ pubData << keyData['flags']
207
+ @body << pubData.join(':')
208
+ @body << "\n"
209
+ uidData = [ "uid" ]
210
+ uidData << keyData['userID'].gsub(/:/, '\x3a')
211
+ uidData << keyData['created']
212
+ uidData << keyData['expires']
213
+ uidData << keyData['flags']
214
+ @body << uidData.join(':')
215
+ @body << "\n"
216
+ end
217
+ else
218
+ #
219
+ # return a (simple) human readable list of the keys found
220
+ #
221
+ @body << @headerHtml
222
+ @body << @lookupKeysForm
223
+ @body << '<h1 class="simpleHKP-searchString">Keys matching: ['+searchString+']</h1><ul class="simpleHKP-searchList">'
224
+ keys.each do | aKey |
225
+ next unless @keyLookupData.has_key?(aKey)
226
+ keyData = @keyLookupData[aKey]
227
+ keyStr = " <li class=\"simpleHKP-searchItem\"><a href=\"lookup?op=get&search=#{aKey}\">#{aKey}:&nbsp;"
228
+ keyStr << keyData['userID']
229
+ keyStr << "</a></li>\n"
230
+ @body << keyStr
231
+ end
232
+ @body << '</ul>'
233
+ @body << @footer
234
+ end
235
+ pp @body if @debug
236
+ end
237
+
238
+ end
239
+ end
@@ -0,0 +1,349 @@
1
+ # Copyright (C) 2015 Stephen Gaito
2
+ #
3
+ # (The MIT License)
4
+ #
5
+ # Copyright (c) 2015 Stephen Gaito
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a
8
+ # copy of this software and associated documentation files (the
9
+ # 'Software'), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be included
16
+ # in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS
19
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ module SimpleHKP
27
+ class Server
28
+ include Keys
29
+ include Identities
30
+
31
+ def loadHtml
32
+ puts "SimpleHKP: loading html partials" if @debug
33
+ Dir.chdir(@htmlDir) do
34
+ @headerHtml = "<html><head>"
35
+ @headerHtml << " <title>#{@title}</title>\n"
36
+ @headerHtml << "</head><body class=\"simpleHKP-body\">\n"
37
+ @headerHtml << "<p><a href=\"/\">#{@title}</a></p>"
38
+ @headerHtml = File.open('header.html','r').read if
39
+ File.exists?('header.html')
40
+ puts @headerHtml if @debug
41
+
42
+ @defaultBody = ""
43
+ @defaultBody << "<h1 class=\"simpleHKP-welcome\">Welcome to SimpleHKP</h1>\n"
44
+ @defaultBody << "<ul class=\"simpleHKP-tasksList\">\n"
45
+ @defaultBody << " <li class=\"simpleHKP-taskItem\"><a href=\"/pks/lookup?op=form\">Search keys</a></li>\n"
46
+ @defaultBody << " <li class=\"simpleHKP-taskItem\"><a href=\"/pks/add\">Upload new key</a></li>\n"
47
+ @defaultBody << " <li class=\"simpleHKP-taskItem\"><a href=\"/pis/lookup?op=form\">Search identities</a></li>\n"
48
+ @defaultBody << " <li class=\"simpleHKP-taskItem\"><a href=\"/pis/add\">Upload new identity</a></li>\n"
49
+ @defaultBody << " <li class=\"simpleHKP-taskItem\"><a href=\"reload\">Reload existing keys and identities</a></li>\n"
50
+ @defaultBody << "</ul>\n"
51
+ @defaultBody = File.open('defaultBody.html','r').read if
52
+ File.exists?('defaultBody.html')
53
+ puts @defaultBody if @debug
54
+
55
+ @lookupKeysForm = ""
56
+ @lookupKeysForm << "<div class=\"simpleHKP-lookupKeysFormDiv\">\n"
57
+ @lookupKeysForm << " <h1 class=\"simpleHKP-lookupKeysFormTitle\">Search GnuPG keys</h1>\n"
58
+ @lookupKeysForm << " <form action=\"/pks/lookup\" method=\"get\" id=\"simpleHKP-lookupKeysForm\">\n"
59
+ @lookupKeysForm << " <input type=\"text\" name=\"search\" size=\"80\" />\n"
60
+ @lookupKeysForm << " <input type=\"hidden\" name=\"op\" value=\"index\" />\n"
61
+ @lookupKeysForm << " <input type=\"submit\" value=\"Search\" />\n"
62
+ @lookupKeysForm << " </form>\n"
63
+ @lookupKeysForm << "</div>\n"
64
+ @lookupKeysForm = File.open('lookupKeysForm.html','r').read if
65
+ File.exists?('lookupKeysForm.html')
66
+ puts @lookupKeysForm if @debug
67
+
68
+ @lookupIdentitiesForm = ""
69
+ @lookupIdentitiesForm << "<div class=\"simpleHKP-lookupIdentitiesFormDiv\">\n"
70
+ @lookupIdentitiesForm << " <h1 class=\"simpleHKP-lookupIdentitiesFormTitle\">Search GnuPG keys</h1>\n"
71
+ @lookupIdentitiesForm << " <form action=\"/pis/lookup\" method=\"get\" id=\"simpleHKP-lookupIdentitiesForm\">\n"
72
+ @lookupIdentitiesForm << " <input type=\"text\" name=\"search\" size=\"80\" />\n"
73
+ @lookupIdentitiesForm << " <input type=\"hidden\" name=\"op\" value=\"index\" />\n"
74
+ @lookupIdentitiesForm << " <input type=\"submit\" value=\"Search\" />\n"
75
+ @lookupIdentitiesForm << " </form>\n"
76
+ @lookupIdentitiesForm << "</div>\n"
77
+ @lookupIdentitiesForm = File.open('lookupIdentitiesForm.html','r').read if
78
+ File.exists?('lookupIdentitiesForm.html')
79
+ puts @lookupIdentitiesForm if @debug
80
+
81
+ @uploadKeyForm = ""
82
+ @uploadKeyForm << "<div class=\"simpleHKP-uploadKeyFormDiv\">\n"
83
+ @uploadKeyForm << " <h1 class=\"simpleHKP-uploadKeyFormTitle\">Paste GnuPG key to be uploaded below</h1>\n"
84
+ @uploadKeyForm << " <form action=\"/pks/add\" method=\"post\" id=\"simpleHKP-uploadKeyForm\">\n"
85
+ @uploadKeyForm << " <textarea name=\"keytext\" form=\"simpleHKP-uploadKeyForm\" rows = \"30\" cols=\"70\" ></textarea>\n"
86
+ @uploadKeyForm << " <input type=\"submit\" value=\"Upload\" />\n"
87
+ @uploadKeyForm << " </form>\n"
88
+ @uploadKeyForm << "</div>\n"
89
+ @uploadKeyForm = File.open('uploadKeyForm.html','r').read if
90
+ File.exists?('uploadKeyForm.html')
91
+ puts @uploadKeyForm if @debug
92
+
93
+ @uploadIdentityForm = ""
94
+ @uploadIdentityForm << "<div class=\"simpleHKP-uploadIdentityFormDiv\">\n"
95
+ @uploadIdentityForm << " <h1 class=\"simpleHKP-uploadIdentityFormTitle\">Paste GnuPG encrypted identity to be uploaded below</h1>\n"
96
+ @uploadIdentityForm << " <form action=\"/pis/add\" method=\"post\" id=\"simpleHKP-uploadIdentityForm\">\n"
97
+ @uploadIdentityForm << " <textarea name=\"keytext\" form=\"simpleHKP-uploadIdentityForm\" rows = \"30\" cols=\"70\" ></textarea>\n"
98
+ @uploadIdentityForm << " <input type=\"submit\" value=\"Upload\" />\n"
99
+ @uploadIdentityForm << " </form>\n"
100
+ @uploadIdentityForm << "</div>\n"
101
+ @uploadIdentityForm = File.open('uploadIdentityForm.html','r').read if
102
+ File.exists?('uploadIdentityForm.html')
103
+ puts @uploadIdentityForm if @debug
104
+
105
+ @footer = "<p style=\"color:grey\">SimpleHKP version: #{SimpleHKP::VERSION}</p></body></html>\n"
106
+ @footer = File.open('footer.html','r').read if
107
+ File.exists?('footer.html')
108
+ puts @footer if @debug
109
+ end
110
+ puts "SimpleHKP: finished loading html partials" if @debug
111
+ end
112
+
113
+ def initialize(options = {})
114
+ #
115
+ # setup the default options
116
+ #
117
+ defaultOptions = {
118
+ 'debug' => false,
119
+ 'title' => 'SimpleHKP',
120
+ 'simpleHKPdir' => 'simpleHKP',
121
+ 'keyDir' => 'keys',
122
+ 'idDir' => 'identities',
123
+ # 'idIndex' => 'identities/idIndex.yaml',
124
+ 'mediaDir' => 'media',
125
+ 'htmlDir' => 'html',
126
+ 'mimeMap' => {
127
+ 'css' => 'text/css',
128
+ 'html' => 'text/html',
129
+ 'js' => 'text/javascript'
130
+ }
131
+ }
132
+ #
133
+ # merge the options mimeMap into the default options mimeMap
134
+ #
135
+ defaultOptions['mimeMap'].merge!(delete(options['mimeMap'])) if
136
+ options.has_key?('mimeMap')
137
+ #
138
+ # merge in the rest of the options into the default options
139
+ #
140
+ @options = defaultOptions.merge(options)
141
+ #
142
+ # setup the required variables
143
+ #
144
+ @debug = @options['debug']
145
+ @title = @options['title']
146
+ @baseDir = @options['simpleHKPdir']
147
+ @keyDir = @baseDir+'/'+@options['keyDir']
148
+ @idDir = @baseDir+'/'+@options['idDir']
149
+ # @idIndex = @baseDir+'/'+@options['idIndex']
150
+ @mediaDir = @baseDir+'/'+@options['mediaDir']
151
+ @htmlDir = @baseDir+'/'+@options['htmlDir']
152
+ @mimeMap = @options['mimeMap']
153
+ if @debug then
154
+ puts "SimpleHKP options:"
155
+ pp @options
156
+ end
157
+ #
158
+ # ensure the required directories all exist
159
+ #
160
+ FileUtils.mkdir_p(@keyDir)
161
+ FileUtils.chmod(0700, @keyDir)
162
+ FileUtils.mkdir_p(@idDir)
163
+ FileUtils.chmod(0700, @idDir)
164
+ FileUtils.mkdir_p(@mediaDir)
165
+ FileUtils.mkdir_p(@htmlDir)
166
+ #
167
+ # load the existing keys and the html partials
168
+ loadKeys
169
+ loadIdentities
170
+ loadHtml
171
+ end
172
+
173
+ def ppEnv(env)
174
+ Dir.pwd+' '+
175
+ # '['+URI.decode_www_form(env["rack.input"].rewind.read).pretty_inspect+']'+
176
+ env.pretty_inspect
177
+ end
178
+
179
+ def replyInternalError(env, exception)
180
+ @statusCode = 500
181
+ @body << @headerHtml
182
+ @body << "<p>Internal server error!</p>\n"
183
+ @body << "<pre>#{exception}</pre>\n" if @debug
184
+ @body << @footer
185
+ if @debug then
186
+ puts "\n\n-----------------------------------------------------------"
187
+ puts exception
188
+ puts "-----------------------------------------------------------"
189
+ puts ppEnv(env)
190
+ puts "-----------------------------------------------------------\n\n"
191
+ end
192
+ end
193
+
194
+ def replyNotImplemented
195
+ @statusCode = 501
196
+ @body << @headerHtml
197
+ @body << '<p>Not implemented!<p>'
198
+ @body << @footer
199
+ end
200
+
201
+ def replyBadRequest(message)
202
+ @statusCode = 400
203
+ @body << @headerHtml
204
+ @body << '<p>Bad request!</p>'
205
+ @body << "<p>#{message}</p>"
206
+ @body << @footer
207
+ end
208
+
209
+ def replyNotFound
210
+ @statusCode = 404
211
+ @body << @headerHtml
212
+ @body << '<p>Not found!</p>'
213
+ @body << @footer
214
+ end
215
+
216
+ def replyDefaultBody
217
+ @body << @headerHtml
218
+ @body << @defaultBody
219
+ @body << @footer
220
+ end
221
+
222
+ def replyKeyLookupForm
223
+ @body << @headerHtml
224
+ @body << @lookupKeysForm
225
+ @body << @footer
226
+ end
227
+
228
+ def replyIdentityLookupForm
229
+ @body << @headerHtml
230
+ @body << @lookupIdentitiesForm
231
+ @body << @footer
232
+ end
233
+
234
+ def replyKeyUploadForm
235
+ @body << @headerHtml
236
+ @body << @uploadKeyForm
237
+ @body << @footer
238
+ end
239
+
240
+ def replyIdentityUploadForm
241
+ @body << @headerHtml
242
+ @body << @uploadIdentityForm
243
+ @body << @footer
244
+ end
245
+
246
+ def replyFile(env)
247
+ fileName = env['REQUEST_PATH'].sub(/^.*\/media\//,'')
248
+ if File.exists?(fileName) then
249
+ fileExt = File.extname(fileName).sub(/^\./,'')
250
+ @header['Content-Type'] = @mimeMap[fileExt] if
251
+ @mimeMap.has_key?(fileExt)
252
+ @body << File.open(fileName,'r').read
253
+ end
254
+ end
255
+
256
+ def decodeQueryString(env)
257
+ queryHash = Hash.new
258
+ URI.decode_www_form(env['QUERY_STRING']).each do | aKeyValue |
259
+ queryHash[aKeyValue[0]] = aKeyValue[1]
260
+ end
261
+ queryHash
262
+ end
263
+
264
+ def call(env)
265
+ #
266
+ # initialize the response parts
267
+ #
268
+ @statusCode = 200
269
+ @header = {"Content-Type" => "text/html; charset=utf-8"}
270
+ @body = Array.new
271
+ #
272
+ # decode the request
273
+ #
274
+ begin
275
+ puts ppEnv(env) if @debug
276
+ case env['REQUEST_METHOD']
277
+ when 'POST'
278
+ case env['REQUEST_PATH']
279
+ when /^\/pks\/add$/i
280
+ storeKey(env)
281
+ when /^\/pis\/add$/i
282
+ storeIdentity(env)
283
+ else
284
+ replyNotImplemented
285
+ end
286
+ when 'GET'
287
+ case env['REQUEST_PATH']
288
+ when /^\/pks\/add$/i
289
+ replyKeyUploadForm
290
+ when /^\/pis\/add$/i
291
+ replyIdentityUploadForm
292
+ when /^\/pks\/lookup$/i
293
+ queryString = decodeQueryString(env)
294
+ if queryString.has_key?('op') then
295
+ case queryString['op']
296
+ when /get/i
297
+ lookUpKey(queryString)
298
+ when /index/i
299
+ indexKeys(queryString)
300
+ else
301
+ replyKeyLookupForm
302
+ end
303
+ else
304
+ replyBadRequest("No op field in queryString")
305
+ end
306
+ when /^\/pis\/lookup$/i
307
+ queryString = decodeQueryString(env)
308
+ if queryString.has_key?('op') then
309
+ case queryString['op']
310
+ when /get/i
311
+ lookUpIdentity(queryString)
312
+ when /index/i
313
+ indexIdentities(queryString)
314
+ else
315
+ replyIdentityLookupForm
316
+ end
317
+ else
318
+ replyBadRequest("No op field in queryString")
319
+ end
320
+ when /media\//i
321
+ replyFile(env)
322
+ when /reload$/i
323
+ loadKeys
324
+ loadIdentities
325
+ loadHtml
326
+ replyDefaultBody
327
+ else
328
+ replyDefaultBody
329
+ end
330
+ else
331
+ replyBadRequest("Unknown request method")
332
+ end
333
+ rescue Exception => exception
334
+ replyInternalError(env, exception)
335
+ end
336
+ #
337
+ # send the response
338
+ #
339
+ if @debug then
340
+ puts "SimpleHKP reply:"
341
+ pp @statusCode
342
+ pp @header
343
+ puts @body.join("\n")
344
+ end
345
+ [ @statusCode, @header, @body.flatten ]
346
+ end
347
+
348
+ end
349
+ end