2 * "$Id: util.c 5064 2006-02-03 16:51:05Z mike $"
4 * Printing utilities for the Common UNIX Printing System (CUPS).
6 * Copyright 1997-2006 by Easy Software Products.
8 * These coded instructions, statements, and computer programs are the
9 * property of Easy Software Products and are protected by Federal
10 * copyright law. Distribution and use rights are outlined in the file
11 * "LICENSE.txt" which should have been included with this file. If this
12 * file is missing or damaged please contact Easy Software Products
15 * Attn: CUPS Licensing Information
16 * Easy Software Products
17 * 44141 Airport View Drive, Suite 204
18 * Hollywood, Maryland 20636 USA
20 * Voice: (301) 373-9600
21 * EMail: cups-info@cups.org
22 * WWW: http://www.cups.org
24 * This file is subject to the Apple OS-Developed Software exception.
28 * cupsCancelJob() - Cancel a print job on the default server.
29 * cupsDoFileRequest() - Do an IPP request.
30 * cupsFreeJobs() - Free memory used by job data.
31 * cupsGetClasses() - Get a list of printer classes from the default
33 * cupsGetDefault() - Get the default printer or class from the default
35 * cupsGetDefault2() - Get the default printer or class from the
37 * cupsGetJobs() - Get the jobs from the default server.
38 * cupsGetJobs2() - Get the jobs from the specified server.
39 * cupsGetPPD() - Get the PPD file for a printer on the default
41 * cupsGetPPD2() - Get the PPD file for a printer on the specified
43 * cupsGetPrinters() - Get a list of printers from the default server.
44 * cupsLastError() - Return the last IPP status code.
45 * cupsLastErrorString() - Return the last IPP status-message.
46 * cupsPrintFile() - Print a file to a printer or class on the default
48 * cupsPrintFile2() - Print a file to a printer or class on the
50 * cupsPrintFiles() - Print one or more files to a printer or class on
52 * cupsPrintFiles2() - Print one or more files to a printer or class on
53 * the specified server.
54 * cups_connect() - Connect to the specified host...
55 * cups_get_printer_uri() - Get the printer-uri-supported attribute for the
56 * first printer in a class.
57 * cups_set_error() - Set the last IPP status code and status-message.
61 * Include necessary headers...
70 #if defined(WIN32) || defined(__EMX__)
74 #endif /* WIN32 || __EMX__ */
81 static char *cups_connect(const char *name
, char *printer
, char *hostname
);
82 static int cups_get_printer_uri(http_t
*http
, const char *name
,
83 char *host
, int hostsize
, int *port
,
84 char *resource
, int resourcesize
,
86 static void cups_set_error(ipp_status_t status
, const char *message
);
90 * 'cupsCancelJob()' - Cancel a print job on the default server.
92 * Use the cupsLastError() and cupsLastErrorString() functions to get
93 * the cause of any failure.
96 int /* O - 1 on success, 0 on failure */
97 cupsCancelJob(const char *name
, /* I - Name of printer or class */
98 int job
) /* I - Job ID */
100 char printer
[HTTP_MAX_URI
], /* Printer name */
101 hostname
[HTTP_MAX_URI
], /* Hostname */
102 uri
[HTTP_MAX_URI
]; /* Printer URI */
103 ipp_t
*request
, /* IPP request */
104 *response
; /* IPP response */
105 cups_lang_t
*language
; /* Language info */
106 _cups_globals_t
*cg
= _cupsGlobals(); /* Pointer to library globals */
110 * See if we can connect to the server...
113 if (!cups_connect(name
, printer
, hostname
))
115 DEBUG_puts("Unable to connect to server!");
121 * Create a printer URI...
124 if (httpAssembleURIf(HTTP_URI_CODING_ALL
, uri
, sizeof(uri
), "ipp", NULL
,
125 "localhost", 0, "/printers/%s", printer
) != HTTP_URI_OK
)
127 cups_set_error(IPP_INTERNAL_ERROR
, NULL
);
133 * Build an IPP_CANCEL_JOB request, which requires the following
137 * attributes-natural-language
140 * [requesting-user-name]
145 request
->request
.op
.operation_id
= IPP_CANCEL_JOB
;
146 request
->request
.op
.request_id
= 1;
148 language
= cupsLangDefault();
150 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_CHARSET
,
151 "attributes-charset", NULL
, cupsLangEncoding(language
));
153 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_LANGUAGE
,
154 "attributes-natural-language", NULL
,
155 language
!= NULL
? language
->language
: "C");
157 cupsLangFree(language
);
159 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
162 ippAddInteger(request
, IPP_TAG_OPERATION
, IPP_TAG_INTEGER
, "job-id", job
);
164 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
, "requesting-user-name",
171 if ((response
= cupsDoRequest(cg
->http
, request
, "/jobs/")) != NULL
)
174 return (cg
->last_error
< IPP_REDIRECTION_OTHER_SITE
);
179 * 'cupsDoFileRequest()' - Do an IPP request.
181 * This function sends any IPP request to the specified server, retrying
182 * and authenticating as necessary.
185 ipp_t
* /* O - Response data */
186 cupsDoFileRequest(http_t
*http
, /* I - HTTP connection to server */
187 ipp_t
*request
, /* I - IPP request */
188 const char *resource
, /* I - HTTP resource for POST */
189 const char *filename
) /* I - File to send or NULL for none */
191 ipp_t
*response
; /* IPP response data */
192 size_t length
; /* Content-Length value */
193 http_status_t status
; /* Status of HTTP request */
194 FILE *file
; /* File to send */
195 struct stat fileinfo
; /* File information */
196 int bytes
; /* Number of bytes read/written */
197 char buffer
[65536]; /* Output buffer */
200 DEBUG_printf(("cupsDoFileRequest(%p, %p, \'%s\', \'%s\')\n",
201 http
, request
, resource
? resource
: "(null)",
202 filename
? filename
: "(null)"));
204 if (http
== NULL
|| request
== NULL
|| resource
== NULL
)
209 cups_set_error(IPP_INTERNAL_ERROR
, NULL
);
215 * See if we have a file to send...
218 if (filename
!= NULL
)
220 if (stat(filename
, &fileinfo
))
223 * Can't get file information!
226 cups_set_error(errno
== ENOENT
? IPP_NOT_FOUND
: IPP_NOT_AUTHORIZED
,
235 if (fileinfo
.st_mode
& _S_IFDIR
)
237 if (S_ISDIR(fileinfo
.st_mode
))
241 * Can't send a directory...
246 cups_set_error(IPP_NOT_POSSIBLE
, NULL
);
251 if ((file
= fopen(filename
, "rb")) == NULL
)
257 cups_set_error(errno
== ENOENT
? IPP_NOT_FOUND
: IPP_NOT_AUTHORIZED
,
269 * Loop until we can send the request without authorization problems.
275 while (response
== NULL
)
277 DEBUG_puts("cupsDoFileRequest: setup...");
280 * Setup the HTTP variables needed...
283 length
= ippLength(request
);
285 length
+= fileinfo
.st_size
;
287 httpClearFields(http
);
288 httpSetLength(http
, length
);
289 httpSetField(http
, HTTP_FIELD_CONTENT_TYPE
, "application/ipp");
290 httpSetField(http
, HTTP_FIELD_AUTHORIZATION
, http
->authstring
);
292 DEBUG_printf(("cupsDoFileRequest: authstring=\"%s\"\n", http
->authstring
));
298 DEBUG_puts("cupsDoFileRequest: post...");
300 if (httpPost(http
, resource
))
302 if (httpReconnect(http
))
312 * Send the IPP data and wait for the response...
315 DEBUG_puts("cupsDoFileRequest: ipp write...");
317 request
->state
= IPP_IDLE
;
318 status
= HTTP_CONTINUE
;
320 if (ippWrite(http
, request
) != IPP_ERROR
)
321 if (filename
!= NULL
)
323 DEBUG_puts("cupsDoFileRequest: file write...");
331 while ((bytes
= (int)fread(buffer
, 1, sizeof(buffer
), file
)) > 0)
335 if ((status
= httpUpdate(http
)) != HTTP_CONTINUE
)
339 if (httpWrite2(http
, buffer
, bytes
) < bytes
)
345 * Get the server's return status...
348 DEBUG_puts("cupsDoFileRequest: update...");
350 while (status
== HTTP_CONTINUE
)
351 status
= httpUpdate(http
);
353 DEBUG_printf(("cupsDoFileRequest: status = %d\n", status
));
355 if (status
== HTTP_UNAUTHORIZED
)
357 DEBUG_puts("cupsDoFileRequest: unauthorized...");
360 * Flush any error message...
366 * See if we can do authentication...
369 if (cupsDoAuthentication(http
, "POST", resource
))
372 if (httpReconnect(http
))
380 else if (status
== HTTP_ERROR
)
383 if (http
->error
!= WSAENETDOWN
&& http
->error
!= WSAENETUNREACH
)
385 if (http
->error
!= ENETDOWN
&& http
->error
!= ENETUNREACH
)
392 else if (status
== HTTP_UPGRADE_REQUIRED
)
394 /* Flush any error message... */
398 if (httpReconnect(http
))
404 /* Upgrade with encryption... */
405 httpEncryption(http
, HTTP_ENCRYPT_REQUIRED
);
407 /* Try again, this time with encryption enabled... */
410 #endif /* HAVE_SSL */
411 else if (status
!= HTTP_OK
)
413 DEBUG_printf(("cupsDoFileRequest: error %d...\n", status
));
416 * Flush any error message...
425 * Read the response...
428 DEBUG_puts("cupsDoFileRequest: response...");
432 if (ippRead(http
, response
) == IPP_ERROR
)
435 * Delete the response...
438 DEBUG_puts("IPP read error!");
442 cups_set_error(IPP_SERVICE_UNAVAILABLE
, strerror(errno
));
450 * Close the file if needed...
453 if (filename
!= NULL
)
457 * Flush any remaining data...
463 * Delete the original request and return the response...
470 ipp_attribute_t
*attr
; /* status-message attribute */
473 attr
= ippFindAttribute(response
, "status-message", IPP_TAG_TEXT
);
475 cups_set_error(response
->request
.status
.status_code
,
476 attr
? attr
->values
[0].string
.text
:
477 ippErrorString(response
->request
.status
.status_code
));
479 else if (status
!= HTTP_OK
)
483 case HTTP_NOT_FOUND
:
484 cups_set_error(IPP_NOT_FOUND
, httpStatus(status
));
487 case HTTP_UNAUTHORIZED
:
488 cups_set_error(IPP_NOT_AUTHORIZED
, httpStatus(status
));
491 case HTTP_FORBIDDEN
:
492 cups_set_error(IPP_FORBIDDEN
, httpStatus(status
));
495 case HTTP_BAD_REQUEST
:
496 cups_set_error(IPP_BAD_REQUEST
, httpStatus(status
));
499 case HTTP_REQUEST_TOO_LARGE
:
500 cups_set_error(IPP_REQUEST_VALUE
, httpStatus(status
));
503 case HTTP_NOT_IMPLEMENTED
:
504 cups_set_error(IPP_OPERATION_NOT_SUPPORTED
, httpStatus(status
));
507 case HTTP_NOT_SUPPORTED
:
508 cups_set_error(IPP_VERSION_NOT_SUPPORTED
, httpStatus(status
));
512 DEBUG_printf(("HTTP error %d mapped to IPP_SERVICE_UNAVAILABLE!\n",
514 cups_set_error(IPP_SERVICE_UNAVAILABLE
, httpStatus(status
));
524 * 'cupsFreeJobs()' - Free memory used by job data.
528 cupsFreeJobs(int num_jobs
, /* I - Number of jobs */
529 cups_job_t
*jobs
) /* I - Jobs */
531 int i
; /* Looping var */
534 if (num_jobs
<= 0 || jobs
== NULL
)
537 for (i
= 0; i
< num_jobs
; i
++)
541 free(jobs
[i
].format
);
550 * 'cupsGetClasses()' - Get a list of printer classes from the default server.
552 * This function is deprecated - use cupsGetDests() instead.
557 int /* O - Number of classes */
558 cupsGetClasses(char ***classes
) /* O - Classes */
560 int n
; /* Number of classes */
561 ipp_t
*request
, /* IPP Request */
562 *response
; /* IPP Response */
563 ipp_attribute_t
*attr
; /* Current attribute */
564 cups_lang_t
*language
; /* Default language */
565 char **temp
; /* Temporary pointer */
566 _cups_globals_t
*cg
= _cupsGlobals(); /* Pointer to library globals */
571 cups_set_error(IPP_INTERNAL_ERROR
, NULL
);
577 * Try to connect to the server...
580 if (!cups_connect("default", NULL
, NULL
))
582 DEBUG_puts("Unable to connect to server!");
588 * Build a CUPS_GET_CLASSES request, which requires the following
592 * attributes-natural-language
593 * requested-attributes
598 request
->request
.op
.operation_id
= CUPS_GET_CLASSES
;
599 request
->request
.op
.request_id
= 1;
601 language
= cupsLangDefault();
603 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_CHARSET
,
604 "attributes-charset", NULL
, cupsLangEncoding(language
));
606 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_LANGUAGE
,
607 "attributes-natural-language", NULL
, language
->language
);
609 cupsLangFree(language
);
611 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
612 "requested-attributes", NULL
, "printer-name");
615 * Do the request and get back a response...
621 if ((response
= cupsDoRequest(cg
->http
, request
, "/")) != NULL
)
623 for (attr
= response
->attrs
; attr
!= NULL
; attr
= attr
->next
)
624 if (attr
->name
!= NULL
&&
625 strcasecmp(attr
->name
, "printer-name") == 0 &&
626 attr
->value_tag
== IPP_TAG_NAME
)
629 temp
= malloc(sizeof(char *));
631 temp
= realloc(*classes
, sizeof(char *) * (n
+ 1));
651 temp
[n
] = strdup(attr
->values
[0].string
.text
);
663 * 'cupsGetDefault()' - Get the default printer or class for the default server.
665 * This function returns the default printer or class as defined by
666 * the LPDEST or PRINTER environment variables. If these environment
667 * variables are not set, the server default destination is returned.
668 * Applications should use the cupsGetDests() and cupsGetDest() functions
669 * to get the user-defined default printer, as this function does not
670 * support the lpoptions-defined default printer.
673 const char * /* O - Default printer or NULL */
676 const char *var
; /* Environment variable */
677 _cups_globals_t
*cg
= _cupsGlobals(); /* Pointer to library globals */
681 * First see if the LPDEST or PRINTER environment variables are
682 * set... However, if PRINTER is set to "lp", ignore it to work
683 * around a "feature" in most Linux distributions - the default
684 * user login scripts set PRINTER to "lp"...
687 if ((var
= getenv("LPDEST")) != NULL
)
689 else if ((var
= getenv("PRINTER")) != NULL
&& strcmp(var
, "lp") != 0)
693 * Try to connect to the server...
696 if (!cups_connect("default", NULL
, NULL
))
698 DEBUG_puts("Unable to connect to server!");
704 * Return the default printer...
707 return (cupsGetDefault2(cg
->http
));
712 * 'cupsGetDefault2()' - Get the default printer or class for the specified server.
714 * This function returns the default printer or class as defined by
715 * the LPDEST or PRINTER environment variables. If these environment
716 * variables are not set, the server default destination is returned.
717 * Applications should use the cupsGetDests() and cupsGetDest() functions
718 * to get the user-defined default printer, as this function does not
719 * support the lpoptions-defined default printer.
721 * @since CUPS 1.1.21@
724 const char * /* O - Default printer or NULL */
725 cupsGetDefault2(http_t
*http
) /* I - HTTP connection */
727 ipp_t
*request
, /* IPP Request */
728 *response
; /* IPP Response */
729 ipp_attribute_t
*attr
; /* Current attribute */
730 cups_lang_t
*language
; /* Default language */
731 const char *var
; /* Environment variable */
732 _cups_globals_t
*cg
= _cupsGlobals(); /* Pointer to library globals */
736 * First see if the LPDEST or PRINTER environment variables are
737 * set... However, if PRINTER is set to "lp", ignore it to work
738 * around a "feature" in most Linux distributions - the default
739 * user login scripts set PRINTER to "lp"...
742 if ((var
= getenv("LPDEST")) != NULL
)
744 else if ((var
= getenv("PRINTER")) != NULL
&& strcmp(var
, "lp") != 0)
748 * Range check input...
755 * Build a CUPS_GET_DEFAULT request, which requires the following
759 * attributes-natural-language
764 request
->request
.op
.operation_id
= CUPS_GET_DEFAULT
;
765 request
->request
.op
.request_id
= 1;
767 language
= cupsLangDefault();
769 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_CHARSET
,
770 "attributes-charset", NULL
, cupsLangEncoding(language
));
772 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_LANGUAGE
,
773 "attributes-natural-language", NULL
, language
->language
);
775 cupsLangFree(language
);
778 * Do the request and get back a response...
781 if ((response
= cupsDoRequest(http
, request
, "/")) != NULL
)
783 if ((attr
= ippFindAttribute(response
, "printer-name", IPP_TAG_NAME
)) != NULL
)
785 strlcpy(cg
->def_printer
, attr
->values
[0].string
.text
, sizeof(cg
->def_printer
));
787 return (cg
->def_printer
);
798 * 'cupsGetJobs()' - Get the jobs from the default server.
801 int /* O - Number of jobs */
802 cupsGetJobs(cups_job_t
**jobs
, /* O - Job data */
803 const char *mydest
, /* I - Only show jobs for dest? */
804 int myjobs
, /* I - Only show my jobs? */
805 int completed
) /* I - Only show completed jobs? */
807 _cups_globals_t
*cg
= _cupsGlobals(); /* Pointer to library globals */
810 * Try to connect to the server...
813 if (!cups_connect("default", NULL
, NULL
))
815 DEBUG_puts("Unable to connect to server!");
824 return (cupsGetJobs2(cg
->http
, jobs
, mydest
, myjobs
, completed
));
830 * 'cupsGetJobs2()' - Get the jobs from the specified server.
832 * @since CUPS 1.1.21@
835 int /* O - Number of jobs */
836 cupsGetJobs2(http_t
*http
, /* I - HTTP connection */
837 cups_job_t
**jobs
, /* O - Job data */
838 const char *mydest
, /* I - Only show jobs for dest? */
839 int myjobs
, /* I - Only show my jobs? */
840 int completed
) /* I - Only show completed jobs? */
842 int n
; /* Number of jobs */
843 ipp_t
*request
, /* IPP Request */
844 *response
; /* IPP Response */
845 ipp_attribute_t
*attr
; /* Current attribute */
846 cups_lang_t
*language
; /* Default language */
847 cups_job_t
*temp
; /* Temporary pointer */
849 priority
, /* job-priority */
850 size
; /* job-k-octets */
851 ipp_jstate_t state
; /* job-state */
852 time_t completed_time
, /* time-at-completed */
853 creation_time
, /* time-at-creation */
854 processing_time
; /* time-at-processing */
855 const char *dest
, /* job-printer-uri */
856 *format
, /* document-format */
857 *title
, /* job-name */
858 *user
; /* job-originating-user-name */
859 char uri
[HTTP_MAX_URI
]; /* URI for jobs */
860 _cups_globals_t
*cg
= _cupsGlobals(); /* Pointer to library globals */
861 static const char * const attrs
[] = /* Requested attributes */
869 "time-at-processing",
873 "job-originating-user-name"
878 * Range check input...
883 cups_set_error(IPP_INTERNAL_ERROR
, NULL
);
889 * Get the right URI...
894 if (httpAssembleURIf(HTTP_URI_CODING_ALL
, uri
, sizeof(uri
), "ipp", NULL
,
895 "localhost", 0, "/printers/%s", mydest
) != HTTP_URI_OK
)
897 cups_set_error(IPP_INTERNAL_ERROR
, NULL
);
903 strcpy(uri
, "ipp://localhost/jobs");
907 * Build an IPP_GET_JOBS request, which requires the following
911 * attributes-natural-language
913 * requesting-user-name
916 * requested-attributes
921 request
->request
.op
.operation_id
= IPP_GET_JOBS
;
922 request
->request
.op
.request_id
= 1;
924 language
= cupsLangDefault();
926 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_CHARSET
,
927 "attributes-charset", NULL
, cupsLangEncoding(language
));
929 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_LANGUAGE
,
930 "attributes-natural-language", NULL
, language
->language
);
932 cupsLangFree(language
);
934 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
,
935 "printer-uri", NULL
, uri
);
937 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
938 "requesting-user-name", NULL
, cupsUser());
941 ippAddBoolean(request
, IPP_TAG_OPERATION
, "my-jobs", 1);
944 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
945 "which-jobs", NULL
, "completed");
947 ippAddStrings(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
948 "requested-attributes", sizeof(attrs
) / sizeof(attrs
[0]),
952 * Do the request and get back a response...
958 if ((response
= cupsDoRequest(http
, request
, "/")) != NULL
)
960 for (attr
= response
->attrs
; attr
!= NULL
; attr
= attr
->next
)
963 * Skip leading attributes until we hit a job...
966 while (attr
!= NULL
&& attr
->group_tag
!= IPP_TAG_JOB
)
973 * Pull the needed attributes from this job...
979 state
= IPP_JOB_PENDING
;
982 format
= "application/octet-stream";
988 while (attr
!= NULL
&& attr
->group_tag
== IPP_TAG_JOB
)
990 if (strcmp(attr
->name
, "job-id") == 0 &&
991 attr
->value_tag
== IPP_TAG_INTEGER
)
992 id
= attr
->values
[0].integer
;
993 else if (strcmp(attr
->name
, "job-state") == 0 &&
994 attr
->value_tag
== IPP_TAG_ENUM
)
995 state
= (ipp_jstate_t
)attr
->values
[0].integer
;
996 else if (strcmp(attr
->name
, "job-priority") == 0 &&
997 attr
->value_tag
== IPP_TAG_INTEGER
)
998 priority
= attr
->values
[0].integer
;
999 else if (strcmp(attr
->name
, "job-k-octets") == 0 &&
1000 attr
->value_tag
== IPP_TAG_INTEGER
)
1001 size
= attr
->values
[0].integer
;
1002 else if (strcmp(attr
->name
, "time-at-completed") == 0 &&
1003 attr
->value_tag
== IPP_TAG_INTEGER
)
1004 completed_time
= attr
->values
[0].integer
;
1005 else if (strcmp(attr
->name
, "time-at-creation") == 0 &&
1006 attr
->value_tag
== IPP_TAG_INTEGER
)
1007 creation_time
= attr
->values
[0].integer
;
1008 else if (strcmp(attr
->name
, "time-at-processing") == 0 &&
1009 attr
->value_tag
== IPP_TAG_INTEGER
)
1010 processing_time
= attr
->values
[0].integer
;
1011 else if (strcmp(attr
->name
, "job-printer-uri") == 0 &&
1012 attr
->value_tag
== IPP_TAG_URI
)
1014 if ((dest
= strrchr(attr
->values
[0].string
.text
, '/')) != NULL
)
1017 else if (strcmp(attr
->name
, "job-originating-user-name") == 0 &&
1018 attr
->value_tag
== IPP_TAG_NAME
)
1019 user
= attr
->values
[0].string
.text
;
1020 else if (strcmp(attr
->name
, "document-format") == 0 &&
1021 attr
->value_tag
== IPP_TAG_MIMETYPE
)
1022 format
= attr
->values
[0].string
.text
;
1023 else if (strcmp(attr
->name
, "job-name") == 0 &&
1024 (attr
->value_tag
== IPP_TAG_TEXT
||
1025 attr
->value_tag
== IPP_TAG_NAME
))
1026 title
= attr
->values
[0].string
.text
;
1032 * See if we have everything needed...
1035 if (dest
== NULL
|| id
== 0)
1044 * Allocate memory for the job...
1048 temp
= malloc(sizeof(cups_job_t
));
1050 temp
= realloc(*jobs
, sizeof(cups_job_t
) * (n
+ 1));
1055 * Ran out of memory!
1058 cupsFreeJobs(n
, *jobs
);
1061 ippDelete(response
);
1070 * Copy the data over...
1073 temp
->dest
= strdup(dest
);
1074 temp
->user
= strdup(user
);
1075 temp
->format
= strdup(format
);
1076 temp
->title
= strdup(title
);
1078 temp
->priority
= priority
;
1079 temp
->state
= state
;
1081 temp
->completed_time
= completed_time
;
1082 temp
->creation_time
= creation_time
;
1083 temp
->processing_time
= processing_time
;
1089 ippDelete(response
);
1092 if (n
== 0 && cg
->last_error
>= IPP_BAD_REQUEST
)
1100 * 'cupsGetPPD()' - Get the PPD file for a printer on the default server.
1102 * For classes, cupsGetPPD() returns the PPD file for the first printer
1106 const char * /* O - Filename for PPD file */
1107 cupsGetPPD(const char *name
) /* I - Printer name */
1109 _cups_globals_t
*cg
= _cupsGlobals(); /* Pointer to library globals */
1112 * See if we can connect to the server...
1115 if (!cups_connect(name
, NULL
, NULL
))
1117 DEBUG_puts("Unable to connect to server!");
1123 * Return the PPD file...
1126 return (cupsGetPPD2(cg
->http
, name
));
1131 * 'cupsGetPPD2()' - Get the PPD file for a printer from the specified server.
1133 * For classes, cupsGetPPD2() returns the PPD file for the first printer
1136 * @since CUPS 1.1.21@
1139 const char * /* O - Filename for PPD file */
1140 cupsGetPPD2(http_t
*http
, /* I - HTTP connection */
1141 const char *name
) /* I - Printer name */
1143 int http_port
; /* Port number */
1144 http_t
*http2
; /* Alternate HTTP connection */
1145 int fd
; /* PPD file */
1146 char localhost
[HTTP_MAX_URI
],/* Local hostname */
1147 hostname
[HTTP_MAX_URI
], /* Hostname */
1148 resource
[HTTP_MAX_URI
]; /* Resource name */
1149 int port
; /* Port number */
1150 http_status_t status
; /* HTTP status from server */
1151 _cups_globals_t
*cg
= _cupsGlobals(); /* Pointer to library globals */
1155 * Range check input...
1158 DEBUG_printf(("cupsGetPPD2(http=%p, name=\"%s\")\n", http
,
1159 name
? name
: "(null)"));
1163 cups_set_error(IPP_INTERNAL_ERROR
, NULL
);
1169 * Try finding a printer URI for this printer...
1172 if (!cups_get_printer_uri(http
, name
, hostname
, sizeof(hostname
), &port
,
1173 resource
, sizeof(resource
), 0))
1177 * Remap local hostname to localhost...
1180 httpGetHostname(localhost
, sizeof(localhost
));
1182 if (!strcasecmp(localhost
, hostname
))
1183 strcpy(hostname
, "localhost");
1186 * Get the port number we are connected to...
1190 if (http
->hostaddr
->addr
.sa_family
== AF_INET6
)
1191 http_port
= ntohs(http
->hostaddr
->ipv6
.sin6_port
);
1193 #endif /* AF_INET6 */
1194 if (http
->hostaddr
->addr
.sa_family
== AF_INET
)
1195 http_port
= ntohs(http
->hostaddr
->ipv4
.sin_port
);
1197 http_port
= ippPort();
1200 * Reconnect to the correct server as needed...
1203 if (!strcasecmp(http
->hostname
, hostname
) && port
== http_port
)
1205 else if ((http2
= httpConnectEncrypt(hostname
, port
,
1206 cupsEncryption())) == NULL
)
1208 DEBUG_puts("Unable to connect to server!");
1214 * Get a temp file...
1217 if ((fd
= cupsTempFd(cg
->ppd_filename
, sizeof(cg
->ppd_filename
))) < 0)
1220 * Can't open file; close the server connection and return NULL...
1223 cups_set_error(IPP_INTERNAL_ERROR
, strerror(errno
));
1232 * And send a request to the HTTP server...
1235 strlcat(resource
, ".ppd", sizeof(resource
));
1237 status
= cupsGetFd(http2
, resource
, fd
);
1245 * See if we actually got the file or an error...
1248 if (status
!= HTTP_OK
)
1252 case HTTP_NOT_FOUND
:
1253 cups_set_error(IPP_NOT_FOUND
, httpStatus(status
));
1256 case HTTP_UNAUTHORIZED
:
1257 cups_set_error(IPP_NOT_AUTHORIZED
, httpStatus(status
));
1261 DEBUG_printf(("HTTP error %d mapped to IPP_SERVICE_UNAVAILABLE!\n",
1263 cups_set_error(IPP_SERVICE_UNAVAILABLE
, httpStatus(status
));
1267 unlink(cg
->ppd_filename
);
1273 * Return the PPD file...
1276 return (cg
->ppd_filename
);
1281 * 'cupsGetPrinters()' - Get a list of printers from the default server.
1283 * This function is deprecated - use cupsGetDests() instead.
1288 int /* O - Number of printers */
1289 cupsGetPrinters(char ***printers
) /* O - Printers */
1291 int n
; /* Number of printers */
1292 ipp_t
*request
, /* IPP Request */
1293 *response
; /* IPP Response */
1294 ipp_attribute_t
*attr
; /* Current attribute */
1295 cups_lang_t
*language
; /* Default language */
1296 char **temp
; /* Temporary pointer */
1297 _cups_globals_t
*cg
= _cupsGlobals(); /* Pointer to library globals */
1300 if (printers
== NULL
)
1302 cups_set_error(IPP_INTERNAL_ERROR
, NULL
);
1308 * Try to connect to the server...
1311 if (!cups_connect("default", NULL
, NULL
))
1313 DEBUG_puts("Unable to connect to server!");
1319 * Build a CUPS_GET_PRINTERS request, which requires the following
1322 * attributes-charset
1323 * attributes-natural-language
1324 * requested-attributes
1329 request
->request
.op
.operation_id
= CUPS_GET_PRINTERS
;
1330 request
->request
.op
.request_id
= 1;
1332 language
= cupsLangDefault();
1334 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_CHARSET
,
1335 "attributes-charset", NULL
, cupsLangEncoding(language
));
1337 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_LANGUAGE
,
1338 "attributes-natural-language", NULL
, language
->language
);
1340 cupsLangFree(language
);
1342 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
,
1343 "requested-attributes", NULL
, "printer-name");
1345 ippAddInteger(request
, IPP_TAG_OPERATION
, IPP_TAG_ENUM
,
1348 ippAddInteger(request
, IPP_TAG_OPERATION
, IPP_TAG_ENUM
,
1349 "printer-type-mask", CUPS_PRINTER_CLASS
);
1352 * Do the request and get back a response...
1358 if ((response
= cupsDoRequest(cg
->http
, request
, "/")) != NULL
)
1360 for (attr
= response
->attrs
; attr
!= NULL
; attr
= attr
->next
)
1361 if (attr
->name
!= NULL
&&
1362 strcasecmp(attr
->name
, "printer-name") == 0 &&
1363 attr
->value_tag
== IPP_TAG_NAME
)
1366 temp
= malloc(sizeof(char *));
1368 temp
= realloc(*printers
, sizeof(char *) * (n
+ 1));
1373 * Ran out of memory!
1379 free((*printers
)[n
]);
1383 ippDelete(response
);
1388 temp
[n
] = strdup(attr
->values
[0].string
.text
);
1392 ippDelete(response
);
1400 * 'cupsLastError()' - Return the last IPP status code.
1403 ipp_status_t
/* O - IPP status code from last request */
1406 return (_cupsGlobals()->last_error
);
1411 * 'cupsLastErrorString()' - Return the last IPP status-message.
1416 const char * /* O - status-message text from last request */
1417 cupsLastErrorString(void)
1419 return (_cupsGlobals()->last_status_message
);
1424 * 'cupsPrintFile()' - Print a file to a printer or class on the default server.
1427 int /* O - Job ID */
1428 cupsPrintFile(const char *name
, /* I - Printer or class name */
1429 const char *filename
, /* I - File to print */
1430 const char *title
, /* I - Title of job */
1431 int num_options
,/* I - Number of options */
1432 cups_option_t
*options
) /* I - Options */
1434 DEBUG_printf(("cupsPrintFile(name=\"%s\", filename=\"%s\", "
1435 "title=\"%s\", num_options=%d, options=%p)\n",
1436 name
, filename
, title
, num_options
, options
));
1438 return (cupsPrintFiles(name
, 1, &filename
, title
, num_options
, options
));
1443 * 'cupsPrintFile2()' - Print a file to a printer or class on the specified server.
1445 * @since CUPS 1.1.21@
1448 int /* O - Job ID */
1449 cupsPrintFile2(http_t
*http
, /* I - HTTP connection */
1450 const char *name
, /* I - Printer or class name */
1451 const char *filename
, /* I - File to print */
1452 const char *title
, /* I - Title of job */
1454 /* I - Number of options */
1455 cups_option_t
*options
) /* I - Options */
1457 DEBUG_printf(("cupsPrintFile2(http=%p, name=\"%s\", filename=\"%s\", "
1458 "title=\"%s\", num_options=%d, options=%p)\n",
1459 http
, name
, filename
, title
, num_options
, options
));
1461 return (cupsPrintFiles2(http
, name
, 1, &filename
, title
, num_options
, options
));
1466 * 'cupsPrintFiles()' - Print one or more files to a printer or class on the
1470 int /* O - Job ID */
1471 cupsPrintFiles(const char *name
, /* I - Printer or class name */
1472 int num_files
, /* I - Number of files */
1473 const char **files
, /* I - File(s) to print */
1474 const char *title
, /* I - Title of job */
1476 /* I - Number of options */
1477 cups_option_t
*options
) /* I - Options */
1479 _cups_globals_t
*cg
= _cupsGlobals(); /* Pointer to library globals */
1481 DEBUG_printf(("cupsPrintFiles(name=\"%s\", num_files=%d, "
1482 "files=%p, title=\"%s\", num_options=%d, options=%p)\n",
1483 name
, num_files
, files
, title
, num_options
, options
));
1487 * Setup a connection and request data...
1490 if (!cups_connect(name
, NULL
, NULL
))
1492 DEBUG_printf(("cupsPrintFiles: Unable to open connection - %s.\n",
1494 DEBUG_puts("Unable to connect to server!");
1500 * Print the file(s)...
1503 return (cupsPrintFiles2(cg
->http
, name
, num_files
, files
, title
,
1504 num_options
, options
));
1510 * 'cupsPrintFiles2()' - Print one or more files to a printer or class on the
1513 * @since CUPS 1.1.21@
1516 int /* O - Job ID */
1517 cupsPrintFiles2(http_t
*http
, /* I - HTTP connection */
1518 const char *name
, /* I - Printer or class name */
1519 int num_files
,/* I - Number of files */
1520 const char **files
, /* I - File(s) to print */
1521 const char *title
, /* I - Title of job */
1523 /* I - Number of options */
1524 cups_option_t
*options
) /* I - Options */
1526 int i
; /* Looping var */
1527 const char *val
; /* Pointer to option value */
1528 ipp_t
*request
; /* IPP request */
1529 ipp_t
*response
; /* IPP response */
1530 ipp_attribute_t
*attr
; /* IPP job-id attribute */
1531 char uri
[HTTP_MAX_URI
]; /* Printer URI */
1532 cups_lang_t
*language
; /* Language to use */
1533 int jobid
; /* New job ID */
1534 const char *base
; /* Basename of current filename */
1537 DEBUG_printf(("cupsPrintFiles(http=%p, name=\"%s\", num_files=%d, "
1538 "files=%p, title=\"%s\", num_options=%d, options=%p)\n",
1539 http
, name
, num_files
, files
, title
, num_options
, options
));
1542 * Range check input...
1545 if (!http
|| !name
|| num_files
< 1 || files
== NULL
)
1547 cups_set_error(IPP_INTERNAL_ERROR
, NULL
);
1553 * Setup the printer URI...
1556 if (httpAssembleURIf(HTTP_URI_CODING_ALL
, uri
, sizeof(uri
), "ipp", NULL
,
1557 "localhost", 0, "/printers/%s", name
) != HTTP_URI_OK
)
1559 cups_set_error(IPP_INTERNAL_ERROR
, NULL
);
1565 * Setup the request data...
1568 language
= cupsLangDefault();
1571 * Build a standard CUPS URI for the printer and fill the standard IPP
1575 if ((request
= ippNew()) == NULL
)
1577 cups_set_error(IPP_INTERNAL_ERROR
, NULL
);
1582 request
->request
.op
.operation_id
= num_files
== 1 ? IPP_PRINT_JOB
:
1584 request
->request
.op
.request_id
= 1;
1586 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_CHARSET
,
1587 "attributes-charset", NULL
, cupsLangEncoding(language
));
1589 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_LANGUAGE
,
1590 "attributes-natural-language", NULL
,
1591 language
!= NULL
? language
->language
: "C");
1593 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
1596 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
, "requesting-user-name",
1600 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
, "job-name", NULL
,
1604 * Then add all options...
1607 cupsEncodeOptions(request
, num_options
, options
);
1613 snprintf(uri
, sizeof(uri
), "/printers/%s", name
);
1616 response
= cupsDoFileRequest(http
, request
, uri
, *files
);
1618 response
= cupsDoRequest(http
, request
, uri
);
1620 if (response
== NULL
)
1622 else if (response
->request
.status
.status_code
> IPP_OK_CONFLICT
)
1624 DEBUG_printf(("IPP response code was 0x%x!\n",
1625 response
->request
.status
.status_code
));
1628 else if ((attr
= ippFindAttribute(response
, "job-id", IPP_TAG_INTEGER
)) == NULL
)
1630 DEBUG_puts("No job ID!");
1632 cups_set_error(IPP_INTERNAL_ERROR
, NULL
);
1637 jobid
= attr
->values
[0].integer
;
1639 if (response
!= NULL
)
1640 ippDelete(response
);
1643 * Handle multiple file jobs if the create-job operation worked...
1646 if (jobid
> 0 && num_files
> 1)
1647 for (i
= 0; i
< num_files
; i
++)
1650 * Build a standard CUPS URI for the job and fill the standard IPP
1654 if ((request
= ippNew()) == NULL
)
1657 request
->request
.op
.operation_id
= IPP_SEND_DOCUMENT
;
1658 request
->request
.op
.request_id
= 1;
1660 snprintf(uri
, sizeof(uri
), "ipp://localhost/jobs/%d", jobid
);
1662 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_CHARSET
,
1663 "attributes-charset", NULL
, cupsLangEncoding(language
));
1665 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_LANGUAGE
,
1666 "attributes-natural-language", NULL
,
1667 language
!= NULL
? language
->language
: "C");
1669 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "job-uri",
1673 * Handle raw print files...
1676 if (cupsGetOption("raw", num_options
, options
))
1677 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_MIMETYPE
,
1678 "document-format", NULL
, "application/vnd.cups-raw");
1679 else if ((val
= cupsGetOption("document-format", num_options
,
1681 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_MIMETYPE
,
1682 "document-format", NULL
, val
);
1684 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_MIMETYPE
,
1685 "document-format", NULL
, "application/octet-stream");
1687 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
1688 "requesting-user-name", NULL
, cupsUser());
1691 * Add the original document filename...
1694 if ((base
= strrchr(files
[i
], '/')) != NULL
)
1699 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
, "document-name",
1703 * Is this the last document?
1706 if (i
== (num_files
- 1))
1707 ippAddBoolean(request
, IPP_TAG_OPERATION
, "last-document", 1);
1713 snprintf(uri
, sizeof(uri
), "/printers/%s", name
);
1715 if ((response
= cupsDoFileRequest(http
, request
, uri
,
1717 ippDelete(response
);
1720 cupsLangFree(language
);
1727 * 'cups_connect()' - Connect to the specified host...
1730 static char * /* I - Printer name or NULL */
1731 cups_connect(const char *name
, /* I - Destination (printer[@host]) */
1732 char *printer
, /* O - Printer name [HTTP_MAX_URI] */
1733 char *hostname
) /* O - Hostname [HTTP_MAX_URI] */
1735 char hostbuf
[HTTP_MAX_URI
]; /* Name of host */
1736 _cups_globals_t
*cg
= _cupsGlobals();/* Pointer to library globals */
1739 DEBUG_printf(("cups_connect(\"%s\", %p, %p)\n", name
, printer
, hostname
));
1743 cups_set_error(IPP_BAD_REQUEST
, NULL
);
1749 * All jobs are now queued to cupsServer() to avoid hostname
1750 * resolution problems and to ensure that the user sees all
1751 * locally queued jobs locally.
1754 strlcpy(hostbuf
, cupsServer(), sizeof(hostbuf
));
1756 if (hostname
!= NULL
)
1757 strlcpy(hostname
, hostbuf
, HTTP_MAX_URI
);
1761 if (printer
!= NULL
)
1762 strlcpy(printer
, name
, HTTP_MAX_URI
);
1764 printer
= (char *)name
;
1766 if (cg
->http
!= NULL
)
1768 if (!strcasecmp(cg
->http
->hostname
, hostname
))
1771 httpClose(cg
->http
);
1774 DEBUG_printf(("connecting to %s on port %d...\n", hostname
, ippPort()));
1776 if ((cg
->http
= httpConnectEncrypt(hostname
, ippPort(),
1777 cupsEncryption())) == NULL
)
1779 DEBUG_puts("Unable to connect to server!");
1781 cups_set_error(IPP_SERVICE_UNAVAILABLE
, strerror(errno
));
1791 * 'cups_get_printer_uri()' - Get the printer-uri-supported attribute for the first printer in a class.
1794 static int /* O - 1 on success, 0 on failure */
1795 cups_get_printer_uri(
1796 http_t
*http
, /* I - HTTP connection */
1797 const char *name
, /* I - Name of printer or class */
1798 char *host
, /* I - Hostname buffer */
1799 int hostsize
, /* I - Size of hostname buffer */
1800 int *port
, /* O - Port number */
1801 char *resource
, /* I - Resource buffer */
1802 int resourcesize
, /* I - Size of resource buffer */
1803 int depth
) /* I - Depth of query */
1805 int i
; /* Looping var */
1806 int http_port
; /* Port number */
1807 http_t
*http2
; /* Alternate HTTP connection */
1808 ipp_t
*request
, /* IPP request */
1809 *response
; /* IPP response */
1810 ipp_attribute_t
*attr
; /* Current attribute */
1811 char uri
[HTTP_MAX_URI
], /* printer-uri attribute */
1812 scheme
[HTTP_MAX_URI
], /* Scheme name */
1813 username
[HTTP_MAX_URI
], /* Username:password */
1814 classname
[255]; /* Temporary class name */
1815 static const char * const requested_attrs
[] =
1816 { /* Requested attributes */
1817 "printer-uri-supported",
1823 DEBUG_printf(("cups_get_printer_uri(http=%p, name=\"%s\", host=%p, "
1824 "hostsize=%d, resource=%p, resourcesize=%d, depth=%d)\n",
1825 http
, name
? name
: "(null)", host
, hostsize
,
1826 resource
, resourcesize
, depth
));
1829 * Setup the printer URI...
1832 if (httpAssembleURIf(HTTP_URI_CODING_ALL
, uri
, sizeof(uri
), "ipp", NULL
,
1833 "localhost", 0, "/printers/%s", name
) != HTTP_URI_OK
)
1835 cups_set_error(IPP_INTERNAL_ERROR
, NULL
);
1843 DEBUG_printf(("cups_get_printer_uri: printer-uri=\"%s\"\n", uri
));
1846 * Get the port number we are connected to...
1850 if (http
->hostaddr
->addr
.sa_family
== AF_INET6
)
1851 http_port
= ntohs(http
->hostaddr
->ipv6
.sin6_port
);
1853 #endif /* AF_INET6 */
1854 if (http
->hostaddr
->addr
.sa_family
== AF_INET
)
1855 http_port
= ntohs(http
->hostaddr
->ipv4
.sin_port
);
1857 http_port
= ippPort();
1860 * Build an IPP_GET_PRINTER_ATTRIBUTES request, which requires the following
1863 * attributes-charset
1864 * attributes-natural-language
1866 * requested-attributes
1869 request
= ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES
);
1871 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
, "printer-uri",
1874 ippAddStrings(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
1875 "requested-attributes",
1876 sizeof(requested_attrs
) / sizeof(requested_attrs
[0]),
1877 NULL
, requested_attrs
);
1880 * Do the request and get back a response...
1883 if ((response
= cupsDoRequest(http
, request
, "/")) != NULL
)
1885 if ((attr
= ippFindAttribute(response
, "member-uris", IPP_TAG_URI
)) != NULL
)
1888 * Get the first actual printer name in the class...
1891 for (i
= 0; i
< attr
->num_values
; i
++)
1893 httpSeparateURI(HTTP_URI_CODING_ALL
, attr
->values
[i
].string
.text
,
1894 scheme
, sizeof(scheme
), username
, sizeof(username
),
1895 host
, hostsize
, port
, resource
, resourcesize
);
1896 if (!strncmp(resource
, "/printers/", 10))
1902 ippDelete(response
);
1909 * No printers in this class - try recursively looking for a printer,
1910 * but not more than 3 levels deep...
1915 for (i
= 0; i
< attr
->num_values
; i
++)
1917 httpSeparateURI(HTTP_URI_CODING_ALL
, attr
->values
[i
].string
.text
,
1918 scheme
, sizeof(scheme
), username
, sizeof(username
),
1919 host
, hostsize
, port
, resource
, resourcesize
);
1920 if (!strncmp(resource
, "/classes/", 9))
1923 * Found a class! Connect to the right server...
1926 if (!strcasecmp(http
->hostname
, host
) && *port
== http_port
)
1928 else if ((http2
= httpConnectEncrypt(host
, *port
,
1929 cupsEncryption())) == NULL
)
1931 DEBUG_puts("Unable to connect to server!");
1937 * Look up printers on that server...
1940 strlcpy(classname
, resource
+ 9, sizeof(classname
));
1942 cups_get_printer_uri(http2
, classname
, host
, hostsize
, port
,
1943 resource
, resourcesize
, depth
+ 1);
1946 * Close the connection as needed...
1958 else if ((attr
= ippFindAttribute(response
, "printer-uri-supported",
1959 IPP_TAG_URI
)) != NULL
)
1961 httpSeparateURI(HTTP_URI_CODING_ALL
, attr
->values
[0].string
.text
,
1962 scheme
, sizeof(scheme
), username
, sizeof(username
),
1963 host
, hostsize
, port
, resource
, resourcesize
);
1964 ippDelete(response
);
1969 ippDelete(response
);
1980 * 'cups_set_error()' - Set the last IPP status code and status-message.
1984 cups_set_error(ipp_status_t status
, /* I - IPP status code */
1985 const char *message
) /* I - status-message value */
1987 _cups_globals_t
*cg
; /* Global data */
1990 cg
= _cupsGlobals();
1991 cg
->last_error
= status
;
1993 if (cg
->last_status_message
)
1995 free(cg
->last_status_message
);
1997 cg
->last_status_message
= NULL
;
2001 cg
->last_status_message
= strdup(message
);
2006 * End of "$Id: util.c 5064 2006-02-03 16:51:05Z mike $".