1 |
|
|
/** |
2 |
|
|
* This file is part of the CernVM File System |
3 |
|
|
*/ |
4 |
|
|
|
5 |
|
|
#ifndef CVMFS_WEBAPI_FCGI_H_ |
6 |
|
|
#define CVMFS_WEBAPI_FCGI_H_ |
7 |
|
|
|
8 |
|
|
#include <netinet/in.h> |
9 |
|
|
#include <stdint.h> |
10 |
|
|
|
11 |
|
|
#include <cstring> |
12 |
|
|
#include <map> |
13 |
|
|
#include <string> |
14 |
|
|
|
15 |
|
|
// TODO(jblomer): deal with termination signals |
16 |
|
|
|
17 |
|
|
/** |
18 |
|
|
* Implements a simple FastCGI responder role. This responder cannot multi- |
19 |
|
|
* plex connections. See http://www.fastcgi.com/devkit/doc/fcgi-spec.html |
20 |
|
|
* It is supposed to be used in the following way. Don't forget to sanitize |
21 |
|
|
* everything you get out from fcgi. |
22 |
|
|
* |
23 |
|
|
* unsigned char *buf; |
24 |
|
|
* unsigned length; |
25 |
|
|
* uint64_t id = 0; |
26 |
|
|
* FastCgi::Event event; |
27 |
|
|
* while ((event = fcgi.NextEvent(&buf, &length, &id)) != FastCgi::kEventExit) { |
28 |
|
|
* switch (event) { |
29 |
|
|
* case FastCgi::kEventAbortReq: |
30 |
|
|
* fcgi.AbortRequest(); |
31 |
|
|
* break; |
32 |
|
|
* case FastCgi::kEventStdin: |
33 |
|
|
* // Use id to detect if this new input is part of the same request than |
34 |
|
|
* // the previous one. |
35 |
|
|
* // Process buf; if length == 0, the input stream is finished |
36 |
|
|
* // For POST: compare CONTENT-LENGTH with actual stream length |
37 |
|
|
* fcgi.GetParam("PARAMETER", ...) ... |
38 |
|
|
* fcgi.SendData("...", false); |
39 |
|
|
* fcgi.SendData("...", true); |
40 |
|
|
* fcgi.SendError("...", true); |
41 |
|
|
* fcgi.EndRequest(0); // or failure status code |
42 |
|
|
* break; |
43 |
|
|
* default: |
44 |
|
|
* abort(); |
45 |
|
|
* } |
46 |
|
|
* } |
47 |
|
|
*/ |
48 |
|
|
class FastCgi { |
49 |
|
|
public: |
50 |
|
|
enum Event { |
51 |
|
|
kEventExit = 0, |
52 |
|
|
kEventTransportError, |
53 |
|
|
kEventAbortReq, |
54 |
|
|
kEventStdin, |
55 |
|
|
}; |
56 |
|
|
|
57 |
|
|
FastCgi(); |
58 |
|
|
~FastCgi(); |
59 |
|
|
bool IsFcgi(); |
60 |
|
|
void AbortRequest(); |
61 |
|
|
void EndRequest(uint32_t exit_code); |
62 |
|
|
bool SendData(const std::string &data, bool finish); |
63 |
|
|
bool SendError(const std::string &data, bool finish); |
64 |
|
|
void ReturnBadRequest(const std::string &reason); |
65 |
|
|
void ReturnNotFound(); |
66 |
|
|
Event NextEvent(unsigned char **buf, unsigned *length, uint64_t *id); |
67 |
|
|
|
68 |
|
|
bool GetParam(const std::string &key, std::string *value); |
69 |
|
|
std::string DumpParams(); |
70 |
|
|
|
71 |
|
|
bool MkTcpSocket(const std::string &ip4_address, uint16_t port); |
72 |
|
|
|
73 |
|
|
private: |
74 |
|
|
static const int kCgiListnsockFileno = 0; |
75 |
|
|
|
76 |
|
|
static const unsigned kMaxContentLength; |
77 |
|
|
|
78 |
|
|
/** |
79 |
|
|
* Value for version component of Header. |
80 |
|
|
*/ |
81 |
|
|
static const int kVersion1 = 1; |
82 |
|
|
|
83 |
|
|
/** |
84 |
|
|
* Value for requestId component of Header. |
85 |
|
|
*/ |
86 |
|
|
static const int kNullRequestId = 0; |
87 |
|
|
|
88 |
|
|
/** |
89 |
|
|
* Mask for flags component of BeginRequestBody. |
90 |
|
|
*/ |
91 |
|
|
static const int kKeepConn = 1; |
92 |
|
|
|
93 |
|
|
/** |
94 |
|
|
* Values the server might query from the app. |
95 |
|
|
*/ |
96 |
|
|
static const char *kValueMaxConns; |
97 |
|
|
static const char *kValueMaxReqs; |
98 |
|
|
static const char *kValueMpxConns; |
99 |
|
|
|
100 |
|
|
/** |
101 |
|
|
* Values for type component of Header. |
102 |
|
|
*/ |
103 |
|
|
enum RecordType { |
104 |
|
|
kTypeBegin = 1, // Application record, WS --> App |
105 |
|
|
kTypeAbort = 2, // Application record, WS --> App |
106 |
|
|
kTypeEnd = 3, // Application record, App --> WS |
107 |
|
|
kTypeParams = 4, // Application record, WS --> App |
108 |
|
|
kTypeStdin = 5, // Application stream record, WS --> App |
109 |
|
|
kTypeStdout = 6, // Application stream record, App --> WS |
110 |
|
|
kTypeStderr = 7, // Application stream record, App --> WS |
111 |
|
|
kTypeData = 8, // Application stream record, WS --> App |
112 |
|
|
kTypeValues = 9, // Management record, WS --> App |
113 |
|
|
kTypeValuesResult = 10, // Management record, App --> WS |
114 |
|
|
kTypeUnknown = 11, // Management record, App --> WS |
115 |
|
|
}; |
116 |
|
|
|
117 |
|
|
/** |
118 |
|
|
* Values for role component of BeginRequestBody. |
119 |
|
|
*/ |
120 |
|
|
enum Role { |
121 |
|
|
kRoleResponder = 1, |
122 |
|
|
kRoleAuthorizer = 2, // unavailable in this implementation |
123 |
|
|
kRoleFilter = 3, // unavailable in this implementation |
124 |
|
|
}; |
125 |
|
|
|
126 |
|
|
/** |
127 |
|
|
* Values for protocolStatus component of EndRequestBody. |
128 |
|
|
*/ |
129 |
|
|
enum ProtocolStatus { |
130 |
|
|
kStatusReqComplete = 0, |
131 |
|
|
kStatusCantMpxConn = 1, |
132 |
|
|
kStatusOverloaded = 2, // unused in this implementation |
133 |
|
|
kStatusUnknownRole = 3, |
134 |
|
|
}; |
135 |
|
|
|
136 |
|
|
struct RawHeader { |
137 |
|
|
RawHeader() |
138 |
|
|
: version(kVersion1), type(0), request_id_b1(0), request_id_b0(0) |
139 |
|
|
, content_length_b1(0), content_length_b0(0), padding_length(0) |
140 |
|
|
, reserved(0) |
141 |
|
|
{ } |
142 |
|
|
unsigned char version; |
143 |
|
|
unsigned char type; |
144 |
|
|
unsigned char request_id_b1; |
145 |
|
|
unsigned char request_id_b0; |
146 |
|
|
unsigned char content_length_b1; |
147 |
|
|
unsigned char content_length_b0; |
148 |
|
|
unsigned char padding_length; |
149 |
|
|
unsigned char reserved; |
150 |
|
|
}; |
151 |
|
|
|
152 |
|
|
struct Header { |
153 |
|
|
unsigned char type; |
154 |
|
|
unsigned char padding_length; |
155 |
|
|
uint16_t request_id; |
156 |
|
|
uint16_t content_length; |
157 |
|
|
}; |
158 |
|
|
|
159 |
|
|
struct BeginRequestBody { |
160 |
|
|
unsigned char role_b1; |
161 |
|
|
unsigned char role_b0; |
162 |
|
|
unsigned char flags; |
163 |
|
|
unsigned char reserved[5]; |
164 |
|
|
}; |
165 |
|
|
|
166 |
|
|
struct EndRequestBody { |
167 |
|
|
EndRequestBody() |
168 |
|
|
: app_status_b3(0), app_status_b2(0), app_status_b1(0), app_status_b0(0) |
169 |
|
|
, protocol_status(0) |
170 |
|
|
{ |
171 |
|
|
memset(reserved, 0, 3); |
172 |
|
|
} |
173 |
|
|
unsigned char app_status_b3; |
174 |
|
|
unsigned char app_status_b2; |
175 |
|
|
unsigned char app_status_b1; |
176 |
|
|
unsigned char app_status_b0; |
177 |
|
|
unsigned char protocol_status; |
178 |
|
|
unsigned char reserved[3]; |
179 |
|
|
}; |
180 |
|
|
|
181 |
|
|
struct UnknownTypeBody { |
182 |
|
|
UnknownTypeBody() : type(0) { |
183 |
|
|
memset(reserved, 0, 7); |
184 |
|
|
} |
185 |
|
|
unsigned char type; |
186 |
|
|
unsigned char reserved[7]; |
187 |
|
|
}; |
188 |
|
|
|
189 |
|
|
bool CheckValidSource(const struct sockaddr_in &addr_in); |
190 |
|
|
void CloseConnection(); |
191 |
|
|
|
192 |
|
|
bool ReadHeader(int fd_transport, Header *header); |
193 |
|
|
bool ReadContent(uint16_t content_length, unsigned char padding_length); |
194 |
|
|
bool ReadBeginBody(int fd_transport, uint16_t *role, bool *keep_connection); |
195 |
|
|
bool ReadParams(const Header &first_header); |
196 |
|
|
|
197 |
|
|
bool ParseKvPair(const char *data, unsigned len, |
198 |
|
|
unsigned *nparsed, std::string *key, std::string *value); |
199 |
|
|
|
200 |
|
|
void ReplyUnknownType(int fd_transport, unsigned char received_type); |
201 |
|
|
void ReplyEndRequest(int fd_transport, uint16_t req_id, |
202 |
|
|
uint32_t exit_code, unsigned char status); |
203 |
|
|
bool ReplyStream(unsigned char type, |
204 |
|
|
const unsigned char *data, unsigned length); |
205 |
|
|
|
206 |
|
|
bool ProcessValues(const Header &request_header); |
207 |
|
|
|
208 |
|
|
inline uint16_t MkUint16(const unsigned char b1, const unsigned char b0) { |
209 |
|
|
return (static_cast<uint16_t>(b1) << 8) + static_cast<uint16_t>(b0); |
210 |
|
|
} |
211 |
|
|
|
212 |
|
|
inline void FlattenUint16(uint16_t val, unsigned char *b1, unsigned char *b0) |
213 |
|
|
{ |
214 |
|
|
*b1 = static_cast<unsigned char>((val >> 8) & 0xff); |
215 |
|
|
*b0 = static_cast<unsigned char>(val & 0xff); |
216 |
|
|
} |
217 |
|
|
|
218 |
|
|
unsigned AddShortKv(const std::string &key, const std::string &value, |
219 |
|
|
unsigned buf_size, unsigned char *buf); |
220 |
|
|
|
221 |
|
|
/** |
222 |
|
|
* kCgiListnsockFileno for fcgi processes spawned by the server, otherwise |
223 |
|
|
* filled by MkTcpSocket(). |
224 |
|
|
*/ |
225 |
|
|
int fd_sock_; |
226 |
|
|
bool is_tcp_socket_; |
227 |
|
|
|
228 |
|
|
unsigned char content_buf_[64 * 1024 + 255]; |
229 |
|
|
int fd_transport_; |
230 |
|
|
|
231 |
|
|
/** |
232 |
|
|
* Returned by NextEvent(). Has a different value for every request. Never |
233 |
|
|
* zero, so the application can initialize its id state to zero and detect |
234 |
|
|
* if a stdin event is for the same request or a new one. |
235 |
|
|
*/ |
236 |
|
|
uint64_t global_request_id_; |
237 |
|
|
|
238 |
|
|
int request_id_; |
239 |
|
|
bool keep_connection_; |
240 |
|
|
std::map<std::string, std::string> params_; |
241 |
|
|
}; |
242 |
|
|
|
243 |
|
|
#endif // CVMFS_WEBAPI_FCGI_H_ |