Commit | Line | Data |
---|---|---|
bac41a7b A |
1 | /* |
2 | * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. | |
3 | * | |
4 | * The contents of this file constitute Original Code as defined in and are | |
5 | * subject to the Apple Public Source License Version 1.2 (the 'License'). | |
6 | * You may not use this file except in compliance with the License. Please obtain | |
7 | * a copy of the License at http://www.apple.com/publicsource and read it before | |
8 | * using this file. | |
9 | * | |
10 | * This Original Code and all software distributed under the License are | |
11 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS | |
12 | * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT | |
13 | * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | |
14 | * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the | |
15 | * specific language governing rights and limitations under the License. | |
16 | */ | |
17 | ||
18 | ||
19 | // | |
20 | // http-protocol - HTTP protocol objects | |
21 | // | |
22 | // HTTP Transfers succeed (state() == finished) if the HTTP protocol was successfully | |
23 | // observed. This means that even 300/400/500 type results are "successful" as far | |
24 | // as state() is concerned. ResultClass() will attempt to classify both successful and | |
25 | // unsuccessful outcomes, and errorDescription() is the primary HTTP response line | |
26 | // (HTTP/1.n ccc some-string). HTTP Transfers fail (state() == failed) only if they can't | |
27 | // talk to the server, or a protocol violation (or unimplemented feature) is detected. | |
28 | // Deal with it. | |
29 | // | |
30 | // Note that the protected flag deferSendRequest allows the state sequencer to be | |
31 | // interrupted at the idle stage (before an HTTP request is sent over the virtual wire). | |
32 | // This is used by the https protocol driver to "wedge in" the SSL negotiation. Not very | |
33 | // elegant, but it works. | |
34 | // | |
35 | // This implementation of the http protocol includes http proxy operation. As a result, | |
36 | // it is very important to distinguish the various HostTargets and Targets involved: | |
37 | // Connection::hostTarget is the host we're talking to - it could be a proxy. | |
38 | // Transfer::target.host is the host we're trying to reach. | |
39 | // From the HTTPConnection's point of view: | |
40 | // hostTarget may be a proxy or the destination | |
41 | // target().host is always the host we're trying to reach | |
42 | // If we're not in proxy mode, these two are usually the same (caveat tester). | |
43 | // | |
44 | #include "http-protocol.h" | |
45 | #include "netparameters.h" | |
46 | ||
47 | ||
48 | namespace Security { | |
49 | namespace Network { | |
50 | ||
51 | ||
52 | // | |
53 | // Construct the protocol object | |
54 | // | |
55 | HTTPProtocol::HTTPProtocol(Manager &mgr, const char *scheme) : Protocol(mgr, scheme) | |
56 | { | |
57 | } | |
58 | ||
59 | ||
60 | // | |
61 | // Create a Transfer object for our protocol | |
62 | // | |
63 | HTTPProtocol::HTTPTransfer *HTTPProtocol::makeTransfer(const Target &target, Operation operation) | |
64 | { | |
65 | return new HTTPTransfer(*this, target, operation, defaultHttpPort); | |
66 | } | |
67 | ||
68 | ||
69 | // | |
70 | // Construct an HTTPConnection object | |
71 | // | |
72 | HTTPProtocol::HTTPConnection::HTTPConnection(Protocol &proto, | |
73 | const HostTarget &hostTarget) | |
74 | : TCPConnection(proto, hostTarget), | |
29654253 | 75 | subVersion(defaultSubVersion), |
bac41a7b A |
76 | state(errorState), deferSendRequest(false) |
77 | { | |
78 | const HostTarget &host = proxyHostTarget(); | |
79 | connect(host.host(), host.port()); | |
80 | state = connecting; | |
81 | } | |
82 | ||
83 | ||
84 | // | |
85 | // Start a request/response transaction on this Connection. This puts out all the | |
86 | // HTTP request headers in one fell swoop (but not any request body). | |
87 | // The Connection must be in idle state. | |
88 | // | |
89 | void HTTPProtocol::HTTPConnection::request(const char *operation) | |
90 | { | |
91 | mOperation = operation; | |
92 | if (state == idle) // already waiting for request | |
93 | sendRequest(); | |
94 | } | |
95 | ||
96 | void HTTPProtocol::HTTPConnection::sendRequest() | |
97 | { | |
98 | assert(state == idle); | |
99 | ||
29654253 A |
100 | // what version of HTTP/1 shall we use? |
101 | subVersion = getv<int>(kNetworkHttpUseVersion, defaultSubVersion); | |
102 | ||
bac41a7b A |
103 | flushOutput(false); // hold output until we're done |
104 | const Target &target = this->target(); | |
105 | if (transfer().useProxyHeaders()) { | |
29654253 | 106 | printfe("%s %s HTTP/1.%d", mOperation.c_str(), target.urlForm().c_str(), subVersion); |
bac41a7b A |
107 | authorizationHeader("Proxy-Authorization", hostTarget, |
108 | kNetworkGenericProxyUsername, kNetworkGenericProxyPassword); | |
109 | } else { | |
29654253 | 110 | printfe("%s %s HTTP/1.%d", mOperation.c_str(), target.path.c_str(), subVersion); |
bac41a7b A |
111 | } |
112 | hostHeader(); | |
113 | authorizationHeader("Authorization", target, | |
114 | kNetworkGenericUsername, kNetworkGenericPassword); | |
115 | printfe("User-Agent: %s", | |
116 | getv<string>(kNetworkHttpUserAgent, "MacNetwork/1.0 (Macintosh)").c_str()); | |
117 | ||
118 | // if restarting, add a Range header | |
119 | if (int restartOffset = getv<int>(kNetworkRestartPosition, 0)) { | |
120 | printfe("Range: bytes=%d-", restartOffset); | |
121 | } | |
122 | ||
123 | // add other headers set by caller, if any | |
124 | { | |
125 | string otherHeaders; | |
126 | if (get(kNetworkHttpMoreHeaders, otherHeaders)) { | |
127 | // launder and rinse - don't let the caller screw up the HTTP header structure | |
128 | static const char lineEndings[] = "\r\n"; | |
129 | const char *p = otherHeaders.c_str(); | |
130 | while (const char *q = strpbrk(p, lineEndings)) { | |
131 | if (q > p) | |
132 | printfe("%.*s", q - p, p); | |
133 | p = q + strspn(q, lineEndings); | |
134 | } | |
135 | // now send any last (unterminated) line | |
136 | if (*p) | |
137 | printfe("%s", p); | |
138 | } | |
139 | } | |
140 | ||
141 | // add fields used for upstream transfer, if any, and initiate | |
142 | if (transfer().hasSource()) { | |
143 | Source &source = transfer().source(); | |
144 | size_t size = source.getSize(); | |
145 | if (size == Source::unknownSize) { | |
146 | //@@@ try to use Transfer-encoding: chunked -- for now, just use EOF delimiting | |
147 | } else { | |
148 | printfe("Content-length: %ld", size); | |
149 | } | |
29654253 | 150 | printfe("Content-Type: %s", getv<string>(kNetworkHttpPostContentType, "text/plain").c_str()); |
bac41a7b A |
151 | printfe(""); // end of headers |
152 | mode(source); // initiate autoWrite mode | |
153 | } else { | |
154 | printfe(""); // end of headers, no data | |
155 | } | |
156 | ||
157 | flushOutput(); // release pent-up output | |
158 | mode(lineInput); // line input mode | |
159 | state = primaryResponse; // prime the state machine | |
160 | } | |
161 | ||
162 | void HTTPProtocol::HTTPConnection::hostHeader() | |
163 | { | |
164 | const HostTarget &host = target().host; | |
165 | if (host.port()) | |
166 | printfe("Host: %s:%d", host.host().name().c_str(), host.port()); | |
167 | else | |
168 | printfe("Host: %s", host.host().name().c_str()); | |
169 | } | |
170 | ||
171 | void HTTPProtocol::HTTPConnection::authorizationHeader(const char *headerName, | |
172 | const HostTarget &host, | |
173 | ParameterSource::Key userKey, ParameterSource::Key passKey) | |
174 | { | |
175 | string username = host.haveUserPass() ? host.username() : getv<string>(userKey); | |
176 | string password = host.haveUserPass() ? host.password() : getv<string>(passKey); | |
177 | //@@@ only "Basic" authentication supported for now | |
178 | if (!username.empty()) { | |
179 | //@@@ ad-hoc Base64 encoding. Replace with suitable stream encoder when available | |
180 | static const char alphabet[] = | |
181 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |
182 | string token = username + ":" + password; | |
183 | char *buffer = new char[4 * token.length() / 3 + 2]; // just enough | |
184 | const char *src = token.c_str(), *end = src + token.length(); | |
185 | char *outp = buffer; | |
186 | while (src < end) { | |
187 | uint32 binary = src[0] << 16; | |
188 | if (src+1 < end) | |
189 | binary |= src[1] << 8 | src[2]; | |
190 | *outp++ = alphabet[(binary >> 18) & 0x3F]; | |
191 | *outp++ = alphabet[(binary >> 12) & 0x3F]; | |
192 | *outp++ = (src+1 < end) ? alphabet[(binary >> 6) & 0x3F] : '='; | |
193 | *outp++ = (src+2 < end) ? alphabet[binary & 0x3F] : '='; | |
194 | src += 3; | |
195 | } | |
196 | *outp = '\0'; | |
197 | printfe("%s: Basic %s", headerName, buffer); | |
198 | delete[] buffer; | |
199 | } | |
200 | } | |
201 | ||
202 | ||
203 | // | |
204 | // This is the master state transit machine for HTTP. | |
205 | // | |
206 | void HTTPProtocol::HTTPConnection::transit(Event event, char *input, size_t length) | |
207 | { | |
208 | switch (event) { | |
209 | case autoWriteDone: // ingore: it's asynchronous to our state machine | |
210 | return; | |
211 | case endOfInput: // most of the time, this is a protocol error, so filter it out now | |
212 | switch (state) { | |
213 | case idle: | |
214 | case readWholeBody: // expected | |
215 | break; | |
216 | case primaryResponse: // Connection failed; restart it | |
217 | return restart(); | |
218 | default: // unexpected; fail | |
219 | UnixError::throwMe(ECONNRESET); // @@@ diagnostic? | |
220 | } | |
221 | break; | |
222 | case connectionDone: // TCP connection complete or failed | |
223 | { | |
224 | assert(state == connecting); | |
225 | int error = length; | |
226 | observe(Observer::connectEvent, &error); | |
227 | if (error) { // retry | |
228 | connect(); | |
229 | } else { // connection good | |
230 | state = idle; | |
231 | if (!deferSendRequest) { // (subclass wants to wedge in) | |
232 | mode(lineInput); | |
233 | sendRequest(); | |
234 | } | |
235 | } | |
236 | } | |
237 | return; | |
238 | default: | |
239 | break; | |
240 | } | |
241 | ||
242 | switch (state) { | |
243 | case primaryResponse: | |
244 | { | |
245 | assert(mode() == lineInput); | |
246 | observe(Observer::protocolReceive, input); | |
247 | transfer().httpResponse() = input; // remember response for caller | |
248 | // --> HTTP/major.minor status reason-phrase | |
249 | int reasonPos; | |
250 | if (sscanf(input, "HTTP/%d.%d %u %n", | |
251 | &httpVersionMajor, &httpVersionMinor, | |
252 | &transfer().httpResponseCode(), &reasonPos) != 3) { | |
253 | // malformed response header | |
254 | fail(Transfer::remoteFailure); | |
255 | } | |
256 | ||
257 | if (httpVersionMajor != 1) // wrong major protocol Version | |
258 | fail(Transfer::remoteFailure); | |
259 | if (httpVersionMinor < 0 || httpVersionMinor > 1) | |
260 | fail(Transfer::remoteFailure); | |
261 | ||
262 | // notify the URLAccess emulation that we have the result code | |
263 | observe (Observer::resultCodeReady); | |
264 | ||
265 | // okay, we grok the version. We'll proceed for now reading headers etc. | |
266 | state = readHeaders; | |
267 | ||
268 | // we got input from the server, so this Connection is now confirmed good | |
269 | restarting(false); | |
270 | break; | |
271 | } | |
272 | case readHeaders: | |
273 | { | |
274 | assert(mode() == lineInput); | |
275 | if (length) { // another header | |
276 | headers().add(input); | |
277 | observe(Observer::protocolReceive, input); | |
278 | } else { // end of headers | |
279 | // we are now handling the transition from response headers to response body | |
280 | observe(Observer::protocolReceive, "** END OF HEADER **"); | |
29654253 | 281 | observe(Observer::downloading, input); |
bac41a7b A |
282 | |
283 | // Transfer-Encoding overrides Content-Length as per RFC2616 p.34 | |
284 | if (const char *encoding = headers().find("Transfer-Encoding")) { | |
285 | if (!strcasecmp(encoding, "chunked")) { | |
286 | // eat input in chunks | |
287 | state = chunkHeader; | |
288 | // mode remains lineInput | |
289 | break; | |
290 | } else if (!strcasecmp(encoding, "identity")) { | |
291 | // allowed and ignored | |
292 | } else { | |
293 | // unrecognized transfer-encoding | |
294 | fail(Transfer::remoteFailure); | |
295 | } | |
296 | } | |
297 | // no transfer-encoding (or transfer-encoding: identity): big gulp mode | |
ded8f8e2 | 298 | state = readWholeBody; |
bac41a7b A |
299 | if (const char *lengthArg = headers().find("Content-Length")) { |
300 | size_t length = strtol(lengthArg, NULL, 10); | |
301 | sink().setSize(length); | |
ded8f8e2 A |
302 | if (length > 0) |
303 | mode(sink(), length); | |
304 | else // null body, already done | |
305 | finish(); | |
306 | } else { // read until EOI | |
bac41a7b A |
307 | mode(sink()); |
308 | } | |
bac41a7b A |
309 | } |
310 | break; | |
311 | } | |
312 | case chunkHeader: | |
313 | { | |
314 | assert(mode() == lineInput); | |
315 | // line should be (just) a hex number, sans "0x" prefix or spaces. Be strict | |
316 | char *endOfMatch; | |
317 | size_t chunkLength = strtol(input, &endOfMatch, 0x10); | |
318 | if (length == 0 || endOfMatch == input) // no valid number | |
319 | fail(Transfer::remoteFailure); | |
320 | if (chunkLength) { | |
df0e469f | 321 | secdebug("http", "reading chunk of %ld bytes", chunkLength); |
bac41a7b A |
322 | mode(sink(), chunkLength); |
323 | state = chunkDownload; | |
324 | } else { | |
df0e469f | 325 | secdebug("http", "final chunk marker"); |
bac41a7b A |
326 | state = chunkTrailer; |
327 | observe(Observer::protocolReceive, "** END OF DATA **"); | |
328 | } | |
329 | break; | |
330 | } | |
331 | case chunkGap: | |
332 | { | |
333 | assert(mode() == lineInput); | |
334 | state = chunkHeader; | |
335 | break; | |
336 | } | |
337 | case chunkTrailer: | |
338 | { | |
339 | assert(mode() == lineInput); | |
340 | if (input[0] == '\0') { // end of trailer | |
341 | finish(); | |
342 | } else { | |
343 | headers().add(input); | |
344 | observe(Observer::protocolReceive, input); | |
345 | } | |
346 | break; | |
347 | } | |
348 | case chunkDownload: | |
349 | { | |
350 | assert(event == autoReadDone); | |
351 | state = chunkGap; | |
352 | mode(lineInput); | |
353 | break; | |
354 | } | |
355 | case readWholeBody: | |
356 | { | |
357 | assert(event == autoReadDone || event == endOfInput); | |
358 | finish(); | |
359 | break; | |
360 | } | |
361 | case idle: | |
362 | { | |
363 | // the only asynchronous event in idle mode is a connection drop | |
df0e469f | 364 | secdebug("http", |
ded8f8e2 | 365 | "%p event %d while idle; destroying connection", this, event); |
bac41a7b A |
366 | abort(); |
367 | state = dead; | |
368 | } | |
369 | break; | |
370 | default: | |
371 | assert(false); | |
372 | } | |
373 | } | |
374 | ||
375 | void HTTPProtocol::HTTPConnection::transitError(const CssmCommonError &error) | |
376 | { | |
377 | // note that fail(const char * [, OSStatus]) has already called setError | |
378 | fail(true); // fail transfer and throw out connection | |
379 | } | |
380 | ||
381 | ||
382 | void HTTPProtocol::HTTPConnection::finish() | |
383 | { | |
ded8f8e2 | 384 | flushInput(); // clear excess garbage input (resynchronize) |
bac41a7b | 385 | chooseRetain(); // shall we keep the Connection? |
bac41a7b A |
386 | mode(lineInput); // ensure valid input mode |
387 | state = idle; // idle state | |
ded8f8e2 | 388 | Connection::finish(); // finish this transfer |
bac41a7b A |
389 | } |
390 | ||
391 | ||
392 | void HTTPProtocol::HTTPConnection::fail(bool forceDrop) | |
393 | { | |
394 | if (forceDrop) | |
395 | retain(false); // drop the Connection | |
396 | else | |
397 | chooseRetain(); // perhaps keep it | |
398 | Connection::fail(); // fail this transfer | |
399 | } | |
400 | ||
401 | ||
402 | bool HTTPProtocol::HTTPConnection::validate() | |
403 | { | |
404 | assert(state == idle); | |
405 | tickle(); // may change state | |
406 | return state == idle; | |
407 | } | |
408 | ||
409 | ||
410 | void HTTPProtocol::HTTPConnection::chooseRetain() | |
411 | { | |
412 | // figure out whether to stay alive | |
413 | retain(strcasecmp(headers().find("Connection", "Keep"), "Close")); | |
414 | //@@@ need to handle the HTTP/1.0 case | |
415 | } | |
416 | ||
417 | ||
418 | // | |
419 | // Transfer objects | |
420 | // | |
421 | HTTPProtocol::HTTPTransfer::HTTPTransfer(Protocol &proto, | |
422 | const Target &tgt, Operation operation, IPPort defaultPort) | |
423 | : Transfer(proto, tgt, operation, defaultPort), | |
424 | mResultClass(unclassifiedFailure) | |
425 | { | |
426 | } | |
427 | ||
428 | void HTTPProtocol::HTTPTransfer::start() | |
429 | { | |
430 | // HTTP servers can serve both proxy requests and direct requests, | |
431 | // and can be pooled based on that fact. Use proxy==target here. | |
432 | const HostTarget &host = proxyHostTarget(); | |
433 | HTTPConnection *connection = protocol.manager.findConnection<HTTPConnection>(host); | |
434 | if (connection == NULL) | |
435 | connection = new HTTPConnection(protocol, host); | |
436 | connection->dock(this); | |
437 | startRequest(); | |
438 | } | |
439 | ||
440 | void HTTPProtocol::HTTPTransfer::abort() | |
441 | { | |
c25717e3 | 442 | observe(Observer::aborting); |
bac41a7b A |
443 | setError("aborted"); |
444 | connectionAs<HTTPConnection>().abort(); | |
445 | } | |
446 | ||
447 | void HTTPProtocol::HTTPConnection::abort() | |
448 | { | |
449 | close(); | |
450 | fail(true); | |
451 | } | |
452 | ||
453 | ||
454 | // | |
455 | // This lower-level request startup function can be called directly by children. | |
456 | // | |
457 | void HTTPProtocol::HTTPTransfer::startRequest() | |
458 | { | |
459 | const char *defaultForm; | |
460 | switch (operation()) { | |
461 | case Protocol::upload: defaultForm = "PUT"; break; | |
462 | case Protocol::transaction: defaultForm = "POST"; break; | |
463 | default: defaultForm = "GET"; break; | |
464 | } | |
465 | connectionAs<HTTPConnection>().request(getv<string>(kNetworkHttpCommand, defaultForm).c_str()); | |
466 | } | |
467 | ||
468 | ||
469 | // | |
470 | // Determine whether we should use the proxy form of HTTP headers. | |
471 | // By default, this is true iff we are used by a proxy Protocol. | |
472 | // However, children may override this determination. | |
473 | // | |
474 | bool HTTPProtocol::HTTPTransfer::useProxyHeaders() const | |
475 | { | |
476 | return protocol.isProxy(); | |
477 | } | |
478 | ||
479 | Transfer::ResultClass HTTPProtocol::HTTPTransfer::resultClass() const | |
480 | { | |
481 | switch (state()) { | |
482 | case failed: | |
483 | return mResultClass; | |
484 | case finished: | |
485 | { | |
486 | if (mResultClass != unclassifiedFailure) | |
487 | return mResultClass; // preclassified | |
488 | unsigned int code = httpResponseCode(); | |
ded8f8e2 | 489 | if (code == 401 || code == 407 || code == 305) // auth or proxy auth required |
bac41a7b | 490 | return authorizationFailure; |
ded8f8e2 A |
491 | else if (code / 100 == 3) // redirect (interpreted as success) |
492 | return success; | |
bac41a7b A |
493 | else if (code / 100 == 2) // success codes |
494 | return success; | |
495 | else // when in doubt, blame the remote end :-) | |
496 | return remoteFailure; | |
497 | } | |
498 | default: | |
499 | assert(false); | |
500 | return localFailure; | |
501 | } | |
502 | } | |
503 | ||
504 | ||
505 | void HTTPProtocol::HTTPTransfer::fail(ResultClass why, OSStatus how) | |
506 | { | |
507 | mResultClass = why; | |
508 | Error::throwMe(how); | |
509 | } | |
510 | ||
511 | ||
512 | // | |
513 | // Manage the HTTP version of a HeaderMap | |
514 | // | |
515 | void HTTPProtocol::HTTPHeaderMap::merge(string key, string &old, string newValue) | |
516 | { | |
517 | // duplicates must be CSV type; concatenate (RFC 2616; section 4.2) | |
518 | old = old + ", " + newValue; | |
519 | } | |
520 | ||
521 | ||
522 | } // end namespace Network | |
523 | } // end namespace Security |