2019-03-10 17:43:47 +00:00
package ipp
import (
"bytes"
"crypto/tls"
2019-03-15 15:20:02 +00:00
"errors"
2019-03-10 17:43:47 +00:00
"fmt"
"io"
2019-03-15 15:20:02 +00:00
"net"
2019-03-10 17:43:47 +00:00
"net/http"
"os"
"path"
"strconv"
)
2020-03-27 23:27:56 +00:00
// Document wraps an io.Reader with more information, needed for encoding
2019-03-15 15:20:02 +00:00
type Document struct {
Document io . Reader
Size int
Name string
MimeType string
}
2020-03-27 23:27:56 +00:00
// IPPClient implements a generic ipp client
2019-03-10 17:43:47 +00:00
type IPPClient struct {
2019-03-13 21:54:08 +00:00
host string
port int
2019-03-10 17:43:47 +00:00
username string
password string
2019-03-13 21:54:08 +00:00
useTLS bool
2019-03-10 17:43:47 +00:00
client * http . Client
}
2020-03-27 23:27:56 +00:00
// NewIPPClient creates a new generic ipp client
2019-03-10 17:43:47 +00:00
func NewIPPClient ( host string , port int , username , password string , useTLS bool ) * IPPClient {
httpClient := http . Client {
Transport : & http . Transport {
TLSClientConfig : & tls . Config {
InsecureSkipVerify : true ,
} ,
} ,
}
return & IPPClient { host , port , username , password , useTLS , & httpClient }
}
2019-03-15 15:20:02 +00:00
func ( c * IPPClient ) getHttpUri ( namespace string , object interface { } ) string {
2019-03-10 17:43:47 +00:00
proto := "http"
if c . useTLS {
proto = "https"
}
2019-03-15 15:20:02 +00:00
uri := fmt . Sprintf ( "%s://%s:%d" , proto , c . host , c . port )
if namespace != "" {
uri = fmt . Sprintf ( "%s/%s" , uri , namespace )
}
if object != nil {
uri = fmt . Sprintf ( "%s/%v" , uri , object )
}
return uri
}
func ( c * IPPClient ) getPrinterUri ( printer string ) string {
return fmt . Sprintf ( "ipp://localhost/printers/%s" , printer )
}
func ( c * IPPClient ) getJobUri ( jobID int ) string {
return fmt . Sprintf ( "ipp://localhost/jobs/%d" , jobID )
2019-03-10 17:43:47 +00:00
}
2019-03-15 15:20:02 +00:00
func ( c * IPPClient ) getClassUri ( printer string ) string {
return fmt . Sprintf ( "ipp://localhost/classes/%s" , printer )
}
2020-03-27 23:27:56 +00:00
// SendRequest sends a request to a remote uri end returns the response
2019-12-26 15:46:08 +00:00
func ( c * IPPClient ) SendRequest ( url string , req * Request , additionalResponseData io . Writer ) ( * Response , error ) {
2019-03-10 17:43:47 +00:00
payload , err := req . Encode ( )
if err != nil {
return nil , err
}
var body io . Reader
size := len ( payload )
2019-03-13 21:54:08 +00:00
if req . File != nil && req . FileSize != - 1 {
2020-03-27 15:38:12 +00:00
size += req . FileSize
2019-03-10 17:43:47 +00:00
2019-03-13 21:54:08 +00:00
body = io . MultiReader ( bytes . NewBuffer ( payload ) , req . File )
2019-03-10 17:43:47 +00:00
} else {
body = bytes . NewBuffer ( payload )
}
httpReq , err := http . NewRequest ( "POST" , url , body )
if err != nil {
return nil , err
}
httpReq . Header . Set ( "Content-Length" , strconv . Itoa ( size ) )
2020-03-06 09:25:31 +00:00
httpReq . Header . Set ( "Content-Type" , ContentTypeIPP )
2019-03-10 17:43:47 +00:00
if c . username != "" && c . password != "" {
httpReq . SetBasicAuth ( c . username , c . password )
}
httpResp , err := c . client . Do ( httpReq )
if err != nil {
return nil , err
}
defer httpResp . Body . Close ( )
if httpResp . StatusCode != 200 {
2020-03-27 15:38:12 +00:00
return nil , HTTPError {
Code : httpResp . StatusCode ,
}
2019-03-10 17:43:47 +00:00
}
2020-03-27 15:38:12 +00:00
resp , err := NewResponseDecoder ( httpResp . Body ) . Decode ( additionalResponseData )
if err != nil {
return nil , err
}
2020-03-27 22:32:12 +00:00
err = resp . CheckForErrors ( )
return resp , err
2019-03-10 17:43:47 +00:00
}
2020-03-27 23:27:56 +00:00
// PrintDocuments prints one or more documents using a Create-Job operation followed by one or more Send-Document operation(s). custom job settings can be specified via the jobAttributes parameter
2019-06-08 22:03:50 +00:00
func ( c * IPPClient ) PrintDocuments ( docs [ ] Document , printer string , jobAttributes map [ string ] interface { } ) ( int , error ) {
2019-03-15 15:20:02 +00:00
printerURI := c . getPrinterUri ( printer )
2019-03-10 17:43:47 +00:00
req := NewRequest ( OperationCreateJob , 1 )
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributePrinterURI ] = printerURI
req . OperationAttributes [ AttributeRequestingUserName ] = c . username
2019-06-08 22:03:50 +00:00
// set defaults for some attributes, may get overwritten
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributeJobName ] = docs [ 0 ] . Name
req . OperationAttributes [ AttributeCopies ] = 1
req . OperationAttributes [ AttributeJobPriority ] = DefaultJobPriority
2019-06-08 22:03:50 +00:00
for key , value := range jobAttributes {
req . JobAttributes [ key ] = value
}
2019-03-10 17:43:47 +00:00
2019-12-26 15:46:08 +00:00
resp , err := c . SendRequest ( c . getHttpUri ( "printers" , printer ) , req , nil )
2019-03-10 17:43:47 +00:00
if err != nil {
return - 1 , err
}
2020-03-05 15:31:05 +00:00
if len ( resp . JobAttributes ) == 0 {
2019-03-15 15:20:02 +00:00
return 0 , errors . New ( "server doesn't returned a job id" )
}
2020-03-09 09:36:29 +00:00
jobID := resp . JobAttributes [ 0 ] [ AttributeJobID ] [ 0 ] . Value . ( int )
2019-03-10 17:43:47 +00:00
2019-03-15 15:20:02 +00:00
documentCount := len ( docs ) - 1
for docID , doc := range docs {
req = NewRequest ( OperationSendDocument , 2 )
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributePrinterURI ] = printerURI
req . OperationAttributes [ AttributeRequestingUserName ] = c . username
req . OperationAttributes [ AttributeJobID ] = jobID
req . OperationAttributes [ AttributeDocumentName ] = doc . Name
req . OperationAttributes [ AttributeDocumentFormat ] = doc . MimeType
req . OperationAttributes [ AttributeLastDocument ] = docID == documentCount
2019-03-15 15:20:02 +00:00
req . File = doc . Document
req . FileSize = doc . Size
2019-12-26 15:46:08 +00:00
_ , err = c . SendRequest ( c . getHttpUri ( "printers" , printer ) , req , nil )
2019-03-15 15:20:02 +00:00
if err != nil {
return - 1 , err
}
2019-03-10 17:43:47 +00:00
}
return jobID , nil
2019-03-13 21:54:08 +00:00
}
2020-03-27 23:27:56 +00:00
// PrintJob prints a document using a Print-Job operation. custom job settings can be specified via the jobAttributes parameter
2019-05-30 00:51:10 +00:00
func ( c * IPPClient ) PrintJob ( doc Document , printer string , jobAttributes map [ string ] interface { } ) ( int , error ) {
printerURI := c . getPrinterUri ( printer )
req := NewRequest ( OperationPrintJob , 1 )
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributePrinterURI ] = printerURI
req . OperationAttributes [ AttributeRequestingUserName ] = c . username
req . OperationAttributes [ AttributeJobName ] = doc . Name
req . OperationAttributes [ AttributeDocumentFormat ] = doc . MimeType
2019-06-08 22:03:50 +00:00
// set defaults for some attributes, may get overwritten
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributeCopies ] = 1
req . OperationAttributes [ AttributeJobPriority ] = DefaultJobPriority
2019-05-30 00:51:10 +00:00
for key , value := range jobAttributes {
req . JobAttributes [ key ] = value
}
req . File = doc . Document
req . FileSize = doc . Size
2019-12-26 15:46:08 +00:00
resp , err := c . SendRequest ( c . getHttpUri ( "printers" , printer ) , req , nil )
2019-05-30 00:51:10 +00:00
if err != nil {
return - 1 , err
}
2020-03-05 15:31:05 +00:00
if len ( resp . JobAttributes ) == 0 {
2019-05-30 00:51:10 +00:00
return 0 , errors . New ( "server doesn't returned a job id" )
}
2020-03-09 09:36:29 +00:00
jobID := resp . JobAttributes [ 0 ] [ AttributeJobID ] [ 0 ] . Value . ( int )
2019-05-30 00:51:10 +00:00
return jobID , nil
}
2020-03-27 23:27:56 +00:00
// PrintFile prints a local file on the file system. custom job settings can be specified via the jobAttributes parameter
2019-06-08 22:03:50 +00:00
func ( c * IPPClient ) PrintFile ( filePath , printer string , jobAttributes map [ string ] interface { } ) ( int , error ) {
2019-03-13 21:54:08 +00:00
fileStats , err := os . Stat ( filePath )
if os . IsNotExist ( err ) {
return - 1 , err
}
2019-03-15 15:20:02 +00:00
fileName := path . Base ( filePath )
2019-03-13 21:54:08 +00:00
document , err := os . Open ( filePath )
if err != nil {
return 0 , err
}
defer document . Close ( )
2020-03-09 09:36:29 +00:00
jobAttributes [ AttributeJobName ] = fileName
2019-06-08 22:03:50 +00:00
return c . PrintDocuments ( [ ] Document {
2019-03-15 15:20:02 +00:00
{
Document : document ,
Name : fileName ,
Size : int ( fileStats . Size ( ) ) ,
MimeType : MimeTypeOctetStream ,
} ,
2019-06-08 22:03:50 +00:00
} , printer , jobAttributes )
2019-03-15 15:20:02 +00:00
}
2020-03-27 23:27:56 +00:00
// GetPrinterAttributes returns the requested attributes for the specified printer, if attributes is nil the default attributes will be used
2019-03-15 15:20:02 +00:00
func ( c * IPPClient ) GetPrinterAttributes ( printer string , attributes [ ] string ) ( Attributes , error ) {
req := NewRequest ( OperationGetPrinterAttributes , 1 )
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributePrinterURI ] = c . getPrinterUri ( printer )
req . OperationAttributes [ AttributeRequestingUserName ] = c . username
2019-03-15 15:20:02 +00:00
if attributes == nil {
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributeRequestedAttributes ] = DefaultPrinterAttributes
2019-03-15 15:20:02 +00:00
} else {
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributeRequestedAttributes ] = attributes
2019-03-15 15:20:02 +00:00
}
2019-12-26 15:46:08 +00:00
resp , err := c . SendRequest ( c . getHttpUri ( "printers" , printer ) , req , nil )
2019-03-15 15:20:02 +00:00
if err != nil {
return nil , err
}
2020-03-05 15:31:05 +00:00
if len ( resp . PrinterAttributes ) == 0 {
2019-03-15 15:20:02 +00:00
return nil , errors . New ( "server doesn't return any printer attributes" )
}
2020-03-05 15:31:05 +00:00
return resp . PrinterAttributes [ 0 ] , nil
2019-03-15 15:20:02 +00:00
}
2020-03-27 23:27:56 +00:00
// ResumePrinter resumes a printer
2019-03-15 15:20:02 +00:00
func ( c * IPPClient ) ResumePrinter ( printer string ) error {
req := NewRequest ( OperationResumePrinter , 1 )
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributePrinterURI ] = c . getPrinterUri ( printer )
2019-03-15 15:20:02 +00:00
2019-12-26 15:46:08 +00:00
_ , err := c . SendRequest ( c . getHttpUri ( "admin" , "" ) , req , nil )
2019-03-15 15:20:02 +00:00
return err
}
2020-03-27 23:27:56 +00:00
// PausePrinter pauses a printer
2019-03-15 15:20:02 +00:00
func ( c * IPPClient ) PausePrinter ( printer string ) error {
req := NewRequest ( OperationPausePrinter , 1 )
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributePrinterURI ] = c . getPrinterUri ( printer )
2019-03-15 15:20:02 +00:00
2019-12-26 15:46:08 +00:00
_ , err := c . SendRequest ( c . getHttpUri ( "admin" , "" ) , req , nil )
2019-03-15 15:20:02 +00:00
return err
}
2020-03-27 23:27:56 +00:00
// GetJobAttributes returns the requested attributes for the specified job, if attributes is nil the default job will be used
2019-03-15 15:20:02 +00:00
func ( c * IPPClient ) GetJobAttributes ( jobID int , attributes [ ] string ) ( Attributes , error ) {
req := NewRequest ( OperationGetJobAttributes , 1 )
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributeJobURI ] = c . getJobUri ( jobID )
2019-03-15 15:20:02 +00:00
if attributes == nil {
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributeRequestedAttributes ] = DefaultJobAttributes
2019-03-15 15:20:02 +00:00
} else {
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributeRequestedAttributes ] = attributes
2019-03-15 15:20:02 +00:00
}
2019-12-26 15:46:08 +00:00
resp , err := c . SendRequest ( c . getHttpUri ( "jobs" , jobID ) , req , nil )
2019-03-15 15:20:02 +00:00
if err != nil {
return nil , err
}
2020-03-05 15:31:05 +00:00
if len ( resp . PrinterAttributes ) == 0 {
2019-03-15 15:20:02 +00:00
return nil , errors . New ( "server doesn't return any job attributes" )
}
2020-03-05 15:31:05 +00:00
return resp . PrinterAttributes [ 0 ] , nil
2019-03-15 15:20:02 +00:00
}
2020-03-27 23:27:56 +00:00
// GetJobs returns jobs from a printer or class
2020-03-09 11:47:23 +00:00
func ( c * IPPClient ) GetJobs ( printer , class string , whichJobs string , myJobs bool , firstJobId , limit int , attributes [ ] string ) ( map [ int ] Attributes , error ) {
2019-03-15 15:20:02 +00:00
req := NewRequest ( OperationGetJobs , 1 )
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributeWhichJobs ] = string ( whichJobs )
req . OperationAttributes [ AttributeMyJobs ] = myJobs
2019-03-15 15:20:02 +00:00
2019-12-26 15:46:08 +00:00
if printer != "" {
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributePrinterURI ] = c . getPrinterUri ( printer )
2019-12-26 15:46:08 +00:00
} else if class != "" {
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributePrinterURI ] = c . getClassUri ( printer )
2019-12-26 15:46:08 +00:00
} else {
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributePrinterURI ] = "ipp://localhost/"
2019-12-26 15:46:08 +00:00
}
2019-12-26 16:14:15 +00:00
if firstJobId > 0 {
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributeFirstJobID ] = firstJobId
2019-12-26 16:14:15 +00:00
}
if limit > 0 {
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributeLimit ] = limit
2019-12-26 16:14:15 +00:00
}
if myJobs {
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributeRequestingUserName ] = c . username
2019-12-26 16:14:15 +00:00
}
2019-03-15 15:20:02 +00:00
if attributes == nil {
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributeRequestedAttributes ] = DefaultJobAttributes
2019-03-15 15:20:02 +00:00
} else {
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributeRequestedAttributes ] = append ( attributes , AttributeJobID )
2019-03-15 15:20:02 +00:00
}
2019-12-26 15:46:08 +00:00
resp , err := c . SendRequest ( c . getHttpUri ( "" , nil ) , req , nil )
2019-03-15 15:20:02 +00:00
if err != nil {
return nil , err
}
jobIDMap := make ( map [ int ] Attributes )
2020-03-05 15:31:05 +00:00
for _ , jobAttributes := range resp . JobAttributes {
2020-03-09 09:36:29 +00:00
jobIDMap [ jobAttributes [ AttributeJobID ] [ 0 ] . Value . ( int ) ] = jobAttributes
2019-03-15 15:20:02 +00:00
}
return jobIDMap , nil
}
2020-03-27 23:27:56 +00:00
// CancelJob cancels a job. if purge is true, the job will also be removed
2019-03-15 15:20:02 +00:00
func ( c * IPPClient ) CancelJob ( jobID int , purge bool ) error {
req := NewRequest ( OperationCancelJob , 1 )
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributeJobURI ] = c . getJobUri ( jobID )
req . OperationAttributes [ AttributePurgeJobs ] = purge
2019-03-15 15:20:02 +00:00
2019-12-26 15:46:08 +00:00
_ , err := c . SendRequest ( c . getHttpUri ( "jobs" , "" ) , req , nil )
2019-03-15 15:20:02 +00:00
return err
}
2020-03-27 23:30:48 +00:00
// CancelAllJob cancels all jobs for a specified printer. if purge is true, the jobs will also be removed
2019-03-15 15:20:02 +00:00
func ( c * IPPClient ) CancelAllJob ( printer string , purge bool ) error {
2019-03-17 12:40:08 +00:00
req := NewRequest ( OperationCancelJobs , 1 )
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributePrinterURI ] = c . getPrinterUri ( printer )
req . OperationAttributes [ AttributePurgeJobs ] = purge
2019-03-15 15:20:02 +00:00
2019-12-26 15:46:08 +00:00
_ , err := c . SendRequest ( c . getHttpUri ( "admin" , "" ) , req , nil )
2019-03-15 15:20:02 +00:00
return err
}
2020-03-27 23:27:56 +00:00
// RestartJob restarts a job
2019-03-15 15:20:02 +00:00
func ( c * IPPClient ) RestartJob ( jobID int ) error {
req := NewRequest ( OperationRestartJob , 1 )
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributeJobURI ] = c . getJobUri ( jobID )
2019-03-15 15:20:02 +00:00
2019-12-26 15:46:08 +00:00
_ , err := c . SendRequest ( c . getHttpUri ( "jobs" , "" ) , req , nil )
2019-03-15 15:20:02 +00:00
return err
}
2020-03-27 23:30:48 +00:00
// HoldJobUntil holds a job
2019-03-15 15:20:02 +00:00
func ( c * IPPClient ) HoldJobUntil ( jobID int , holdUntil string ) error {
req := NewRequest ( OperationRestartJob , 1 )
2020-03-09 09:36:29 +00:00
req . OperationAttributes [ AttributeJobURI ] = c . getJobUri ( jobID )
req . JobAttributes [ AttributeHoldJobUntil ] = holdUntil
2019-03-15 15:20:02 +00:00
2019-12-26 15:46:08 +00:00
_ , err := c . SendRequest ( c . getHttpUri ( "jobs" , "" ) , req , nil )
2019-03-15 15:20:02 +00:00
return err
}
2020-03-27 23:27:56 +00:00
// TestConnection tests if a tcp connection to the remote server is possible
2019-03-15 15:20:02 +00:00
func ( c * IPPClient ) TestConnection ( ) error {
conn , err := net . Dial ( "tcp" , fmt . Sprintf ( "%s:%d" , c . host , c . port ) )
if err != nil {
return err
}
conn . Close ( )
return nil
2019-03-13 21:54:08 +00:00
}