blob: baa769c4ae21ca60ef8a1e49ee4d9cb872794a6e (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
# frozen_string_literal: true
##
# The WebauthnListener Response class is used by the WebauthnListener to create
# responses to be sent to the Gem host. It creates a Net::HTTPResponse instance
# when initialized and can be converted to the appropriate format to be sent by a socket using `to_s`.
# Net::HTTPResponse instances cannot be directly sent over a socket.
#
# Types of response classes:
# - OkResponse
# - NoContentResponse
# - BadRequestResponse
# - NotFoundResponse
# - MethodNotAllowedResponse
#
# Example usage:
#
# server = TCPServer.new(0)
# socket = server.accept
#
# response = OkResponse.for("https://rubygems.example")
# socket.print response.to_s
# socket.close
#
class Gem::WebauthnListener
class Response
attr_reader :http_response
def self.for(host)
new(host)
end
def initialize(host)
@host = host
build_http_response
end
def to_s
status_line = "HTTP/#{@http_response.http_version} #{@http_response.code} #{@http_response.message}\r\n"
headers = @http_response.to_hash.map {|header, value| "#{header}: #{value.join(", ")}\r\n" }.join + "\r\n"
body = @http_response.body ? "#{@http_response.body}\n" : ""
status_line + headers + body
end
private
# Must be implemented in subclasses
def code
raise NotImplementedError
end
def reason_phrase
raise NotImplementedError
end
def body; end
def build_http_response
response_class = Net::HTTPResponse::CODE_TO_OBJ[code.to_s]
@http_response = response_class.new("1.1", code, reason_phrase)
@http_response.instance_variable_set(:@read, true)
add_connection_header
add_access_control_headers
add_body
end
def add_connection_header
@http_response["connection"] = "close"
end
def add_access_control_headers
@http_response["access-control-allow-origin"] = @host
@http_response["access-control-allow-methods"] = "POST"
@http_response["access-control-allow-headers"] = %w[Content-Type Authorization x-csrf-token]
end
def add_body
return unless body
@http_response["content-type"] = "text/plain"
@http_response["content-length"] = body.bytesize
@http_response.instance_variable_set(:@body, body)
end
end
class OkResponse < Response
private
def code
200
end
def reason_phrase
"OK"
end
def body
"success"
end
end
class NoContentResponse < Response
private
def code
204
end
def reason_phrase
"No Content"
end
end
class BadRequestResponse < Response
private
def code
400
end
def reason_phrase
"Bad Request"
end
def body
"missing code parameter"
end
end
class NotFoundResponse < Response
private
def code
404
end
def reason_phrase
"Not Found"
end
end
class MethodNotAllowedResponse < Response
private
def code
405
end
def reason_phrase
"Method Not Allowed"
end
def add_access_control_headers
super
@http_response["allow"] = %w[GET OPTIONS]
end
end
end
|