/src/libcups/cups/request.c
Line | Count | Source (jump to first uncovered line) |
1 | | // |
2 | | // IPP utilities for CUPS. |
3 | | // |
4 | | // Copyright © 2021-2025 by OpenPrinting. |
5 | | // Copyright © 2007-2018 by Apple Inc. |
6 | | // Copyright © 1997-2007 by Easy Software Products. |
7 | | // |
8 | | // Licensed under Apache License v2.0. See the file "LICENSE" for more |
9 | | // information. |
10 | | // |
11 | | |
12 | | #include "cups-private.h" |
13 | | #include <fcntl.h> |
14 | | #include <sys/stat.h> |
15 | | #if defined(_WIN32) || defined(__EMX__) |
16 | | # include <io.h> |
17 | | #else |
18 | | # include <unistd.h> |
19 | | #endif // _WIN32 || __EMX__ |
20 | | #ifndef O_BINARY |
21 | 0 | # define O_BINARY 0 |
22 | | #endif // O_BINARY |
23 | | #ifndef MSG_DONTWAIT |
24 | | # define MSG_DONTWAIT 0 |
25 | | #endif // !MSG_DONTWAIT |
26 | | |
27 | | |
28 | | // |
29 | | // 'cupsDoFileRequest()' - Do an IPP request with a file. |
30 | | // |
31 | | // This function sends the IPP request and attached file to the specified |
32 | | // server, retrying and authenticating as necessary. The request is freed with |
33 | | // @link ippDelete@. |
34 | | // |
35 | | |
36 | | ipp_t * // O - Response data |
37 | | cupsDoFileRequest(http_t *http, // I - Connection to server or `CUPS_HTTP_DEFAULT` |
38 | | ipp_t *request, // I - IPP request |
39 | | const char *resource, // I - HTTP resource for POST |
40 | | const char *filename) // I - File to send or `NULL` for none |
41 | 0 | { |
42 | 0 | ipp_t *response; // IPP response data |
43 | 0 | int infile; // Input file |
44 | | |
45 | |
|
46 | 0 | DEBUG_printf("cupsDoFileRequest(http=%p, request=%p(%s), resource=\"%s\", filename=\"%s\")", (void *)http, (void *)request, request ? ippOpString(request->request.op.operation_id) : "?", resource, filename); |
47 | |
|
48 | 0 | if (filename) |
49 | 0 | { |
50 | 0 | if ((infile = open(filename, O_RDONLY | O_BINARY)) < 0) |
51 | 0 | { |
52 | | // Can't get file information! |
53 | 0 | _cupsSetError(errno == ENOENT ? IPP_STATUS_ERROR_NOT_FOUND : IPP_STATUS_ERROR_NOT_AUTHORIZED, NULL, false); |
54 | |
|
55 | 0 | ippDelete(request); |
56 | |
|
57 | 0 | return (NULL); |
58 | 0 | } |
59 | 0 | } |
60 | 0 | else |
61 | 0 | { |
62 | 0 | infile = -1; |
63 | 0 | } |
64 | | |
65 | 0 | response = cupsDoIORequest(http, request, resource, infile, -1); |
66 | |
|
67 | 0 | if (infile >= 0) |
68 | 0 | close(infile); |
69 | |
|
70 | 0 | return (response); |
71 | 0 | } |
72 | | |
73 | | |
74 | | // |
75 | | // 'cupsDoIORequest()' - Do an IPP request with file descriptors. |
76 | | // |
77 | | // This function sends the IPP request with the optional input file "infile" to |
78 | | // the specified server, retrying and authenticating as necessary. The request |
79 | | // is freed with @link ippDelete@. |
80 | | // |
81 | | // If "infile" is a valid file descriptor, @code cupsDoIORequest@ copies |
82 | | // all of the data from the file after the IPP request message. |
83 | | // |
84 | | // If "outfile" is a valid file descriptor, @code cupsDoIORequest@ copies |
85 | | // all of the data after the IPP response message to the file. |
86 | | // |
87 | | |
88 | | ipp_t * // O - Response data |
89 | | cupsDoIORequest(http_t *http, // I - Connection to server or `CUPS_HTTP_DEFAULT` |
90 | | ipp_t *request, // I - IPP request |
91 | | const char *resource, // I - HTTP resource for POST |
92 | | int infile, // I - File to read from or `-1` for none |
93 | | int outfile) // I - File to write to or `-1` for none |
94 | 0 | { |
95 | 0 | ipp_t *response = NULL; // IPP response data |
96 | 0 | size_t length = 0; // Content-Length value |
97 | 0 | http_status_t status; // Status of HTTP request |
98 | 0 | struct stat fileinfo; // File information |
99 | 0 | ssize_t bytes; // Number of bytes read/written |
100 | 0 | char buffer[32768]; // Output buffer |
101 | | |
102 | |
|
103 | 0 | DEBUG_printf("cupsDoIORequest(http=%p, request=%p(%s), resource=\"%s\", infile=%d, outfile=%d)", (void *)http, (void *)request, request ? ippOpString(request->request.op.operation_id) : "?", resource, infile, outfile); |
104 | | |
105 | | // Range check input... |
106 | 0 | if (!request || !resource) |
107 | 0 | { |
108 | 0 | ippDelete(request); |
109 | |
|
110 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), false); |
111 | |
|
112 | 0 | return (NULL); |
113 | 0 | } |
114 | | |
115 | | // Get the default connection as needed... |
116 | 0 | if (!http && (http = _cupsConnect()) == NULL) |
117 | 0 | { |
118 | 0 | ippDelete(request); |
119 | |
|
120 | 0 | return (NULL); |
121 | 0 | } |
122 | | |
123 | | // See if we have a file to send... |
124 | 0 | if (infile >= 0) |
125 | 0 | { |
126 | 0 | if (fstat(infile, &fileinfo)) |
127 | 0 | { |
128 | | // Can't get file information! |
129 | 0 | _cupsSetError(errno == EBADF ? IPP_STATUS_ERROR_NOT_FOUND : IPP_STATUS_ERROR_NOT_AUTHORIZED, NULL, false); |
130 | 0 | ippDelete(request); |
131 | |
|
132 | 0 | return (NULL); |
133 | 0 | } |
134 | | |
135 | | #ifdef _WIN32 |
136 | | if (fileinfo.st_mode & _S_IFDIR) |
137 | | #else |
138 | 0 | if (S_ISDIR(fileinfo.st_mode)) |
139 | 0 | #endif // _WIN32 |
140 | 0 | { |
141 | | // Can't send a directory... |
142 | 0 | _cupsSetError(IPP_STATUS_ERROR_NOT_POSSIBLE, strerror(EISDIR), false); |
143 | 0 | ippDelete(request); |
144 | |
|
145 | 0 | return (NULL); |
146 | 0 | } |
147 | | |
148 | 0 | #ifndef _WIN32 |
149 | 0 | if (!S_ISREG(fileinfo.st_mode)) |
150 | 0 | length = 0; // Chunk when piping |
151 | 0 | else |
152 | 0 | #endif // !_WIN32 |
153 | 0 | length = ippGetLength(request) + (size_t)fileinfo.st_size; |
154 | 0 | } |
155 | 0 | else |
156 | 0 | { |
157 | 0 | length = ippGetLength(request); |
158 | 0 | } |
159 | | |
160 | 0 | DEBUG_printf("2cupsDoIORequest: Request length=%ld, total length=%ld", (long)ippGetLength(request), (long)length); |
161 | | |
162 | | // Clear any "Local" authentication data since it is probably stale... |
163 | 0 | if (http->authstring && !strncmp(http->authstring, "Local ", 6)) |
164 | 0 | httpSetAuthString(http, NULL, NULL); |
165 | | |
166 | | // Loop until we can send the request without authorization problems. |
167 | 0 | while (response == NULL) |
168 | 0 | { |
169 | 0 | DEBUG_puts("2cupsDoIORequest: setup..."); |
170 | | |
171 | | // Send the request... |
172 | 0 | status = cupsSendRequest(http, request, resource, length); |
173 | |
|
174 | 0 | DEBUG_printf("2cupsDoIORequest: status=%d", status); |
175 | |
|
176 | 0 | if (status == HTTP_STATUS_CONTINUE && request->state == IPP_STATE_DATA && infile >= 0) |
177 | 0 | { |
178 | 0 | DEBUG_puts("2cupsDoIORequest: file write..."); |
179 | | |
180 | | // Send the file with the request... |
181 | 0 | #ifndef _WIN32 |
182 | 0 | if (S_ISREG(fileinfo.st_mode)) |
183 | 0 | #endif // _WIN32 |
184 | 0 | lseek(infile, 0, SEEK_SET); |
185 | |
|
186 | 0 | while ((bytes = read(infile, buffer, sizeof(buffer))) > 0) |
187 | 0 | { |
188 | 0 | if ((status = cupsWriteRequestData(http, buffer, (size_t)bytes)) |
189 | 0 | != HTTP_STATUS_CONTINUE) |
190 | 0 | break; |
191 | 0 | } |
192 | 0 | } |
193 | | |
194 | | // Get the server's response... |
195 | 0 | if (status <= HTTP_STATUS_CONTINUE || status == HTTP_STATUS_OK) |
196 | 0 | { |
197 | 0 | response = cupsGetResponse(http, resource); |
198 | 0 | status = httpGetStatus(http); |
199 | 0 | } |
200 | |
|
201 | 0 | DEBUG_printf("2cupsDoIORequest: status=%d", status); |
202 | |
|
203 | 0 | if (status == HTTP_STATUS_ERROR || (status >= HTTP_STATUS_BAD_REQUEST && status != HTTP_STATUS_UNAUTHORIZED && status != HTTP_STATUS_UPGRADE_REQUIRED)) |
204 | 0 | { |
205 | 0 | _cupsSetHTTPError(http, status); |
206 | 0 | break; |
207 | 0 | } |
208 | | |
209 | 0 | if (response && outfile >= 0) |
210 | 0 | { |
211 | | // Write trailing data to file... |
212 | 0 | while ((bytes = httpRead(http, buffer, sizeof(buffer))) > 0) |
213 | 0 | { |
214 | 0 | if (write(outfile, buffer, (size_t)bytes) < bytes) |
215 | 0 | break; |
216 | 0 | } |
217 | 0 | } |
218 | |
|
219 | 0 | if (http->state != HTTP_STATE_WAITING) |
220 | 0 | { |
221 | | // Flush any remaining data... |
222 | 0 | httpFlush(http); |
223 | 0 | } |
224 | 0 | } |
225 | | |
226 | | // Delete the original request and return the response... |
227 | 0 | ippDelete(request); |
228 | |
|
229 | 0 | return (response); |
230 | 0 | } |
231 | | |
232 | | |
233 | | // |
234 | | // 'cupsDoRequest()' - Do an IPP request. |
235 | | // |
236 | | // This function sends the IPP request to the specified server, retrying |
237 | | // and authenticating as necessary. The request is freed with @link ippDelete@. |
238 | | // |
239 | | |
240 | | ipp_t * // O - Response data |
241 | | cupsDoRequest(http_t *http, // I - Connection to server or `CUPS_HTTP_DEFAULT` |
242 | | ipp_t *request, // I - IPP request |
243 | | const char *resource) // I - HTTP resource for POST |
244 | 0 | { |
245 | 0 | DEBUG_printf("cupsDoRequest(http=%p, request=%p(%s), resource=\"%s\")", (void *)http, (void *)request, request ? ippOpString(request->request.op.operation_id) : "?", resource); |
246 | |
|
247 | 0 | return (cupsDoIORequest(http, request, resource, -1, -1)); |
248 | 0 | } |
249 | | |
250 | | |
251 | | // |
252 | | // 'cupsGetResponse()' - Get a response to an IPP request. |
253 | | // |
254 | | // Use this function to get the response for an IPP request sent using |
255 | | // @link cupsSendRequest@. For requests that return additional data, use |
256 | | // @link cupsReadResponseData@ after getting a successful response, |
257 | | // otherwise call @link httpFlush@ to complete the response processing. |
258 | | // |
259 | | |
260 | | ipp_t * // O - Response or `NULL` on HTTP error |
261 | | cupsGetResponse(http_t *http, // I - Connection to server or `CUPS_HTTP_DEFAULT` |
262 | | const char *resource) // I - HTTP resource for POST |
263 | 0 | { |
264 | 0 | http_status_t status; // HTTP status |
265 | 0 | ipp_state_t state; // IPP read state |
266 | 0 | ipp_t *response = NULL; // IPP response |
267 | | |
268 | |
|
269 | 0 | DEBUG_printf("cupsGetResponse(http=%p, resource=\"%s\")", (void *)http, resource); |
270 | 0 | DEBUG_printf("1cupsGetResponse: http->state=%d", http ? http->state : HTTP_STATE_ERROR); |
271 | | |
272 | | // Connect to the default server as needed... |
273 | 0 | if (!http) |
274 | 0 | { |
275 | 0 | _cups_globals_t *cg = _cupsGlobals(); |
276 | | // Pointer to library globals |
277 | |
|
278 | 0 | if ((http = cg->http) == NULL) |
279 | 0 | { |
280 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No active connection."), true); |
281 | 0 | DEBUG_puts("1cupsGetResponse: No active connection - returning NULL."); |
282 | 0 | return (NULL); |
283 | 0 | } |
284 | 0 | } |
285 | | |
286 | 0 | if (http->state != HTTP_STATE_POST_RECV && http->state != HTTP_STATE_POST_SEND) |
287 | 0 | { |
288 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No request sent."), true); |
289 | 0 | DEBUG_puts("1cupsGetResponse: Not in POST state - returning NULL."); |
290 | 0 | return (NULL); |
291 | 0 | } |
292 | | |
293 | | // Check for an unfinished chunked request... |
294 | 0 | if (http->state == HTTP_STATE_POST_RECV && http->data_encoding == HTTP_ENCODING_CHUNKED) |
295 | 0 | { |
296 | | // Send a 0-length chunk to finish off the request... |
297 | 0 | DEBUG_puts("2cupsGetResponse: Finishing chunked POST..."); |
298 | |
|
299 | 0 | if (httpWrite(http, "", 0) < 0) |
300 | 0 | { |
301 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to finish request."), true); |
302 | 0 | return (NULL); |
303 | 0 | } |
304 | 0 | } |
305 | | |
306 | | // Wait for a response from the server... |
307 | 0 | DEBUG_printf("2cupsGetResponse: Update loop, http->status=%d...", http->status); |
308 | |
|
309 | 0 | do |
310 | 0 | { |
311 | 0 | status = httpUpdate(http); |
312 | 0 | } |
313 | 0 | while (status == HTTP_STATUS_CONTINUE); |
314 | |
|
315 | 0 | DEBUG_printf("2cupsGetResponse: status=%d", status); |
316 | |
|
317 | 0 | if (status == HTTP_STATUS_OK) |
318 | 0 | { |
319 | | // Get the IPP response... |
320 | 0 | response = ippNew(); |
321 | |
|
322 | 0 | while ((state = ippRead(http, response)) != IPP_STATE_DATA) |
323 | 0 | { |
324 | 0 | if (state == IPP_STATE_ERROR) |
325 | 0 | break; |
326 | 0 | } |
327 | |
|
328 | 0 | if (state == IPP_STATE_ERROR) |
329 | 0 | { |
330 | | // Flush remaining data and delete the response... |
331 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to read response."), true); |
332 | 0 | DEBUG_puts("1cupsGetResponse: IPP read error!"); |
333 | |
|
334 | 0 | httpFlush(http); |
335 | |
|
336 | 0 | ippDelete(response); |
337 | 0 | response = NULL; |
338 | |
|
339 | 0 | http->status = HTTP_STATUS_ERROR; |
340 | 0 | http->error = EINVAL; |
341 | 0 | } |
342 | 0 | } |
343 | 0 | else if (status != HTTP_STATUS_ERROR) |
344 | 0 | { |
345 | | // Flush any error message... |
346 | 0 | httpFlush(http); |
347 | |
|
348 | 0 | _cupsSetHTTPError(http, status); |
349 | | |
350 | | // Then handle encryption and authentication... |
351 | 0 | if (status == HTTP_STATUS_UNAUTHORIZED) |
352 | 0 | { |
353 | | // See if we can do authentication... |
354 | 0 | DEBUG_puts("2cupsGetResponse: Need authorization..."); |
355 | |
|
356 | 0 | if (cupsDoAuthentication(http, "POST", resource)) |
357 | 0 | { |
358 | 0 | if (!httpConnectAgain(http, 30000, NULL)) |
359 | 0 | http->status = HTTP_STATUS_ERROR; |
360 | 0 | } |
361 | 0 | else |
362 | 0 | { |
363 | 0 | http->status = HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED; |
364 | 0 | } |
365 | 0 | } |
366 | 0 | else if (status == HTTP_STATUS_UPGRADE_REQUIRED) |
367 | 0 | { |
368 | | // Force a reconnect with encryption... |
369 | 0 | DEBUG_puts("2cupsGetResponse: Need encryption..."); |
370 | |
|
371 | 0 | if (httpConnectAgain(http, 30000, NULL)) |
372 | 0 | httpSetEncryption(http, HTTP_ENCRYPTION_REQUIRED); |
373 | 0 | } |
374 | 0 | } |
375 | |
|
376 | 0 | if (response) |
377 | 0 | { |
378 | 0 | ipp_attribute_t *attr; // status-message attribute |
379 | |
|
380 | 0 | attr = ippFindAttribute(response, "status-message", IPP_TAG_TEXT); |
381 | |
|
382 | 0 | DEBUG_printf("1cupsGetResponse: status-code=%s, status-message=\"%s\"", ippErrorString(response->request.status.status_code), attr ? attr->values[0].string.text : ""); |
383 | |
|
384 | 0 | _cupsSetError(response->request.status.status_code, attr ? attr->values[0].string.text : ippErrorString(response->request.status.status_code), false); |
385 | 0 | } |
386 | |
|
387 | 0 | return (response); |
388 | 0 | } |
389 | | |
390 | | |
391 | | // |
392 | | // 'cupsGetError()' - Return the last IPP status code received on the current |
393 | | // thread. |
394 | | // |
395 | | |
396 | | ipp_status_t // O - IPP status code from last request |
397 | | cupsGetError(void) |
398 | 0 | { |
399 | 0 | return (_cupsGlobals()->last_error); |
400 | 0 | } |
401 | | |
402 | | |
403 | | // |
404 | | // 'cupsGetErrorString()' - Return the last IPP status-message received on the |
405 | | // current thread. |
406 | | // |
407 | | |
408 | | const char * // O - "status-message" text from last request |
409 | | cupsGetErrorString(void) |
410 | 0 | { |
411 | 0 | return (_cupsGlobals()->last_status_message); |
412 | 0 | } |
413 | | |
414 | | |
415 | | // |
416 | | // 'cupsReadResponseData()' - Read additional data after the IPP response. |
417 | | // |
418 | | // This function is used after @link cupsGetResponse@ to read any trailing |
419 | | // document data after an IPP response. |
420 | | // |
421 | | |
422 | | ssize_t // O - Bytes read, 0 on EOF, -1 on error |
423 | | cupsReadResponseData( |
424 | | http_t *http, // I - Connection to server or `CUPS_HTTP_DEFAULT` |
425 | | char *buffer, // I - Buffer to use |
426 | | size_t length) // I - Number of bytes to read |
427 | 0 | { |
428 | | // Get the default connection as needed... |
429 | 0 | DEBUG_printf("cupsReadResponseData(http=%p, buffer=%p, length=" CUPS_LLFMT ")", (void *)http, (void *)buffer, CUPS_LLCAST length); |
430 | |
|
431 | 0 | if (!http) |
432 | 0 | { |
433 | 0 | _cups_globals_t *cg = _cupsGlobals(); |
434 | | // Pointer to library globals |
435 | |
|
436 | 0 | if ((http = cg->http) == NULL) |
437 | 0 | { |
438 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No active connection"), true); |
439 | 0 | return (-1); |
440 | 0 | } |
441 | 0 | } |
442 | | |
443 | | // Then read from the HTTP connection... |
444 | 0 | return (httpRead(http, buffer, length)); |
445 | 0 | } |
446 | | |
447 | | |
448 | | // |
449 | | // 'cupsSendRequest()' - Send an IPP request. |
450 | | // |
451 | | // Use @link cupsWriteRequestData@ to write any additional data (document, etc.) |
452 | | // for the request, @link cupsGetResponse@ to get the IPP response, and |
453 | | // @link cupsReadResponseData@ to read any additional data following the |
454 | | // response. Only one request can be sent/queued at a time per `http_t` |
455 | | // connection. |
456 | | // |
457 | | // Returns the initial HTTP status code, which will be `HTTP_STATUS_CONTINUE` |
458 | | // on a successful send of the request. |
459 | | // |
460 | | // Note: Unlike @link cupsDoFileRequest@, @link cupsDoIORequest@, and |
461 | | // @link cupsDoRequest@, the request is NOT freed with @link ippDelete@. |
462 | | // |
463 | | |
464 | | http_status_t // O - Initial HTTP status |
465 | | cupsSendRequest(http_t *http, // I - Connection to server or `CUPS_HTTP_DEFAULT` |
466 | | ipp_t *request, // I - IPP request |
467 | | const char *resource, // I - Resource path |
468 | | size_t length) // I - Length of data to follow or `CUPS_LENGTH_VARIABLE` |
469 | 0 | { |
470 | 0 | http_status_t status; // Status of HTTP request |
471 | 0 | bool got_status; // Did we get the status? |
472 | 0 | ipp_state_t state; // State of IPP processing |
473 | 0 | http_status_t expect; // Expect: header to use |
474 | 0 | char date[256]; // Date: header value |
475 | 0 | int digest; // Are we using Digest authentication? |
476 | | |
477 | |
|
478 | 0 | DEBUG_printf("cupsSendRequest(http=%p, request=%p(%s), resource=\"%s\", length=" CUPS_LLFMT ")", (void *)http, (void *)request, request ? ippOpString(request->request.op.operation_id) : "?", resource, CUPS_LLCAST length); |
479 | | |
480 | | // Range check input... |
481 | 0 | if (!request || !resource) |
482 | 0 | { |
483 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), false); |
484 | |
|
485 | 0 | return (HTTP_STATUS_ERROR); |
486 | 0 | } |
487 | | |
488 | | // Get the default connection as needed... |
489 | 0 | if (!http && (http = _cupsConnect()) == NULL) |
490 | 0 | return (HTTP_STATUS_SERVICE_UNAVAILABLE); |
491 | | |
492 | | // If the prior request was not flushed out, do so now... |
493 | 0 | if (http->state == HTTP_STATE_GET_SEND || http->state == HTTP_STATE_POST_SEND) |
494 | 0 | { |
495 | 0 | DEBUG_puts("2cupsSendRequest: Flush prior response."); |
496 | 0 | httpFlush(http); |
497 | 0 | } |
498 | 0 | else if (http->state != HTTP_STATE_WAITING) |
499 | 0 | { |
500 | 0 | DEBUG_printf("1cupsSendRequest: Unknown HTTP state (%d), reconnecting.", http->state); |
501 | 0 | if (!httpConnectAgain(http, 30000, NULL)) |
502 | 0 | return (HTTP_STATUS_ERROR); |
503 | 0 | } |
504 | | |
505 | | // See if we have an auth-info attribute and are communicating over |
506 | | // a non-local link. If so, encrypt the link so that we can pass |
507 | | // the authentication information securely... |
508 | 0 | if (ippFindAttribute(request, "auth-info", IPP_TAG_TEXT) && !httpAddrIsLocalhost(http->hostaddr) && !http->tls && !httpSetEncryption(http, HTTP_ENCRYPTION_REQUIRED)) |
509 | 0 | { |
510 | 0 | DEBUG_puts("1cupsSendRequest: Unable to encrypt connection."); |
511 | 0 | return (HTTP_STATUS_SERVICE_UNAVAILABLE); |
512 | 0 | } |
513 | | |
514 | | // Reconnect if the last response had a "Connection: close"... |
515 | 0 | if (!_cups_strcasecmp(httpGetField(http, HTTP_FIELD_CONNECTION), "close")) |
516 | 0 | { |
517 | 0 | DEBUG_puts("2cupsSendRequest: Connection: close"); |
518 | 0 | httpClearFields(http); |
519 | 0 | if (!httpConnectAgain(http, 30000, NULL)) |
520 | 0 | { |
521 | 0 | DEBUG_puts("1cupsSendRequest: Unable to reconnect."); |
522 | 0 | return (HTTP_STATUS_SERVICE_UNAVAILABLE); |
523 | 0 | } |
524 | 0 | } |
525 | | |
526 | | // Loop until we can send the request without authorization problems. |
527 | 0 | expect = HTTP_STATUS_CONTINUE; |
528 | |
|
529 | 0 | for (;;) |
530 | 0 | { |
531 | 0 | DEBUG_puts("2cupsSendRequest: Setup..."); |
532 | | |
533 | | // Setup the HTTP variables needed... |
534 | 0 | httpClearFields(http); |
535 | 0 | httpSetExpect(http, expect); |
536 | 0 | httpSetField(http, HTTP_FIELD_CONTENT_TYPE, "application/ipp"); |
537 | 0 | httpSetField(http, HTTP_FIELD_DATE, httpGetDateString(time(NULL), date, sizeof(date))); |
538 | 0 | httpSetLength(http, length); |
539 | |
|
540 | 0 | digest = http->authstring && !strncmp(http->authstring, "Digest ", 7); |
541 | |
|
542 | 0 | if (digest) |
543 | 0 | { |
544 | | // Update the Digest authentication string... |
545 | 0 | _httpSetDigestAuthString(http, http->nextnonce, "POST", resource); |
546 | 0 | } |
547 | |
|
548 | 0 | httpSetField(http, HTTP_FIELD_AUTHORIZATION, http->authstring); |
549 | |
|
550 | 0 | DEBUG_printf("2cupsSendRequest: authstring=\"%s\"", http->authstring); |
551 | | |
552 | | // Try the request... |
553 | 0 | DEBUG_puts("2cupsSendRequest: Sending HTTP POST..."); |
554 | |
|
555 | 0 | if (!httpWriteRequest(http, "POST", resource)) |
556 | 0 | { |
557 | 0 | DEBUG_puts("2cupsSendRequest: POST failed, reconnecting."); |
558 | 0 | if (!httpConnectAgain(http, 30000, NULL)) |
559 | 0 | { |
560 | 0 | DEBUG_puts("1cupsSendRequest: Unable to reconnect."); |
561 | 0 | return (HTTP_STATUS_SERVICE_UNAVAILABLE); |
562 | 0 | } |
563 | 0 | else |
564 | 0 | { |
565 | 0 | continue; |
566 | 0 | } |
567 | 0 | } |
568 | | |
569 | | // Send the IPP data... |
570 | 0 | DEBUG_puts("2cupsSendRequest: Writing IPP request..."); |
571 | |
|
572 | 0 | request->state = IPP_STATE_IDLE; |
573 | 0 | status = HTTP_STATUS_CONTINUE; |
574 | 0 | got_status = false; |
575 | |
|
576 | 0 | while ((state = ippWrite(http, request)) != IPP_STATE_DATA) |
577 | 0 | { |
578 | 0 | if (httpWait(http, 0)) |
579 | 0 | { |
580 | 0 | got_status = true; |
581 | |
|
582 | 0 | _httpUpdate(http, &status); |
583 | 0 | if (status >= HTTP_STATUS_MULTIPLE_CHOICES) |
584 | 0 | break; |
585 | 0 | } |
586 | 0 | else if (state == IPP_STATE_ERROR) |
587 | 0 | { |
588 | 0 | break; |
589 | 0 | } |
590 | 0 | } |
591 | |
|
592 | 0 | if (state == IPP_STATE_ERROR) |
593 | 0 | { |
594 | | // We weren't able to send the IPP request. But did we already get a HTTP |
595 | | // error status? |
596 | 0 | if (!got_status || status < HTTP_STATUS_MULTIPLE_CHOICES) |
597 | 0 | { |
598 | | // No, something else went wrong. |
599 | 0 | DEBUG_puts("1cupsSendRequest: Unable to send IPP request."); |
600 | |
|
601 | 0 | http->status = HTTP_STATUS_ERROR; |
602 | 0 | http->state = HTTP_STATE_WAITING; |
603 | |
|
604 | 0 | return (HTTP_STATUS_ERROR); |
605 | 0 | } |
606 | 0 | } |
607 | | |
608 | | // Wait up to 1 second to get the 100-continue response as needed... |
609 | 0 | if (!got_status || (digest && status == HTTP_STATUS_CONTINUE)) |
610 | 0 | { |
611 | 0 | if (expect == HTTP_STATUS_CONTINUE || digest) |
612 | 0 | { |
613 | 0 | DEBUG_puts("2cupsSendRequest: Waiting for 100-continue..."); |
614 | |
|
615 | 0 | if (httpWait(http, 1000)) |
616 | 0 | _httpUpdate(http, &status); |
617 | 0 | } |
618 | 0 | else if (httpWait(http, 0)) |
619 | 0 | { |
620 | 0 | _httpUpdate(http, &status); |
621 | 0 | } |
622 | 0 | } |
623 | |
|
624 | 0 | DEBUG_printf("2cupsSendRequest: status=%d", status); |
625 | | |
626 | | // Process the current HTTP status... |
627 | 0 | if (status >= HTTP_STATUS_MULTIPLE_CHOICES) |
628 | 0 | { |
629 | 0 | int temp_status; // Temporary status |
630 | |
|
631 | 0 | _cupsSetHTTPError(http, status); |
632 | |
|
633 | 0 | do |
634 | 0 | { |
635 | 0 | temp_status = httpUpdate(http); |
636 | 0 | } |
637 | 0 | while (temp_status != HTTP_STATUS_ERROR && http->state == HTTP_STATE_POST_RECV); |
638 | |
|
639 | 0 | httpFlush(http); |
640 | 0 | } |
641 | |
|
642 | 0 | switch (status) |
643 | 0 | { |
644 | 0 | case HTTP_STATUS_CONTINUE : |
645 | 0 | case HTTP_STATUS_OK : |
646 | 0 | case HTTP_STATUS_ERROR : |
647 | 0 | DEBUG_printf("1cupsSendRequest: Returning %d.", status); |
648 | 0 | return (status); |
649 | | |
650 | 0 | case HTTP_STATUS_UNAUTHORIZED : |
651 | 0 | if (!cupsDoAuthentication(http, "POST", resource)) |
652 | 0 | { |
653 | 0 | DEBUG_puts("1cupsSendRequest: Returning HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED."); |
654 | 0 | return (HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED); |
655 | 0 | } |
656 | | |
657 | 0 | DEBUG_puts("2cupsSendRequest: Reconnecting after HTTP_STATUS_UNAUTHORIZED."); |
658 | |
|
659 | 0 | if (!httpConnectAgain(http, 30000, NULL)) |
660 | 0 | { |
661 | 0 | DEBUG_puts("1cupsSendRequest: Unable to reconnect."); |
662 | 0 | return (HTTP_STATUS_SERVICE_UNAVAILABLE); |
663 | 0 | } |
664 | 0 | break; |
665 | | |
666 | 0 | case HTTP_STATUS_UPGRADE_REQUIRED : |
667 | | // Flush any error message, reconnect, and then upgrade with |
668 | | // encryption... |
669 | 0 | DEBUG_puts("2cupsSendRequest: Reconnecting after HTTP_STATUS_UPGRADE_REQUIRED."); |
670 | |
|
671 | 0 | if (!httpConnectAgain(http, 30000, NULL)) |
672 | 0 | { |
673 | 0 | DEBUG_puts("1cupsSendRequest: Unable to reconnect."); |
674 | 0 | return (HTTP_STATUS_SERVICE_UNAVAILABLE); |
675 | 0 | } |
676 | | |
677 | 0 | DEBUG_puts("2cupsSendRequest: Upgrading to TLS."); |
678 | 0 | if (!httpSetEncryption(http, HTTP_ENCRYPTION_REQUIRED)) |
679 | 0 | { |
680 | 0 | DEBUG_puts("1cupsSendRequest: Unable to encrypt connection."); |
681 | 0 | return (HTTP_STATUS_SERVICE_UNAVAILABLE); |
682 | 0 | } |
683 | 0 | break; |
684 | | |
685 | 0 | case HTTP_STATUS_EXPECTATION_FAILED : |
686 | | // Don't try using the Expect: header the next time around... |
687 | 0 | expect = HTTP_STATUS_NONE; |
688 | |
|
689 | 0 | DEBUG_puts("2cupsSendRequest: Reconnecting after HTTP_EXPECTATION_FAILED."); |
690 | |
|
691 | 0 | if (!httpConnectAgain(http, 30000, NULL)) |
692 | 0 | { |
693 | 0 | DEBUG_puts("1cupsSendRequest: Unable to reconnect."); |
694 | 0 | return (HTTP_STATUS_SERVICE_UNAVAILABLE); |
695 | 0 | } |
696 | 0 | break; |
697 | | |
698 | 0 | default : |
699 | | // Some other error... |
700 | 0 | return (status); |
701 | 0 | } |
702 | 0 | } |
703 | 0 | } |
704 | | |
705 | | |
706 | | // |
707 | | // 'cupsWriteRequestData()' - Write additional data after an IPP request. |
708 | | // |
709 | | // This function writes a buffer of additional data after an IPP request and is |
710 | | // used after calling the @link cupsSendRequest@ and/or @link cupsStartDocument@ |
711 | | // functions, typically to send all or part of a document or file. Each call |
712 | | // appends the given buffer of data to the request, allowing an application to |
713 | | // stream content to the receiving IPP server. |
714 | | // |
715 | | // Call the @link cupsGetResponse@ or @link cupsFinishDestDocument@ functions |
716 | | // to complete the current request and get the corresponding response. |
717 | | // |
718 | | |
719 | | http_status_t // O - `HTTP_STATUS_CONTINUE` if OK or HTTP status on error |
720 | | cupsWriteRequestData( |
721 | | http_t *http, // I - Connection to server or `CUPS_HTTP_DEFAULT` |
722 | | const char *buffer, // I - Bytes to write |
723 | | size_t length) // I - Number of bytes to write |
724 | 0 | { |
725 | 0 | int wused; // Previous bytes in buffer |
726 | | |
727 | | |
728 | | // Get the default connection as needed... |
729 | 0 | DEBUG_printf("cupsWriteRequestData(http=%p, buffer=%p, length=" CUPS_LLFMT ")", (void *)http, (void *)buffer, CUPS_LLCAST length); |
730 | |
|
731 | 0 | if (!http) |
732 | 0 | { |
733 | 0 | _cups_globals_t *cg = _cupsGlobals(); |
734 | | // Pointer to library globals |
735 | |
|
736 | 0 | if ((http = cg->http) == NULL) |
737 | 0 | { |
738 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No active connection"), true); |
739 | 0 | DEBUG_puts("1cupsWriteRequestData: Returning HTTP_STATUS_ERROR."); |
740 | 0 | return (HTTP_STATUS_ERROR); |
741 | 0 | } |
742 | 0 | } |
743 | | |
744 | | // Then write to the HTTP connection... |
745 | 0 | wused = http->wused; |
746 | |
|
747 | 0 | if (httpWrite(http, buffer, length) < 0) |
748 | 0 | { |
749 | 0 | DEBUG_puts("1cupsWriteRequestData: Returning HTTP_STATUS_ERROR."); |
750 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(http->error), false); |
751 | 0 | return (HTTP_STATUS_ERROR); |
752 | 0 | } |
753 | | |
754 | | // Finally, check if we have any pending data from the server... |
755 | 0 | if (length >= HTTP_MAX_BUFFER || http->wused < wused || (wused > 0 && (size_t)http->wused == length)) |
756 | 0 | { |
757 | | // We've written something to the server, so check for response data... |
758 | 0 | if (_httpWait(http, 0, 1)) |
759 | 0 | { |
760 | 0 | http_status_t status; // Status from _httpUpdate |
761 | |
|
762 | 0 | _httpUpdate(http, &status); |
763 | 0 | if (status >= HTTP_STATUS_MULTIPLE_CHOICES) |
764 | 0 | { |
765 | 0 | _cupsSetHTTPError(http, status); |
766 | |
|
767 | 0 | do |
768 | 0 | { |
769 | 0 | status = httpUpdate(http); |
770 | 0 | } |
771 | 0 | while (status != HTTP_STATUS_ERROR && http->state == HTTP_STATE_POST_RECV); |
772 | |
|
773 | 0 | httpFlush(http); |
774 | 0 | } |
775 | |
|
776 | 0 | DEBUG_printf("1cupsWriteRequestData: Returning %d.\n", status); |
777 | 0 | return (status); |
778 | 0 | } |
779 | 0 | } |
780 | | |
781 | 0 | DEBUG_puts("1cupsWriteRequestData: Returning HTTP_STATUS_CONTINUE."); |
782 | 0 | return (HTTP_STATUS_CONTINUE); |
783 | 0 | } |
784 | | |
785 | | |
786 | | // |
787 | | // '_cupsConnect()' - Get the default server connection... |
788 | | // |
789 | | |
790 | | http_t * // O - HTTP connection |
791 | | _cupsConnect(void) |
792 | 0 | { |
793 | 0 | _cups_globals_t *cg = _cupsGlobals(); // Pointer to library globals |
794 | | |
795 | | |
796 | | // See if we are connected to the same server... |
797 | 0 | if (cg->http) |
798 | 0 | { |
799 | | // Compare the connection hostname, port, and encryption settings to |
800 | | // the cached defaults; these were initialized the first time we |
801 | | // connected... |
802 | 0 | if (strcmp(cg->http->hostname, cg->server) || |
803 | 0 | #ifdef AF_LOCAL |
804 | 0 | (httpAddrGetFamily(cg->http->hostaddr) != AF_LOCAL && cg->ipp_port != httpAddrGetPort(cg->http->hostaddr)) || |
805 | | #else |
806 | | cg->ipp_port != httpAddrGetPort(cg->http->hostaddr) || |
807 | | #endif // AF_LOCAL |
808 | 0 | (cg->http->encryption != cg->encryption && |
809 | 0 | cg->http->encryption == HTTP_ENCRYPTION_NEVER)) |
810 | 0 | { |
811 | | // Need to close the current connection because something has changed... |
812 | 0 | httpClose(cg->http); |
813 | 0 | cg->http = NULL; |
814 | 0 | } |
815 | 0 | else |
816 | 0 | { |
817 | | // Same server, see if the connection is still established... |
818 | 0 | char ch; // Connection check byte |
819 | 0 | ssize_t n; // Number of bytes |
820 | |
|
821 | | #ifdef _WIN32 |
822 | | if ((n = recv(cg->http->fd, &ch, 1, MSG_PEEK)) == 0 || |
823 | | (n < 0 && WSAGetLastError() != WSAEWOULDBLOCK)) |
824 | | #else |
825 | 0 | if ((n = recv(cg->http->fd, &ch, 1, MSG_PEEK | MSG_DONTWAIT)) == 0 || |
826 | 0 | (n < 0 && errno != EWOULDBLOCK)) |
827 | 0 | #endif // _WIN32 |
828 | 0 | { |
829 | | // Nope, close the connection... |
830 | 0 | httpClose(cg->http); |
831 | 0 | cg->http = NULL; |
832 | 0 | } |
833 | 0 | } |
834 | 0 | } |
835 | | |
836 | | // (Re)connect as needed... |
837 | 0 | if (!cg->http) |
838 | 0 | { |
839 | 0 | if ((cg->http = httpConnect(cupsGetServer(), ippGetPort(), NULL, AF_UNSPEC, cupsGetEncryption(), 1, 30000, NULL)) == NULL) |
840 | 0 | { |
841 | 0 | if (errno) |
842 | 0 | _cupsSetError(IPP_STATUS_ERROR_SERVICE_UNAVAILABLE, NULL, false); |
843 | 0 | else |
844 | 0 | _cupsSetError(IPP_STATUS_ERROR_SERVICE_UNAVAILABLE, _("Unable to connect to host."), true); |
845 | 0 | } |
846 | 0 | } |
847 | | |
848 | | // Return the cached connection... |
849 | 0 | return (cg->http); |
850 | 0 | } |
851 | | |
852 | | |
853 | | // |
854 | | // '_cupsSetError()' - Set the last IPP status code and status-message. |
855 | | // |
856 | | |
857 | | void |
858 | | _cupsSetError(ipp_status_t status, // I - IPP status code |
859 | | const char *message, // I - status-message value |
860 | | bool localize) // I - Localize the message? |
861 | 490 | { |
862 | 490 | _cups_globals_t *cg; // Global data |
863 | | |
864 | | |
865 | 490 | if (!message && errno) |
866 | 0 | { |
867 | 0 | message = strerror(errno); |
868 | 0 | localize = 0; |
869 | 0 | } |
870 | | |
871 | 490 | cg = _cupsGlobals(); |
872 | 490 | cg->last_error = status; |
873 | | |
874 | 490 | if (cg->last_status_message) |
875 | 489 | { |
876 | 489 | _cupsStrFree(cg->last_status_message); |
877 | | |
878 | 489 | cg->last_status_message = NULL; |
879 | 489 | } |
880 | | |
881 | 490 | if (message) |
882 | 490 | { |
883 | 490 | if (localize) |
884 | 490 | { |
885 | | // Get the message catalog... |
886 | 490 | if (!cg->lang_default) |
887 | 0 | cg->lang_default = cupsLangDefault(); |
888 | | |
889 | 490 | cg->last_status_message = _cupsStrAlloc(cupsLangGetString(cg->lang_default, message)); |
890 | 490 | } |
891 | 0 | else |
892 | 0 | { |
893 | 0 | cg->last_status_message = _cupsStrAlloc(message); |
894 | 0 | } |
895 | 490 | } |
896 | | |
897 | 490 | DEBUG_printf("4_cupsSetError: last_error=%s, last_status_message=\"%s\"", ippErrorString(cg->last_error), cg->last_status_message); |
898 | 490 | } |
899 | | |
900 | | |
901 | | // |
902 | | // '_cupsSetHTTPError()' - Set the last error using the HTTP status. |
903 | | // |
904 | | |
905 | | void |
906 | | _cupsSetHTTPError(http_t *http, // I - HTTP connection |
907 | | http_status_t status) // I - HTTP status code |
908 | 0 | { |
909 | 0 | switch (status) |
910 | 0 | { |
911 | 0 | case HTTP_STATUS_NOT_MODIFIED : |
912 | 0 | _cupsSetError(IPP_STATUS_OK_EVENTS_COMPLETE, httpStatusString(status), false); |
913 | 0 | break; |
914 | | |
915 | 0 | case HTTP_STATUS_NOT_FOUND : |
916 | 0 | _cupsSetError(IPP_STATUS_ERROR_NOT_FOUND, httpStatusString(status), false); |
917 | 0 | break; |
918 | | |
919 | 0 | case HTTP_STATUS_UNAUTHORIZED : |
920 | 0 | _cupsSetError(IPP_STATUS_ERROR_NOT_AUTHENTICATED, httpStatusString(status), false); |
921 | 0 | break; |
922 | | |
923 | 0 | case HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED : |
924 | 0 | _cupsSetError(IPP_STATUS_ERROR_CUPS_AUTHENTICATION_CANCELED, httpStatusString(status), false); |
925 | 0 | break; |
926 | | |
927 | 0 | case HTTP_STATUS_FORBIDDEN : |
928 | 0 | _cupsSetError(IPP_STATUS_ERROR_FORBIDDEN, httpStatusString(status), false); |
929 | 0 | break; |
930 | | |
931 | 0 | case HTTP_STATUS_BAD_REQUEST : |
932 | 0 | _cupsSetError(IPP_STATUS_ERROR_BAD_REQUEST, httpStatusString(status), false); |
933 | 0 | break; |
934 | | |
935 | 0 | case HTTP_STATUS_CONTENT_TOO_LARGE : |
936 | 0 | _cupsSetError(IPP_STATUS_ERROR_REQUEST_VALUE, httpStatusString(status), false); |
937 | 0 | break; |
938 | | |
939 | 0 | case HTTP_STATUS_NOT_IMPLEMENTED : |
940 | 0 | _cupsSetError(IPP_STATUS_ERROR_OPERATION_NOT_SUPPORTED, httpStatusString(status), false); |
941 | 0 | break; |
942 | | |
943 | 0 | case HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED : |
944 | 0 | _cupsSetError(IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED, httpStatusString(status), false); |
945 | 0 | break; |
946 | | |
947 | 0 | case HTTP_STATUS_UPGRADE_REQUIRED : |
948 | 0 | _cupsSetError(IPP_STATUS_ERROR_CUPS_UPGRADE_REQUIRED, httpStatusString(status), false); |
949 | 0 | break; |
950 | | |
951 | 0 | case HTTP_STATUS_CUPS_PKI_ERROR : |
952 | 0 | _cupsSetError(IPP_STATUS_ERROR_CUPS_PKI, httpStatusString(status), false); |
953 | 0 | break; |
954 | | |
955 | 0 | case HTTP_STATUS_ERROR : |
956 | 0 | _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(http->error), false); |
957 | 0 | break; |
958 | | |
959 | 0 | default : |
960 | 0 | if ((int)status >= 300) |
961 | 0 | { |
962 | 0 | DEBUG_printf("4_cupsSetHTTPError: HTTP error %d mapped to IPP_STATUS_ERROR_SERVICE_UNAVAILABLE!", status); |
963 | 0 | _cupsSetError(IPP_STATUS_ERROR_SERVICE_UNAVAILABLE, httpStatusString(status), false); |
964 | 0 | } |
965 | 0 | break; |
966 | 0 | } |
967 | 0 | } |