OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "printing/backend/cups_jobs.h" |
| 6 |
| 7 #include <cups/ipp.h> |
| 8 |
| 9 #include <array> |
| 10 #include <map> |
| 11 #include <memory> |
| 12 |
| 13 #include "base/logging.h" |
| 14 #include "base/strings/string_piece.h" |
| 15 #include "base/strings/stringprintf.h" |
| 16 |
| 17 namespace printing { |
| 18 namespace { |
| 19 |
| 20 using PReason = PrinterStatus::PrinterReason::Reason; |
| 21 using PSeverity = PrinterStatus::PrinterReason::Severity; |
| 22 |
| 23 // printer attributes |
| 24 const char kPrinterUri[] = "printer-uri"; |
| 25 const char kPrinterState[] = "printer-state"; |
| 26 const char kPrinterStateReasons[] = "printer-state-reasons"; |
| 27 const char kPrinterStateMessage[] = "printer-state-message"; |
| 28 |
| 29 // job attributes |
| 30 const char kJobUri[] = "job-uri"; |
| 31 const char kJobId[] = "job-id"; |
| 32 const char kJobState[] = "job-state"; |
| 33 const char kJobStateReasons[] = "job-state-reasons"; |
| 34 const char kJobStateMessage[] = "job-state-message"; |
| 35 const char kJobImpressionsCompleted[] = "job-impressions-completed"; |
| 36 const char kTimeAtProcessing[] = "time-at-processing"; |
| 37 |
| 38 // request parameters |
| 39 const char kRequestedAttributes[] = "requested-attributes"; |
| 40 const char kWhichJobs[] = "which-jobs"; |
| 41 const char kLimit[] = "limit"; |
| 42 |
| 43 // request values |
| 44 const char kCompleted[] = "completed"; |
| 45 const char kNotCompleted[] = "not-completed"; |
| 46 |
| 47 // printer state severities |
| 48 const char kSeverityReport[] = "report"; |
| 49 const char kSeverityWarn[] = "warning"; |
| 50 const char kSeverityError[] = "error"; |
| 51 |
| 52 // printer state reason values |
| 53 const char kNone[] = "none"; |
| 54 const char kMediaNeeded[] = "media-needed"; |
| 55 const char kMediaJam[] = "media-jam"; |
| 56 const char kMovingToPaused[] = "moving-to-paused"; |
| 57 const char kPaused[] = "paused"; |
| 58 const char kShutdown[] = "shutdown"; |
| 59 const char kConnectingToDevice[] = "connecting-to-device"; |
| 60 const char kTimedOut[] = "timed-out"; |
| 61 const char kStopping[] = "stopping"; |
| 62 const char kStoppedPartly[] = "stopped-partly"; |
| 63 const char kTonerLow[] = "toner-low"; |
| 64 const char kTonerEmpty[] = "toner-empty"; |
| 65 const char kSpoolAreaFull[] = "spool-area-full"; |
| 66 const char kCoverOpen[] = "cover-open"; |
| 67 const char kInterlockOpen[] = "interlock-open"; |
| 68 const char kDoorOpen[] = "door-open"; |
| 69 const char kInputTrayMissing[] = "input-tray-missing"; |
| 70 const char kMediaLow[] = "media-low"; |
| 71 const char kMediaEmpty[] = "media-empty"; |
| 72 const char kOutputTrayMissing[] = "output-tray-missing"; |
| 73 const char kOutputAreaAlmostFull[] = "output-area-almost-full"; |
| 74 const char kOutputAreaFull[] = "output-area-full"; |
| 75 const char kMarkerSupplyLow[] = "marker-supply-low"; |
| 76 const char kMarkerSupplyEmpty[] = "marker-supply-empty"; |
| 77 const char kMarkerWasteAlmostFull[] = "marker-waste-almost-full"; |
| 78 const char kMarkerWasteFull[] = "marker-waste-full"; |
| 79 const char kFuserOverTemp[] = "fuser-over-temp"; |
| 80 const char kFuserUnderTemp[] = "fuser-under-temp"; |
| 81 const char kOpcNearEol[] = "opc-near-eol"; |
| 82 const char kOpcLifeOver[] = "opc-life-over"; |
| 83 const char kDeveloperLow[] = "developer-low"; |
| 84 const char kDeveloperEmpty[] = "developer-empty"; |
| 85 const char kInterpreterResourceUnavailable[] = |
| 86 "interpreter-resource-unavailable"; |
| 87 |
| 88 constexpr std::array<const char* const, 3> kPrinterAttributes = { |
| 89 kPrinterState, kPrinterStateReasons, kPrinterStateMessage}; |
| 90 |
| 91 std::unique_ptr<ipp_t, void (*)(ipp_t*)> WrapIpp(ipp_t* ipp) { |
| 92 return std::unique_ptr<ipp_t, void (*)(ipp_t*)>(ipp, &ippDelete); |
| 93 } |
| 94 |
| 95 // Converts an IPP attribute |attr| to the appropriate JobState enum. |
| 96 CupsJob::JobState ToJobState(ipp_attribute_t* attr) { |
| 97 DCHECK_EQ(IPP_TAG_ENUM, ippGetValueTag(attr)); |
| 98 int state = ippGetInteger(attr, 0); |
| 99 switch (state) { |
| 100 case IPP_JOB_ABORTED: |
| 101 return CupsJob::ABORTED; |
| 102 case IPP_JOB_CANCELLED: |
| 103 return CupsJob::CANCELED; |
| 104 case IPP_JOB_COMPLETED: |
| 105 return CupsJob::COMPLETED; |
| 106 case IPP_JOB_HELD: |
| 107 return CupsJob::HELD; |
| 108 case IPP_JOB_PENDING: |
| 109 return CupsJob::PENDING; |
| 110 case IPP_JOB_PROCESSING: |
| 111 return CupsJob::PROCESSING; |
| 112 case IPP_JOB_STOPPED: |
| 113 return CupsJob::STOPPED; |
| 114 default: |
| 115 NOTREACHED() << "Unidentifed state " << state; |
| 116 break; |
| 117 } |
| 118 |
| 119 return CupsJob::UNKNOWN; |
| 120 } |
| 121 |
| 122 // Returns a lookup map from strings to PrinterReason::Reason. |
| 123 const std::map<base::StringPiece, PReason>& GetLabelToReason() { |
| 124 static const std::map<base::StringPiece, PReason> kLabelToReason = |
| 125 std::map<base::StringPiece, PReason>{ |
| 126 {kNone, PReason::NONE}, |
| 127 {kMediaNeeded, PReason::MEDIA_NEEDED}, |
| 128 {kMediaJam, PReason::MEDIA_JAM}, |
| 129 {kMovingToPaused, PReason::MOVING_TO_PAUSED}, |
| 130 {kPaused, PReason::PAUSED}, |
| 131 {kShutdown, PReason::SHUTDOWN}, |
| 132 {kConnectingToDevice, PReason::CONNECTING_TO_DEVICE}, |
| 133 {kTimedOut, PReason::TIMED_OUT}, |
| 134 {kStopping, PReason::STOPPING}, |
| 135 {kStoppedPartly, PReason::STOPPED_PARTLY}, |
| 136 {kTonerLow, PReason::TONER_LOW}, |
| 137 {kTonerEmpty, PReason::TONER_EMPTY}, |
| 138 {kSpoolAreaFull, PReason::SPOOL_AREA_FULL}, |
| 139 {kCoverOpen, PReason::COVER_OPEN}, |
| 140 {kInterlockOpen, PReason::INTERLOCK_OPEN}, |
| 141 {kDoorOpen, PReason::DOOR_OPEN}, |
| 142 {kInputTrayMissing, PReason::INPUT_TRAY_MISSING}, |
| 143 {kMediaLow, PReason::MEDIA_LOW}, |
| 144 {kMediaEmpty, PReason::MEDIA_EMPTY}, |
| 145 {kOutputTrayMissing, PReason::OUTPUT_TRAY_MISSING}, |
| 146 {kOutputAreaAlmostFull, PReason::OUTPUT_AREA_ALMOST_FULL}, |
| 147 {kOutputAreaFull, PReason::OUTPUT_AREA_FULL}, |
| 148 {kMarkerSupplyLow, PReason::MARKER_SUPPLY_LOW}, |
| 149 {kMarkerSupplyEmpty, PReason::MARKER_SUPPLY_EMPTY}, |
| 150 {kMarkerWasteAlmostFull, PReason::MARKER_WASTE_ALMOST_FULL}, |
| 151 {kMarkerWasteFull, PReason::MARKER_WASTE_FULL}, |
| 152 {kFuserOverTemp, PReason::FUSER_OVER_TEMP}, |
| 153 {kFuserUnderTemp, PReason::FUSER_UNDER_TEMP}, |
| 154 {kOpcNearEol, PReason::OPC_NEAR_EOL}, |
| 155 {kOpcLifeOver, PReason::OPC_LIFE_OVER}, |
| 156 {kDeveloperLow, PReason::DEVELOPER_LOW}, |
| 157 {kDeveloperEmpty, PReason::DEVELOPER_EMPTY}, |
| 158 {kInterpreterResourceUnavailable, |
| 159 PReason::INTERPRETER_RESOURCE_UNAVAILABLE}, |
| 160 }; |
| 161 return kLabelToReason; |
| 162 } |
| 163 |
| 164 // Returns the Reason cooresponding to the string |reason|. Returns |
| 165 // UNKOWN_REASON if the string is not recognized. |
| 166 PrinterStatus::PrinterReason::Reason ToReason(base::StringPiece reason) { |
| 167 const auto& enum_map = GetLabelToReason(); |
| 168 const auto& entry = enum_map.find(reason); |
| 169 return entry != enum_map.end() ? entry->second : PReason::UNKNOWN_REASON; |
| 170 } |
| 171 |
| 172 // Returns the Severity cooresponding to |severity|. Returns UNKNOWN_SEVERITY |
| 173 // if the strin gis not recognized. |
| 174 PSeverity ToSeverity(base::StringPiece severity) { |
| 175 if (severity == kSeverityError) |
| 176 return PSeverity::ERROR; |
| 177 |
| 178 if (severity == kSeverityWarn) |
| 179 return PSeverity::WARNING; |
| 180 |
| 181 if (severity == kSeverityReport) |
| 182 return PSeverity::REPORT; |
| 183 |
| 184 return PSeverity::UNKNOWN_SEVERITY; |
| 185 } |
| 186 |
| 187 // Parses the |reason| string into a PrinterReason. Splits the string based on |
| 188 // the last '-' to determine severity. If a recognized severity is not |
| 189 // included, severity is assumed to be ERROR per RFC2911. |
| 190 PrinterStatus::PrinterReason ToPrinterReason(base::StringPiece reason) { |
| 191 PrinterStatus::PrinterReason parsed; |
| 192 |
| 193 if (reason == kNone) { |
| 194 parsed.reason = PReason::NONE; |
| 195 parsed.severity = PSeverity::UNKNOWN_SEVERITY; |
| 196 return parsed; |
| 197 } |
| 198 |
| 199 size_t last_dash = reason.rfind('-'); |
| 200 auto severity = PSeverity::UNKNOWN_SEVERITY; |
| 201 if (last_dash != base::StringPiece::npos) { |
| 202 // try to parse the last part of the string as the severity. |
| 203 severity = ToSeverity(reason.substr(last_dash + 1)); |
| 204 } |
| 205 |
| 206 if (severity == PSeverity::UNKNOWN_SEVERITY) { |
| 207 // Severity is unknown. No severity in the reason. |
| 208 // Per spec, if there is no severity, severity is error. |
| 209 parsed.severity = PSeverity::ERROR; |
| 210 parsed.reason = ToReason(reason); |
| 211 } else { |
| 212 parsed.severity = severity; |
| 213 // reason is the beginning of the string |
| 214 parsed.reason = ToReason(reason.substr(0, last_dash)); |
| 215 } |
| 216 |
| 217 return parsed; |
| 218 } |
| 219 |
| 220 // Populates |collection| with the collection of strings in |attr|. |
| 221 void ParseCollection(ipp_attribute_t* attr, |
| 222 std::vector<std::string>* collection) { |
| 223 int count = ippGetCount(attr); |
| 224 for (int i = 0; i < count; i++) { |
| 225 base::StringPiece value = ippGetString(attr, i, nullptr); |
| 226 collection->push_back(value.as_string()); |
| 227 } |
| 228 } |
| 229 |
| 230 // Parse a field for the CupsJob |job| from IPP attribute |attr| using the |
| 231 // attribute name |name|. |
| 232 void ParseField(ipp_attribute_t* attr, base::StringPiece name, CupsJob* job) { |
| 233 DCHECK(!name.empty()); |
| 234 if (name == kJobId) { |
| 235 job->id = ippGetInteger(attr, 0); |
| 236 } else if (name == kJobImpressionsCompleted) { |
| 237 job->current_pages = ippGetInteger(attr, 0); |
| 238 } else if (name == kJobState) { |
| 239 job->state = ToJobState(attr); |
| 240 } else if (name == kJobStateReasons) { |
| 241 ParseCollection(attr, &(job->state_reasons)); |
| 242 } else if (name == kJobStateMessage) { |
| 243 job->state_message = ippGetString(attr, 0, nullptr); |
| 244 } else if (name == kTimeAtProcessing) { |
| 245 job->processing_started = ippGetInteger(attr, 0); |
| 246 } |
| 247 } |
| 248 |
| 249 // Returns a new CupsJob allocated in |jobs| with |printer_id| populated. |
| 250 CupsJob* NewJob(const std::string& printer_id, std::vector<CupsJob>* jobs) { |
| 251 jobs->emplace_back(); |
| 252 CupsJob* job = &jobs->back(); |
| 253 job->printer_id = printer_id; |
| 254 return job; |
| 255 } |
| 256 |
| 257 void ParseJobs(ipp_t* response, |
| 258 const std::string& printer_id, |
| 259 ipp_attribute_t* starting_attr, |
| 260 std::vector<CupsJob>* jobs) { |
| 261 // We know this is a non-empty job section. Start parsing fields for at least |
| 262 // one job. |
| 263 CupsJob* current_job = NewJob(printer_id, jobs); |
| 264 for (ipp_attribute_t* attr = starting_attr; attr != nullptr; |
| 265 attr = ippNextAttribute(response)) { |
| 266 base::StringPiece attribute_name = ippGetName(attr); |
| 267 // Separators indicate a new job. Separators have empty names. |
| 268 if (attribute_name.empty()) { |
| 269 current_job = NewJob(printer_id, jobs); |
| 270 continue; |
| 271 } |
| 272 |
| 273 // Continue to populate the job fileds. |
| 274 ParseField(attr, attribute_name, current_job); |
| 275 } |
| 276 } |
| 277 |
| 278 // Returns the uri for printer with |id| as served by CUPS. Assumes that |id| |
| 279 // is a valid CUPS printer name and performs no error checking or escaping. |
| 280 std::string PrinterUriFromName(const std::string& id) { |
| 281 return base::StringPrintf("ipp://localhost/printers/%s", id.c_str()); |
| 282 } |
| 283 |
| 284 } // namespace |
| 285 |
| 286 void ParseJobsResponse(ipp_t* response, |
| 287 const std::string& printer_id, |
| 288 std::vector<CupsJob>* jobs) { |
| 289 // Advance the position in the response to the jobs section. |
| 290 ipp_attribute_t* attr = ippFirstAttribute(response); |
| 291 while (attr != nullptr && ippGetGroupTag(attr) != IPP_TAG_JOB) { |
| 292 attr = ippNextAttribute(response); |
| 293 } |
| 294 |
| 295 if (attr != nullptr) { |
| 296 ParseJobs(response, printer_id, attr, jobs); |
| 297 } |
| 298 } |
| 299 |
| 300 void ParsePrinterStatus(ipp_t* response, PrinterStatus* printer_status) { |
| 301 for (ipp_attribute_t* attr = ippFirstAttribute(response); attr != nullptr; |
| 302 attr = ippNextAttribute(response)) { |
| 303 base::StringPiece name = ippGetName(attr); |
| 304 if (name.empty()) { |
| 305 continue; |
| 306 } |
| 307 |
| 308 if (name == kPrinterState) { |
| 309 DCHECK_EQ(IPP_TAG_ENUM, ippGetValueTag(attr)); |
| 310 printer_status->state = static_cast<ipp_pstate_t>(ippGetInteger(attr, 0)); |
| 311 } else if (name == kPrinterStateReasons) { |
| 312 std::vector<std::string> reason_strings; |
| 313 ParseCollection(attr, &reason_strings); |
| 314 for (const std::string& reason : reason_strings) { |
| 315 printer_status->reasons.push_back(ToPrinterReason(reason)); |
| 316 } |
| 317 } else if (name == kPrinterStateMessage) { |
| 318 printer_status->message = ippGetString(attr, 0, nullptr); |
| 319 } |
| 320 } |
| 321 } |
| 322 |
| 323 bool GetPrinterStatus(http_t* http, |
| 324 const std::string& printer_id, |
| 325 PrinterStatus* printer_status) { |
| 326 DCHECK(http); |
| 327 |
| 328 auto request = WrapIpp(ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES)); |
| 329 |
| 330 const std::string printer_uri = PrinterUriFromName(printer_id); |
| 331 ippAddString(request.get(), IPP_TAG_OPERATION, IPP_TAG_URI, kPrinterUri, |
| 332 nullptr, printer_uri.data()); |
| 333 |
| 334 ippAddStrings(request.get(), IPP_TAG_OPERATION, IPP_TAG_KEYWORD, |
| 335 kRequestedAttributes, kPrinterAttributes.size(), nullptr, |
| 336 kPrinterAttributes.data()); |
| 337 |
| 338 auto response = |
| 339 WrapIpp(cupsDoRequest(http, request.release(), printer_uri.c_str())); |
| 340 |
| 341 if (ippGetStatusCode(response.get()) != IPP_STATUS_OK) |
| 342 return false; |
| 343 |
| 344 ParsePrinterStatus(response.get(), printer_status); |
| 345 |
| 346 return true; |
| 347 } |
| 348 |
| 349 bool GetCupsJobs(http_t* http, |
| 350 const std::string& printer_id, |
| 351 int limit, |
| 352 JobCompletionState which, |
| 353 std::vector<CupsJob>* jobs) { |
| 354 DCHECK(http); |
| 355 |
| 356 auto request = WrapIpp(ippNewRequest(IPP_OP_GET_JOBS)); |
| 357 const std::string printer_uri = PrinterUriFromName(printer_id); |
| 358 ippAddString(request.get(), IPP_TAG_OPERATION, IPP_TAG_URI, kPrinterUri, |
| 359 nullptr, printer_uri.data()); |
| 360 ippAddInteger(request.get(), IPP_TAG_OPERATION, IPP_TAG_INTEGER, kLimit, |
| 361 limit); |
| 362 |
| 363 std::vector<const char*> job_attributes = { |
| 364 kJobUri, kJobId, kJobState, |
| 365 kJobStateReasons, kJobStateMessage, kJobImpressionsCompleted, |
| 366 kTimeAtProcessing}; |
| 367 |
| 368 ippAddStrings(request.get(), IPP_TAG_OPERATION, IPP_TAG_KEYWORD, |
| 369 kRequestedAttributes, job_attributes.size(), nullptr, |
| 370 job_attributes.data()); |
| 371 |
| 372 ippAddString(request.get(), IPP_TAG_OPERATION, IPP_TAG_KEYWORD, kWhichJobs, |
| 373 nullptr, which == COMPLETED ? kCompleted : kNotCompleted); |
| 374 |
| 375 if (ippValidateAttributes(request.get()) != 1) { |
| 376 LOG(WARNING) << "Could not validate ipp request: " << cupsLastErrorString(); |
| 377 return false; |
| 378 } |
| 379 |
| 380 // cupsDoRequest will delete the request. |
| 381 auto response = |
| 382 WrapIpp(cupsDoRequest(http, request.release(), printer_uri.c_str())); |
| 383 |
| 384 ipp_status_t status = ippGetStatusCode(response.get()); |
| 385 |
| 386 if (status != IPP_OK) { |
| 387 LOG(WARNING) << "IPP Error: " << cupsLastErrorString(); |
| 388 return false; |
| 389 } |
| 390 |
| 391 ParseJobsResponse(response.get(), printer_id, jobs); |
| 392 |
| 393 return true; |
| 394 } |
| 395 |
| 396 } // namespace printing |
OLD | NEW |