initial commit
This commit is contained in:
5
pkg/ipp/CREDITS.MD
Normal file
5
pkg/ipp/CREDITS.MD
Normal file
@@ -0,0 +1,5 @@
|
||||
# Credits
|
||||
|
||||
* Original socket adapter code is mostly taken from [korylprince/printer-manager-cups](https://github.com/korylprince/printer-manager-cups)
|
||||
([MIT](https://github.com/korylprince/printer-manager-cups/blob/v1.0.9/LICENSE) licensed):
|
||||
[conn.go](https://github.com/korylprince/printer-manager-cups/blob/v1.0.9/cups/conn.go)
|
||||
201
pkg/ipp/LICENSE
Normal file
201
pkg/ipp/LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
132
pkg/ipp/adapter-http.go
Normal file
132
pkg/ipp/adapter-http.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package ipp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HttpAdapter struct {
|
||||
host string
|
||||
port int
|
||||
username string
|
||||
password string
|
||||
useTLS bool
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func NewHttpAdapter(host string, port int, username, password string, useTLS bool) *HttpAdapter {
|
||||
httpClient := http.Client{
|
||||
Timeout: 0,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
ResponseHeaderTimeout: 90 * time.Second,
|
||||
IdleConnTimeout: 120 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
return &HttpAdapter{
|
||||
host: host,
|
||||
port: port,
|
||||
username: username,
|
||||
password: password,
|
||||
useTLS: useTLS,
|
||||
client: &httpClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HttpAdapter) SendRequest(url string, req *Request, additionalResponseData io.Writer) (*Response, error) {
|
||||
payload, err := req.Encode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
size := len(payload)
|
||||
var body io.Reader
|
||||
if req.File != nil && req.FileSize != -1 {
|
||||
size += req.FileSize
|
||||
body = io.MultiReader(bytes.NewBuffer(payload), req.File)
|
||||
} 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))
|
||||
httpReq.Header.Set("Content-Type", ContentTypeIPP)
|
||||
|
||||
if h.username != "" && h.password != "" {
|
||||
httpReq.SetBasicAuth(h.username, h.password)
|
||||
}
|
||||
|
||||
httpResp, err := h.client.Do(httpReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer httpResp.Body.Close()
|
||||
|
||||
if httpResp.StatusCode != 200 {
|
||||
return nil, HTTPError{
|
||||
Code: httpResp.StatusCode,
|
||||
}
|
||||
}
|
||||
|
||||
// buffer response to avoid read issues
|
||||
buf := new(bytes.Buffer)
|
||||
if httpResp.ContentLength > 0 {
|
||||
buf.Grow(int(httpResp.ContentLength))
|
||||
}
|
||||
if _, err := io.Copy(buf, httpResp.Body); err != nil {
|
||||
return nil, fmt.Errorf("unable to buffer response: %w", err)
|
||||
}
|
||||
|
||||
ippResp, err := NewResponseDecoder(buf).Decode(additionalResponseData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = ippResp.CheckForErrors(); err != nil {
|
||||
return nil, fmt.Errorf("received error IPP response: %w", err)
|
||||
}
|
||||
|
||||
return ippResp, nil
|
||||
}
|
||||
|
||||
func (h *HttpAdapter) GetHttpUri(namespace string, object interface{}) string {
|
||||
proto := "http"
|
||||
if h.useTLS {
|
||||
proto = "https"
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s://%s:%d", proto, h.host, h.port)
|
||||
|
||||
if namespace != "" {
|
||||
uri = fmt.Sprintf("%s/%s", uri, namespace)
|
||||
}
|
||||
|
||||
if object != nil {
|
||||
uri = fmt.Sprintf("%s/%v", uri, object)
|
||||
}
|
||||
|
||||
return uri
|
||||
}
|
||||
|
||||
func (h *HttpAdapter) TestConnection() error {
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", h.host, h.port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
9
pkg/ipp/adapter.go
Normal file
9
pkg/ipp/adapter.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package ipp
|
||||
|
||||
import "io"
|
||||
|
||||
type Adapter interface {
|
||||
SendRequest(url string, req *Request, additionalResponseData io.Writer) (*Response, error)
|
||||
GetHttpUri(namespace string, object interface{}) string
|
||||
TestConnection() error
|
||||
}
|
||||
528
pkg/ipp/attribute.go
Normal file
528
pkg/ipp/attribute.go
Normal file
@@ -0,0 +1,528 @@
|
||||
package ipp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
sizeInteger = int16(4)
|
||||
sizeBoolean = int16(1)
|
||||
)
|
||||
|
||||
// AttributeEncoder encodes attribute to a io.Writer
|
||||
type AttributeEncoder struct {
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
// NewAttributeEncoder returns a new encoder that writes to w
|
||||
func NewAttributeEncoder(w io.Writer) *AttributeEncoder {
|
||||
return &AttributeEncoder{w}
|
||||
}
|
||||
|
||||
// Encode encodes a attribute and its value to a io.Writer
|
||||
// the tag is determined by the AttributeTagMapping map
|
||||
func (e *AttributeEncoder) Encode(attribute string, value interface{}) error {
|
||||
tag, ok := AttributeTagMapping[attribute]
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot get tag of attribute %s", attribute)
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
case int:
|
||||
if tag != TagInteger && tag != TagEnum {
|
||||
return fmt.Errorf("tag for attribute %s does not match with value type", attribute)
|
||||
}
|
||||
|
||||
if err := e.encodeTag(tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.encodeString(attribute); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.encodeInteger(int32(v)); err != nil {
|
||||
return err
|
||||
}
|
||||
case int16:
|
||||
if tag != TagInteger && tag != TagEnum {
|
||||
return fmt.Errorf("tag for attribute %s does not match with value type", attribute)
|
||||
}
|
||||
|
||||
if err := e.encodeTag(tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.encodeString(attribute); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.encodeInteger(int32(v)); err != nil {
|
||||
return err
|
||||
}
|
||||
case int8:
|
||||
if tag != TagInteger && tag != TagEnum {
|
||||
return fmt.Errorf("tag for attribute %s does not match with value type", attribute)
|
||||
}
|
||||
|
||||
if err := e.encodeTag(tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.encodeString(attribute); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.encodeInteger(int32(v)); err != nil {
|
||||
return err
|
||||
}
|
||||
case int32:
|
||||
if tag != TagInteger && tag != TagEnum {
|
||||
return fmt.Errorf("tag for attribute %s does not match with value type", attribute)
|
||||
}
|
||||
|
||||
if err := e.encodeTag(tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.encodeString(attribute); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.encodeInteger(v); err != nil {
|
||||
return err
|
||||
}
|
||||
case int64:
|
||||
if tag != TagInteger && tag != TagEnum {
|
||||
return fmt.Errorf("tag for attribute %s does not match with value type", attribute)
|
||||
}
|
||||
|
||||
if err := e.encodeTag(tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.encodeString(attribute); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.encodeInteger(int32(v)); err != nil {
|
||||
return err
|
||||
}
|
||||
case []int:
|
||||
if tag != TagInteger && tag != TagEnum {
|
||||
return fmt.Errorf("tag for attribute %s does not match with value type", attribute)
|
||||
}
|
||||
|
||||
for index, val := range v {
|
||||
if err := e.encodeTag(tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if index == 0 {
|
||||
if err := e.encodeString(attribute); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := e.writeNullByte(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.encodeInteger(int32(val)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case []int16:
|
||||
if tag != TagInteger && tag != TagEnum {
|
||||
return fmt.Errorf("tag for attribute %s does not match with value type", attribute)
|
||||
}
|
||||
|
||||
for index, val := range v {
|
||||
if err := e.encodeTag(tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if index == 0 {
|
||||
if err := e.encodeString(attribute); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := e.writeNullByte(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.encodeInteger(int32(val)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case []int8:
|
||||
if tag != TagInteger && tag != TagEnum {
|
||||
return fmt.Errorf("tag for attribute %s does not match with value type", attribute)
|
||||
}
|
||||
|
||||
for index, val := range v {
|
||||
if err := e.encodeTag(tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if index == 0 {
|
||||
if err := e.encodeString(attribute); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := e.writeNullByte(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.encodeInteger(int32(val)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case []int32:
|
||||
if tag != TagInteger && tag != TagEnum {
|
||||
return fmt.Errorf("tag for attribute %s does not match with value type", attribute)
|
||||
}
|
||||
|
||||
for index, val := range v {
|
||||
if err := e.encodeTag(tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if index == 0 {
|
||||
if err := e.encodeString(attribute); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := e.writeNullByte(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.encodeInteger(val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case []int64:
|
||||
if tag != TagInteger && tag != TagEnum {
|
||||
return fmt.Errorf("tag for attribute %s does not match with value type", attribute)
|
||||
}
|
||||
|
||||
for index, val := range v {
|
||||
if err := e.encodeTag(tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if index == 0 {
|
||||
if err := e.encodeString(attribute); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := e.writeNullByte(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.encodeInteger(int32(val)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case bool:
|
||||
if tag != TagBoolean {
|
||||
return fmt.Errorf("tag for attribute %s does not match with value type", attribute)
|
||||
}
|
||||
|
||||
if err := e.encodeTag(tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.encodeString(attribute); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.encodeBoolean(v); err != nil {
|
||||
return err
|
||||
}
|
||||
case []bool:
|
||||
if tag != TagBoolean {
|
||||
return fmt.Errorf("tag for attribute %s does not match with value type", attribute)
|
||||
}
|
||||
|
||||
for index, val := range v {
|
||||
if err := e.encodeTag(tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if index == 0 {
|
||||
if err := e.encodeString(attribute); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := e.writeNullByte(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.encodeBoolean(val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case string:
|
||||
if err := e.encodeTag(tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.encodeString(attribute); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.encodeString(v); err != nil {
|
||||
return err
|
||||
}
|
||||
case []string:
|
||||
for index, val := range v {
|
||||
if err := e.encodeTag(tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if index == 0 {
|
||||
if err := e.encodeString(attribute); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := e.writeNullByte(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := e.encodeString(val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("type %T is not supported", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *AttributeEncoder) encodeString(s string) error {
|
||||
if err := binary.Write(e.writer, binary.BigEndian, int16(len(s))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := e.writer.Write([]byte(s))
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *AttributeEncoder) encodeInteger(i int32) error {
|
||||
if err := binary.Write(e.writer, binary.BigEndian, sizeInteger); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return binary.Write(e.writer, binary.BigEndian, i)
|
||||
}
|
||||
|
||||
func (e *AttributeEncoder) encodeBoolean(b bool) error {
|
||||
if err := binary.Write(e.writer, binary.BigEndian, sizeBoolean); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return binary.Write(e.writer, binary.BigEndian, b)
|
||||
}
|
||||
|
||||
func (e *AttributeEncoder) encodeTag(t int8) error {
|
||||
return binary.Write(e.writer, binary.BigEndian, t)
|
||||
}
|
||||
|
||||
func (e *AttributeEncoder) writeNullByte() error {
|
||||
return binary.Write(e.writer, binary.BigEndian, int16(0))
|
||||
}
|
||||
|
||||
// Attribute defines an ipp attribute
|
||||
type Attribute struct {
|
||||
Tag int8
|
||||
Name string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// Resolution defines the resolution attribute
|
||||
type Resolution struct {
|
||||
Height int32
|
||||
Width int32
|
||||
Depth int8
|
||||
}
|
||||
|
||||
// AttributeDecoder reads and decodes ipp from an input stream
|
||||
type AttributeDecoder struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
// NewAttributeDecoder returns a new decoder that reads from r
|
||||
func NewAttributeDecoder(r io.Reader) *AttributeDecoder {
|
||||
return &AttributeDecoder{r}
|
||||
}
|
||||
|
||||
// Decode reads the next ipp attribute into a attribute struct. the type is identified by a tag passed as an argument
|
||||
func (d *AttributeDecoder) Decode(tag int8) (*Attribute, error) {
|
||||
attr := Attribute{Tag: tag}
|
||||
|
||||
name, err := d.decodeString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attr.Name = name
|
||||
|
||||
switch attr.Tag {
|
||||
case TagEnum, TagInteger:
|
||||
val, err := d.decodeInteger()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attr.Value = val
|
||||
case TagBoolean:
|
||||
val, err := d.decodeBool()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attr.Value = val
|
||||
case TagDate:
|
||||
val, err := d.decodeDate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attr.Value = val
|
||||
case TagRange:
|
||||
val, err := d.decodeRange()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attr.Value = val
|
||||
case TagResolution:
|
||||
val, err := d.decodeResolution()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attr.Value = val
|
||||
default:
|
||||
val, err := d.decodeString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attr.Value = val
|
||||
}
|
||||
|
||||
return &attr, nil
|
||||
}
|
||||
|
||||
func (d *AttributeDecoder) decodeBool() (b bool, err error) {
|
||||
if _, err = d.readValueLength(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = binary.Read(d.reader, binary.BigEndian, &b); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *AttributeDecoder) decodeInteger() (i int, err error) {
|
||||
if _, err = d.readValueLength(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var reti int32
|
||||
if err = binary.Read(d.reader, binary.BigEndian, &reti); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return int(reti), nil
|
||||
}
|
||||
|
||||
func (d *AttributeDecoder) decodeString() (string, error) {
|
||||
length, err := d.readValueLength()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if length == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
bs := make([]byte, length)
|
||||
if _, err := d.reader.Read(bs); err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return string(bs), nil
|
||||
}
|
||||
|
||||
func (d *AttributeDecoder) decodeDate() ([]int, error) {
|
||||
length, err := d.readValueLength()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
is := make([]int, length)
|
||||
var ti int8
|
||||
|
||||
for i := int16(0); i < length; i++ {
|
||||
if err = binary.Read(d.reader, binary.BigEndian, &ti); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
is[i] = int(ti)
|
||||
}
|
||||
|
||||
return is, nil
|
||||
}
|
||||
|
||||
func (d *AttributeDecoder) decodeRange() ([]int32, error) {
|
||||
length, err := d.readValueLength()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// initialize range element count (c) and range slice (r)
|
||||
c := length / 4
|
||||
r := make([]int32, c)
|
||||
|
||||
for i := int16(0); i < c; i++ {
|
||||
var ti int32
|
||||
if err = binary.Read(d.reader, binary.BigEndian, &ti); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r[i] = ti
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (d *AttributeDecoder) decodeResolution() (res Resolution, err error) {
|
||||
_, err = d.readValueLength()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = binary.Read(d.reader, binary.BigEndian, &res.Height); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = binary.Read(d.reader, binary.BigEndian, &res.Width); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = binary.Read(d.reader, binary.BigEndian, &res.Depth); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *AttributeDecoder) readValueLength() (length int16, err error) {
|
||||
err = binary.Read(d.reader, binary.BigEndian, &length)
|
||||
return
|
||||
}
|
||||
449
pkg/ipp/constants.go
Normal file
449
pkg/ipp/constants.go
Normal file
@@ -0,0 +1,449 @@
|
||||
package ipp
|
||||
|
||||
// ipp status codes
|
||||
const (
|
||||
StatusCupsInvalid int16 = -1
|
||||
StatusOk int16 = 0x0000
|
||||
StatusOkIgnoredOrSubstituted int16 = 0x0001
|
||||
StatusOkConflicting int16 = 0x0002
|
||||
StatusOkIgnoredSubscriptions int16 = 0x0003
|
||||
StatusOkIgnoredNotifications int16 = 0x0004
|
||||
StatusOkTooManyEvents int16 = 0x0005
|
||||
StatusOkButCancelSubscription int16 = 0x0006
|
||||
StatusOkEventsComplete int16 = 0x0007
|
||||
StatusRedirectionOtherSite int16 = 0x0200
|
||||
StatusCupsSeeOther int16 = 0x0280
|
||||
StatusErrorBadRequest int16 = 0x0400
|
||||
StatusErrorForbidden int16 = 0x0401
|
||||
StatusErrorNotAuthenticated int16 = 0x0402
|
||||
StatusErrorNotAuthorized int16 = 0x0403
|
||||
StatusErrorNotPossible int16 = 0x0404
|
||||
StatusErrorTimeout int16 = 0x0405
|
||||
StatusErrorNotFound int16 = 0x0406
|
||||
StatusErrorGone int16 = 0x0407
|
||||
StatusErrorRequestEntity int16 = 0x0408
|
||||
StatusErrorRequestValue int16 = 0x0409
|
||||
StatusErrorDocumentFormatNotSupported int16 = 0x040a
|
||||
StatusErrorAttributesOrValues int16 = 0x040b
|
||||
StatusErrorUriScheme int16 = 0x040c
|
||||
StatusErrorCharset int16 = 0x040d
|
||||
StatusErrorConflicting int16 = 0x040e
|
||||
StatusErrorCompressionError int16 = 0x040f
|
||||
StatusErrorDocumentFormatError int16 = 0x0410
|
||||
StatusErrorDocumentAccess int16 = 0x0411
|
||||
StatusErrorAttributesNotSettable int16 = 0x0412
|
||||
StatusErrorIgnoredAllSubscriptions int16 = 0x0413
|
||||
StatusErrorTooManySubscriptions int16 = 0x0414
|
||||
StatusErrorIgnoredAllNotifications int16 = 0x0415
|
||||
StatusErrorPrintSupportFileNotFound int16 = 0x0416
|
||||
StatusErrorDocumentPassword int16 = 0x0417
|
||||
StatusErrorDocumentPermission int16 = 0x0418
|
||||
StatusErrorDocumentSecurity int16 = 0x0419
|
||||
StatusErrorDocumentUnprintable int16 = 0x041a
|
||||
StatusErrorAccountInfoNeeded int16 = 0x041b
|
||||
StatusErrorAccountClosed int16 = 0x041c
|
||||
StatusErrorAccountLimitReached int16 = 0x041d
|
||||
StatusErrorAccountAuthorizationFailed int16 = 0x041e
|
||||
StatusErrorNotFetchable int16 = 0x041f
|
||||
StatusErrorCupsAccountInfoNeeded int16 = 0x049C
|
||||
StatusErrorCupsAccountClosed int16 = 0x049d
|
||||
StatusErrorCupsAccountLimitReached int16 = 0x049e
|
||||
StatusErrorCupsAccountAuthorizationFailed int16 = 0x049f
|
||||
StatusErrorInternal int16 = 0x0500
|
||||
StatusErrorOperationNotSupported int16 = 0x0501
|
||||
StatusErrorServiceUnavailable int16 = 0x0502
|
||||
StatusErrorVersionNotSupported int16 = 0x0503
|
||||
StatusErrorDevice int16 = 0x0504
|
||||
StatusErrorTemporary int16 = 0x0505
|
||||
StatusErrorNotAcceptingJobs int16 = 0x0506
|
||||
StatusErrorBusy int16 = 0x0507
|
||||
StatusErrorJobCanceled int16 = 0x0508
|
||||
StatusErrorMultipleJobsNotSupported int16 = 0x0509
|
||||
StatusErrorPrinterIsDeactivated int16 = 0x050a
|
||||
StatusErrorTooManyJobs int16 = 0x050b
|
||||
StatusErrorTooManyDocuments int16 = 0x050c
|
||||
StatusErrorCupsAuthenticationCanceled int16 = 0x1000
|
||||
StatusErrorCupsPki int16 = 0x1001
|
||||
StatusErrorCupsUpgradeRequired int16 = 0x1002
|
||||
)
|
||||
|
||||
// ipp operations
|
||||
const (
|
||||
OperationCupsInvalid int16 = -0x0001
|
||||
OperationCupsNone int16 = 0x0000
|
||||
OperationPrintJob int16 = 0x0002
|
||||
OperationPrintUri int16 = 0x0003
|
||||
OperationValidateJob int16 = 0x0004
|
||||
OperationCreateJob int16 = 0x0005
|
||||
OperationSendDocument int16 = 0x0006
|
||||
OperationSendUri int16 = 0x0007
|
||||
OperationCancelJob int16 = 0x0008
|
||||
OperationGetJobAttributes int16 = 0x0009
|
||||
OperationGetJobs int16 = 0x000a
|
||||
OperationGetPrinterAttributes int16 = 0x000b
|
||||
OperationHoldJob int16 = 0x000c
|
||||
OperationReleaseJob int16 = 0x000d
|
||||
OperationRestartJob int16 = 0x000e
|
||||
OperationPausePrinter int16 = 0x0010
|
||||
OperationResumePrinter int16 = 0x0011
|
||||
OperationPurgeJobs int16 = 0x0012
|
||||
OperationSetPrinterAttributes int16 = 0x0013
|
||||
OperationSetJobAttributes int16 = 0x0014
|
||||
OperationGetPrinterSupportedValues int16 = 0x0015
|
||||
OperationCreatePrinterSubscriptions int16 = 0x0016
|
||||
OperationCreateJobSubscriptions int16 = 0x0017
|
||||
OperationGetSubscriptionAttributes int16 = 0x0018
|
||||
OperationGetSubscriptions int16 = 0x0019
|
||||
OperationRenewSubscription int16 = 0x001a
|
||||
OperationCancelSubscription int16 = 0x001b
|
||||
OperationGetNotifications int16 = 0x001c
|
||||
OperationSendNotifications int16 = 0x001d
|
||||
OperationGetResourceAttributes int16 = 0x001e
|
||||
OperationGetResourceData int16 = 0x001f
|
||||
OperationGetResources int16 = 0x0020
|
||||
OperationGetPrintSupportFiles int16 = 0x0021
|
||||
OperationEnablePrinter int16 = 0x0022
|
||||
OperationDisablePrinter int16 = 0x0023
|
||||
OperationPausePrinterAfterCurrentJob int16 = 0x0024
|
||||
OperationHoldNewJobs int16 = 0x0025
|
||||
OperationReleaseHeldNewJobs int16 = 0x0026
|
||||
OperationDeactivatePrinter int16 = 0x0027
|
||||
OperationActivatePrinter int16 = 0x0028
|
||||
OperationRestartPrinter int16 = 0x0029
|
||||
OperationShutdownPrinter int16 = 0x002a
|
||||
OperationStartupPrinter int16 = 0x002b
|
||||
OperationReprocessJob int16 = 0x002c
|
||||
OperationCancelCurrentJob int16 = 0x002d
|
||||
OperationSuspendCurrentJob int16 = 0x002e
|
||||
OperationResumeJob int16 = 0x002f
|
||||
OperationOperationPromoteJob int16 = 0x0030
|
||||
OperationScheduleJobAfter int16 = 0x0031
|
||||
OperationCancelDocument int16 = 0x0033
|
||||
OperationGetDocumentAttributes int16 = 0x0034
|
||||
OperationGetDocuments int16 = 0x0035
|
||||
OperationDeleteDocument int16 = 0x0036
|
||||
OperationSetDocumentAttributes int16 = 0x0037
|
||||
OperationCancelJobs int16 = 0x0038
|
||||
OperationCancelMyJobs int16 = 0x0039
|
||||
OperationResubmitJob int16 = 0x003a
|
||||
OperationCloseJob int16 = 0x003b
|
||||
OperationIdentifyPrinter int16 = 0x003c
|
||||
OperationValidateDocument int16 = 0x003d
|
||||
OperationAddDocumentImages int16 = 0x003e
|
||||
OperationAcknowledgeDocument int16 = 0x003f
|
||||
OperationAcknowledgeIdentifyPrinter int16 = 0x0040
|
||||
OperationAcknowledgeJob int16 = 0x0041
|
||||
OperationFetchDocument int16 = 0x0042
|
||||
OperationFetchJob int16 = 0x0043
|
||||
OperationGetOutputDeviceAttributes int16 = 0x0044
|
||||
OperationUpdateActiveJobs int16 = 0x0045
|
||||
OperationDeregisterOutputDevice int16 = 0x0046
|
||||
OperationUpdateDocumentStatus int16 = 0x0047
|
||||
OperationUpdateJobStatus int16 = 0x0048
|
||||
OperationUpdateOutputDeviceAttributes int16 = 0x0049
|
||||
OperationGetNextDocumentData int16 = 0x004a
|
||||
OperationAllocatePrinterResources int16 = 0x004b
|
||||
OperationCreatePrinter int16 = 0x004c
|
||||
OperationDeallocatePrinterResources int16 = 0x004d
|
||||
OperationDeletePrinter int16 = 0x004e
|
||||
OperationGetPrinters int16 = 0x004f
|
||||
OperationShutdownOnePrinter int16 = 0x0050
|
||||
OperationStartupOnePrinter int16 = 0x0051
|
||||
OperationCancelResource int16 = 0x0052
|
||||
OperationCreateResource int16 = 0x0053
|
||||
OperationInstallResource int16 = 0x0054
|
||||
OperationSendResourceData int16 = 0x0055
|
||||
OperationSetResourceAttributes int16 = 0x0056
|
||||
OperationCreateResourceSubscriptions int16 = 0x0057
|
||||
OperationCreateSystemSubscriptions int16 = 0x0058
|
||||
OperationDisableAllPrinters int16 = 0x0059
|
||||
OperationEnableAllPrinters int16 = 0x005a
|
||||
OperationGetSystemAttributes int16 = 0x005b
|
||||
OperationGetSystemSupportedValues int16 = 0x005c
|
||||
OperationPauseAllPrinters int16 = 0x005d
|
||||
OperationPauseAllPrintersAfterCurrentJob int16 = 0x005e
|
||||
OperationRegisterOutputDevice int16 = 0x005f
|
||||
OperationRestartSystem int16 = 0x0060
|
||||
OperationResumeAllPrinters int16 = 0x0061
|
||||
OperationSetSystemAttributes int16 = 0x0062
|
||||
OperationShutdownAllPrinter int16 = 0x0063
|
||||
OperationStartupAllPrinters int16 = 0x0064
|
||||
OperationPrivate int16 = 0x4000
|
||||
OperationCupsGetDefault int16 = 0x4001
|
||||
OperationCupsGetPrinters int16 = 0x4002
|
||||
OperationCupsAddModifyPrinter int16 = 0x4003
|
||||
OperationCupsDeletePrinter int16 = 0x4004
|
||||
OperationCupsGetClasses int16 = 0x4005
|
||||
OperationCupsAddModifyClass int16 = 0x4006
|
||||
OperationCupsDeleteClass int16 = 0x4007
|
||||
OperationCupsAcceptJobs int16 = 0x4008
|
||||
OperationCupsRejectJobs int16 = 0x4009
|
||||
OperationCupsSetDefault int16 = 0x400a
|
||||
OperationCupsGetDevices int16 = 0x400b
|
||||
OperationCupsGetPPDs int16 = 0x400c
|
||||
OperationCupsMoveJob int16 = 0x400d
|
||||
OperationCupsAuthenticateJob int16 = 0x400e
|
||||
OperationCupsGetPpd int16 = 0x400f
|
||||
OperationCupsGetDocument int16 = 0x4027
|
||||
OperationCupsCreateLocalPrinter int16 = 0x4028
|
||||
)
|
||||
|
||||
// ipp tags
|
||||
const (
|
||||
TagCupsInvalid int8 = -1
|
||||
TagZero int8 = 0x00
|
||||
TagOperation int8 = 0x01
|
||||
TagJob int8 = 0x02
|
||||
TagEnd int8 = 0x03
|
||||
TagPrinter int8 = 0x04
|
||||
TagUnsupportedGroup int8 = 0x05
|
||||
TagSubscription int8 = 0x06
|
||||
TagEventNotification int8 = 0x07
|
||||
TagResource int8 = 0x08
|
||||
TagDocument int8 = 0x09
|
||||
TagSystem int8 = 0x0a
|
||||
TagUnsupportedValue int8 = 0x10
|
||||
TagDefault int8 = 0x11
|
||||
TagUnknown int8 = 0x12
|
||||
TagNoValue int8 = 0x13
|
||||
TagNotSettable int8 = 0x15
|
||||
TagDeleteAttr int8 = 0x16
|
||||
TagAdminDefine int8 = 0x17
|
||||
TagInteger int8 = 0x21
|
||||
TagBoolean int8 = 0x22
|
||||
TagEnum int8 = 0x23
|
||||
TagString int8 = 0x30
|
||||
TagDate int8 = 0x31
|
||||
TagResolution int8 = 0x32
|
||||
TagRange int8 = 0x33
|
||||
TagBeginCollection int8 = 0x34
|
||||
TagTextLang int8 = 0x35
|
||||
TagNameLang int8 = 0x36
|
||||
TagEndCollection int8 = 0x37
|
||||
TagText int8 = 0x41
|
||||
TagName int8 = 0x42
|
||||
TagReservedString int8 = 0x43
|
||||
TagKeyword int8 = 0x44
|
||||
TagUri int8 = 0x45
|
||||
TagUriScheme int8 = 0x46
|
||||
TagCharset int8 = 0x47
|
||||
TagLanguage int8 = 0x48
|
||||
TagMimeType int8 = 0x49
|
||||
TagMemberName int8 = 0x4a
|
||||
TagExtension int8 = 0x7f
|
||||
)
|
||||
|
||||
// job states
|
||||
const (
|
||||
JobStatePending int8 = 0x03
|
||||
JobStateHeld int8 = 0x04
|
||||
JobStateProcessing int8 = 0x05
|
||||
JobStateStopped int8 = 0x06
|
||||
JobStateCanceled int8 = 0x07
|
||||
JobStateAborted int8 = 0x08
|
||||
JobStateCompleted int8 = 0x09
|
||||
)
|
||||
|
||||
// document states
|
||||
const (
|
||||
DocumentStatePending int8 = 0x03
|
||||
DocumentStateProcessing int8 = 0x05
|
||||
DocumentStateCanceled int8 = 0x07
|
||||
DocumentStateAborted int8 = 0x08
|
||||
DocumentStateCompleted int8 = 0x08
|
||||
)
|
||||
|
||||
// printer states
|
||||
const (
|
||||
PrinterStateIdle int8 = 0x0003
|
||||
PrinterStateProcessing int8 = 0x0004
|
||||
PrinterStateStopped int8 = 0x0005
|
||||
)
|
||||
|
||||
// job state filter
|
||||
const (
|
||||
JobStateFilterNotCompleted = "not-completed"
|
||||
JobStateFilterCompleted = "completed"
|
||||
JobStateFilterAll = "all"
|
||||
)
|
||||
|
||||
// error policies
|
||||
const (
|
||||
ErrorPolicyRetryJob = "retry-job"
|
||||
ErrorPolicyAbortJob = "abort-job"
|
||||
ErrorPolicyRetryCurrentJob = "retry-current-job"
|
||||
ErrorPolicyStopPrinter = "stop-printer"
|
||||
)
|
||||
|
||||
// ipp defaults
|
||||
const (
|
||||
CharsetLanguage = "en-US"
|
||||
Charset = "utf-8"
|
||||
ProtocolVersionMajor = int8(2)
|
||||
ProtocolVersionMinor = int8(0)
|
||||
|
||||
DefaultJobPriority = 50
|
||||
)
|
||||
|
||||
// useful mime types for ipp
|
||||
const (
|
||||
MimeTypePostscript = "application/postscript"
|
||||
MimeTypeOctetStream = "application/octet-stream"
|
||||
)
|
||||
|
||||
// ipp content types
|
||||
const (
|
||||
ContentTypeIPP = "application/ipp"
|
||||
)
|
||||
|
||||
// known ipp attributes
|
||||
const (
|
||||
AttributeCopies = "copies"
|
||||
AttributeDocumentFormat = "document-format"
|
||||
AttributeDocumentName = "document-name"
|
||||
AttributeJobID = "job-id"
|
||||
AttributeJobName = "job-name"
|
||||
AttributeJobPriority = "job-priority"
|
||||
AttributeJobURI = "job-uri"
|
||||
AttributeLastDocument = "last-document"
|
||||
AttributeMyJobs = "my-jobs"
|
||||
AttributePPDName = "ppd-name"
|
||||
AttributePPDMakeAndModel = "ppd-make-and-model"
|
||||
AttributePrinterIsShared = "printer-is-shared"
|
||||
AttributePrinterIsTemporary = "printer-is-temporary"
|
||||
AttributePrinterURI = "printer-uri"
|
||||
AttributePurgeJobs = "purge-jobs"
|
||||
AttributeRequestedAttributes = "requested-attributes"
|
||||
AttributeRequestingUserName = "requesting-user-name"
|
||||
AttributeWhichJobs = "which-jobs"
|
||||
AttributeFirstJobID = "first-job-id"
|
||||
AttributeLimit = "limit"
|
||||
AttributeStatusMessage = "status-message"
|
||||
AttributeCharset = "attributes-charset"
|
||||
AttributeNaturalLanguage = "attributes-natural-language"
|
||||
AttributeDeviceURI = "device-uri"
|
||||
AttributeHoldJobUntil = "job-hold-until"
|
||||
AttributePrinterErrorPolicy = "printer-error-policy"
|
||||
AttributePrinterInfo = "printer-info"
|
||||
AttributePrinterLocation = "printer-location"
|
||||
AttributePrinterName = "printer-name"
|
||||
AttributePrinterStateReasons = "printer-state-reasons"
|
||||
AttributeJobPrinterURI = "job-printer-uri"
|
||||
AttributeMemberURIs = "member-uris"
|
||||
AttributeDocumentNumber = "document-number"
|
||||
AttributeDocumentState = "document-state"
|
||||
AttributeFinishings = "finishings"
|
||||
AttributeJobHoldUntil = "hold-job-until"
|
||||
AttributeJobSheets = "job-sheets"
|
||||
AttributeJobState = "job-state"
|
||||
AttributeJobStateReason = "job-state-reason"
|
||||
AttributeMedia = "media"
|
||||
AttributeSides = "sides"
|
||||
AttributeNumberUp = "number-up"
|
||||
AttributeOrientationRequested = "orientation-requested"
|
||||
AttributePrintQuality = "print-quality"
|
||||
AttributePrinterIsAcceptingJobs = "printer-is-accepting-jobs"
|
||||
AttributePrinterResolution = "printer-resolution"
|
||||
AttributePrinterState = "printer-state"
|
||||
AttributeMemberNames = "member-names"
|
||||
AttributePrinterType = "printer-type"
|
||||
AttributePrinterMakeAndModel = "printer-make-and-model"
|
||||
AttributePrinterStateMessage = "printer-state-message"
|
||||
AttributePrinterUriSupported = "printer-uri-supported"
|
||||
AttributeJobMediaProgress = "job-media-progress"
|
||||
AttributeJobKilobyteOctets = "job-k-octets"
|
||||
AttributeNumberOfDocuments = "number-of-documents"
|
||||
AttributeJobOriginatingUserName = "job-originating-user-name"
|
||||
AttributeOutputOrder = "outputorder"
|
||||
AttributeJobStateReasons = "job-state-reasons"
|
||||
AttributeJobStateMessage = "job-state-message"
|
||||
AttributeJobPrinterStateReasons = "job-printer-state-reasons"
|
||||
AttributeJobPrinterStateMessage = "job-printer-state-message"
|
||||
AttributeJobImpressionsCompleted = "job-impressions-completed"
|
||||
AttributePrintScaling = "print-scaling"
|
||||
)
|
||||
|
||||
// Default attributes
|
||||
var (
|
||||
DefaultClassAttributes = []string{AttributePrinterName, AttributeMemberNames}
|
||||
DefaultPrinterAttributes = []string{
|
||||
AttributePrinterName, AttributePrinterType, AttributePrinterLocation, AttributePrinterInfo,
|
||||
AttributePrinterMakeAndModel, AttributePrinterState, AttributePrinterStateMessage, AttributePrinterStateReasons,
|
||||
AttributePrinterUriSupported, AttributeDeviceURI, AttributePrinterIsShared,
|
||||
}
|
||||
DefaultJobAttributes = []string{
|
||||
AttributeJobID, AttributeJobName, AttributePrinterURI, AttributeJobState, AttributeJobStateReason,
|
||||
AttributeJobHoldUntil, AttributeJobMediaProgress, AttributeJobKilobyteOctets, AttributeNumberOfDocuments, AttributeCopies,
|
||||
AttributeJobOriginatingUserName,
|
||||
}
|
||||
)
|
||||
|
||||
// Attribute to tag mapping
|
||||
var (
|
||||
AttributeTagMapping = map[string]int8{
|
||||
AttributeCharset: TagCharset,
|
||||
AttributeNaturalLanguage: TagLanguage,
|
||||
AttributeCopies: TagInteger,
|
||||
AttributeDeviceURI: TagUri,
|
||||
AttributeDocumentFormat: TagMimeType,
|
||||
AttributeDocumentName: TagName,
|
||||
AttributeDocumentNumber: TagInteger,
|
||||
AttributeDocumentState: TagEnum,
|
||||
AttributeFinishings: TagEnum,
|
||||
AttributeJobHoldUntil: TagKeyword,
|
||||
AttributeHoldJobUntil: TagKeyword,
|
||||
AttributeJobID: TagInteger,
|
||||
AttributeJobName: TagName,
|
||||
AttributeJobPrinterURI: TagUri,
|
||||
AttributeJobPriority: TagInteger,
|
||||
AttributeJobSheets: TagName,
|
||||
AttributeJobState: TagEnum,
|
||||
AttributeJobStateReason: TagKeyword,
|
||||
AttributeJobURI: TagUri,
|
||||
AttributeLastDocument: TagBoolean,
|
||||
AttributeMedia: TagKeyword,
|
||||
AttributeSides: TagKeyword,
|
||||
AttributeMemberURIs: TagUri,
|
||||
AttributeMyJobs: TagBoolean,
|
||||
AttributeNumberUp: TagInteger,
|
||||
AttributeOrientationRequested: TagEnum,
|
||||
AttributePPDName: TagName,
|
||||
AttributePPDMakeAndModel: TagText,
|
||||
AttributeNumberOfDocuments: TagInteger,
|
||||
AttributePrintQuality: TagEnum,
|
||||
AttributePrinterErrorPolicy: TagName,
|
||||
AttributePrinterInfo: TagText,
|
||||
AttributePrinterIsAcceptingJobs: TagBoolean,
|
||||
AttributePrinterIsShared: TagBoolean,
|
||||
AttributePrinterIsTemporary: TagBoolean,
|
||||
AttributePrinterName: TagName,
|
||||
AttributePrinterLocation: TagText,
|
||||
AttributePrinterResolution: TagResolution,
|
||||
AttributePrinterState: TagEnum,
|
||||
AttributePrinterStateReasons: TagKeyword,
|
||||
AttributePrinterURI: TagUri,
|
||||
AttributePurgeJobs: TagBoolean,
|
||||
AttributeRequestedAttributes: TagKeyword,
|
||||
AttributeRequestingUserName: TagName,
|
||||
AttributeWhichJobs: TagKeyword,
|
||||
AttributeFirstJobID: TagInteger,
|
||||
AttributeStatusMessage: TagText,
|
||||
AttributeLimit: TagInteger,
|
||||
AttributeOutputOrder: TagName,
|
||||
AttributeJobStateReasons: TagString,
|
||||
AttributeJobStateMessage: TagString,
|
||||
AttributeJobPrinterStateReasons: TagString,
|
||||
AttributeJobPrinterStateMessage: TagString,
|
||||
AttributeJobImpressionsCompleted: TagInteger,
|
||||
AttributePrintScaling: TagKeyword,
|
||||
// IPP Subscription/Notification attributes (added for dankdots)
|
||||
"notify-events": TagKeyword,
|
||||
"notify-pull-method": TagKeyword,
|
||||
"notify-lease-duration": TagInteger,
|
||||
"notify-subscription-id": TagInteger,
|
||||
"notify-subscription-ids": TagInteger,
|
||||
"notify-sequence-numbers": TagInteger,
|
||||
"notify-wait": TagBoolean,
|
||||
"notify-recipient-uri": TagUri,
|
||||
}
|
||||
)
|
||||
322
pkg/ipp/cups-client.go
Normal file
322
pkg/ipp/cups-client.go
Normal file
@@ -0,0 +1,322 @@
|
||||
package ipp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CUPSClient implements a ipp client with specific cups operations
|
||||
type CUPSClient struct {
|
||||
*IPPClient
|
||||
}
|
||||
|
||||
// NewCUPSClient creates a new cups ipp client (used HttpAdapter internally)
|
||||
func NewCUPSClient(host string, port int, username, password string, useTLS bool) *CUPSClient {
|
||||
ippClient := NewIPPClient(host, port, username, password, useTLS)
|
||||
return &CUPSClient{ippClient}
|
||||
}
|
||||
|
||||
// NewCUPSClientWithAdapter creates a new cups ipp client with given Adapter
|
||||
func NewCUPSClientWithAdapter(username string, adapter Adapter) *CUPSClient {
|
||||
ippClient := NewIPPClientWithAdapter(username, adapter)
|
||||
return &CUPSClient{ippClient}
|
||||
}
|
||||
|
||||
// GetDevices returns a map of device uris and printer attributes
|
||||
func (c *CUPSClient) GetDevices() (map[string]Attributes, error) {
|
||||
req := NewRequest(OperationCupsGetDevices, 1)
|
||||
|
||||
resp, err := c.SendRequest(c.adapter.GetHttpUri("", nil), req, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
printerNameMap := make(map[string]Attributes)
|
||||
|
||||
for _, printerAttributes := range resp.PrinterAttributes {
|
||||
printerNameMap[printerAttributes[AttributeDeviceURI][0].Value.(string)] = printerAttributes
|
||||
}
|
||||
|
||||
return printerNameMap, nil
|
||||
}
|
||||
|
||||
// MoveJob moves a job to a other printer
|
||||
func (c *CUPSClient) MoveJob(jobID int, destPrinter string) error {
|
||||
req := NewRequest(OperationCupsMoveJob, 1)
|
||||
req.OperationAttributes[AttributeJobURI] = c.getJobUri(jobID)
|
||||
req.PrinterAttributes[AttributeJobPrinterURI] = c.getPrinterUri(destPrinter)
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("jobs", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// MoveAllJob moves all job from a printer to a other printer
|
||||
func (c *CUPSClient) MoveAllJob(srcPrinter, destPrinter string) error {
|
||||
req := NewRequest(OperationCupsMoveJob, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(srcPrinter)
|
||||
req.PrinterAttributes[AttributeJobPrinterURI] = c.getPrinterUri(destPrinter)
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("jobs", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetPPDs returns a map of ppd names and attributes
|
||||
func (c *CUPSClient) GetPPDs() (map[string]Attributes, error) {
|
||||
req := NewRequest(OperationCupsGetPPDs, 1)
|
||||
|
||||
resp, err := c.SendRequest(c.adapter.GetHttpUri("", nil), req, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ppdNameMap := make(map[string]Attributes)
|
||||
|
||||
for _, printerAttributes := range resp.PrinterAttributes {
|
||||
ppdNameMap[printerAttributes[AttributePPDName][0].Value.(string)] = printerAttributes
|
||||
}
|
||||
|
||||
return ppdNameMap, nil
|
||||
}
|
||||
|
||||
// AcceptJobs lets a printer accept jobs again
|
||||
func (c *CUPSClient) AcceptJobs(printer string) error {
|
||||
req := NewRequest(OperationCupsAcceptJobs, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer)
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// RejectJobs does not let a printer accept jobs
|
||||
func (c *CUPSClient) RejectJobs(printer string) error {
|
||||
req := NewRequest(OperationCupsRejectJobs, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer)
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// AddPrinterToClass adds a printer to a class, if the class does not exists it will be crated
|
||||
func (c *CUPSClient) AddPrinterToClass(class, printer string) error {
|
||||
attributes, err := c.GetPrinterAttributes(class, []string{AttributeMemberURIs})
|
||||
if err != nil && !IsNotExistsError(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
memberURIList := make([]string, 0)
|
||||
|
||||
if !IsNotExistsError(err) {
|
||||
for _, member := range attributes[AttributeMemberURIs] {
|
||||
memberString := strings.Split(member.Value.(string), "/")
|
||||
printerName := memberString[len(memberString)-1]
|
||||
|
||||
if printerName == printer {
|
||||
return nil
|
||||
}
|
||||
|
||||
memberURIList = append(memberURIList, member.Value.(string))
|
||||
}
|
||||
}
|
||||
|
||||
memberURIList = append(memberURIList, c.getPrinterUri(printer))
|
||||
|
||||
req := NewRequest(OperationCupsAddModifyClass, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getClassUri(class)
|
||||
req.PrinterAttributes[AttributeMemberURIs] = memberURIList
|
||||
|
||||
_, err = c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeletePrinterFromClass removes a printer from a class, if a class has no more printer it will be deleted
|
||||
func (c *CUPSClient) DeletePrinterFromClass(class, printer string) error {
|
||||
attributes, err := c.GetPrinterAttributes(class, []string{AttributeMemberURIs})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
memberURIList := make([]string, 0)
|
||||
|
||||
for _, member := range attributes[AttributeMemberURIs] {
|
||||
memberString := strings.Split(member.Value.(string), "/")
|
||||
printerName := memberString[len(memberString)-1]
|
||||
|
||||
if printerName != printer {
|
||||
memberURIList = append(memberURIList, member.Value.(string))
|
||||
}
|
||||
}
|
||||
|
||||
if len(memberURIList) == 0 {
|
||||
return c.DeleteClass(class)
|
||||
}
|
||||
|
||||
req := NewRequest(OperationCupsAddModifyClass, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getClassUri(class)
|
||||
req.PrinterAttributes[AttributeMemberURIs] = memberURIList
|
||||
|
||||
_, err = c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteClass deletes a class
|
||||
func (c *CUPSClient) DeleteClass(class string) error {
|
||||
req := NewRequest(OperationCupsDeleteClass, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getClassUri(class)
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// CreatePrinter creates a new printer
|
||||
func (c *CUPSClient) CreatePrinter(name, deviceURI, ppd string, shared bool, errorPolicy string, information, location string) error {
|
||||
req := NewRequest(OperationCupsAddModifyPrinter, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(name)
|
||||
req.OperationAttributes[AttributePPDName] = ppd
|
||||
req.OperationAttributes[AttributePrinterIsShared] = shared
|
||||
req.PrinterAttributes[AttributePrinterStateReasons] = "none"
|
||||
req.PrinterAttributes[AttributeDeviceURI] = deviceURI
|
||||
req.PrinterAttributes[AttributePrinterInfo] = information
|
||||
req.PrinterAttributes[AttributePrinterLocation] = location
|
||||
req.PrinterAttributes[AttributePrinterErrorPolicy] = errorPolicy
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetPrinterPPD sets the ppd for a printer
|
||||
func (c *CUPSClient) SetPrinterPPD(printer, ppd string) error {
|
||||
req := NewRequest(OperationCupsAddModifyPrinter, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer)
|
||||
req.OperationAttributes[AttributePPDName] = ppd
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetPrinterDeviceURI sets the device uri for a printer
|
||||
func (c *CUPSClient) SetPrinterDeviceURI(printer, deviceURI string) error {
|
||||
req := NewRequest(OperationCupsAddModifyPrinter, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer)
|
||||
req.PrinterAttributes[AttributeDeviceURI] = deviceURI
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetPrinterIsShared shares or unshares a printer in the network
|
||||
func (c *CUPSClient) SetPrinterIsShared(printer string, shared bool) error {
|
||||
req := NewRequest(OperationCupsAddModifyPrinter, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer)
|
||||
req.OperationAttributes[AttributePrinterIsShared] = shared
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetPrinterErrorPolicy sets the error policy for a printer
|
||||
func (c *CUPSClient) SetPrinterErrorPolicy(printer string, errorPolicy string) error {
|
||||
req := NewRequest(OperationCupsAddModifyPrinter, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer)
|
||||
req.PrinterAttributes[AttributePrinterErrorPolicy] = errorPolicy
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetPrinterInformation sets general printer information
|
||||
func (c *CUPSClient) SetPrinterInformation(printer, information string) error {
|
||||
req := NewRequest(OperationCupsAddModifyPrinter, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer)
|
||||
req.PrinterAttributes[AttributePrinterInfo] = information
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetPrinterLocation sets the printer location
|
||||
func (c *CUPSClient) SetPrinterLocation(printer, location string) error {
|
||||
req := NewRequest(OperationCupsAddModifyPrinter, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer)
|
||||
req.PrinterAttributes[AttributePrinterLocation] = location
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeletePrinter deletes a printer
|
||||
func (c *CUPSClient) DeletePrinter(printer string) error {
|
||||
req := NewRequest(OperationCupsDeletePrinter, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer)
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetPrinters returns a map of printer names and attributes
|
||||
func (c *CUPSClient) GetPrinters(attributes []string) (map[string]Attributes, error) {
|
||||
req := NewRequest(OperationCupsGetPrinters, 1)
|
||||
|
||||
if attributes == nil {
|
||||
req.OperationAttributes[AttributeRequestedAttributes] = DefaultPrinterAttributes
|
||||
} else {
|
||||
req.OperationAttributes[AttributeRequestedAttributes] = append(attributes, AttributePrinterName)
|
||||
}
|
||||
|
||||
resp, err := c.SendRequest(c.adapter.GetHttpUri("", nil), req, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
printerNameMap := make(map[string]Attributes)
|
||||
|
||||
for _, printerAttributes := range resp.PrinterAttributes {
|
||||
printerNameMap[printerAttributes[AttributePrinterName][0].Value.(string)] = printerAttributes
|
||||
}
|
||||
|
||||
return printerNameMap, nil
|
||||
}
|
||||
|
||||
// GetClasses returns a map of class names and attributes
|
||||
func (c *CUPSClient) GetClasses(attributes []string) (map[string]Attributes, error) {
|
||||
req := NewRequest(OperationCupsGetClasses, 1)
|
||||
|
||||
if attributes == nil {
|
||||
req.OperationAttributes[AttributeRequestedAttributes] = DefaultClassAttributes
|
||||
} else {
|
||||
req.OperationAttributes[AttributeRequestedAttributes] = append(attributes, AttributePrinterName)
|
||||
}
|
||||
|
||||
resp, err := c.SendRequest(c.adapter.GetHttpUri("", nil), req, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
printerNameMap := make(map[string]Attributes)
|
||||
|
||||
for _, printerAttributes := range resp.PrinterAttributes {
|
||||
printerNameMap[printerAttributes[AttributePrinterName][0].Value.(string)] = printerAttributes
|
||||
}
|
||||
|
||||
return printerNameMap, nil
|
||||
}
|
||||
|
||||
// PrintTestPage prints a test page of type application/vnd.cups-pdf-banner
|
||||
func (c *CUPSClient) PrintTestPage(printer string) (int, error) {
|
||||
testPage := new(bytes.Buffer)
|
||||
testPage.WriteString("#PDF-BANNER\n")
|
||||
testPage.WriteString("Template default-testpage.pdf\n")
|
||||
testPage.WriteString("Show printer-name printer-info printer-location printer-make-and-model printer-driver-name")
|
||||
testPage.WriteString("printer-driver-version paper-size imageable-area job-id options time-at-creation")
|
||||
testPage.WriteString("time-at-processing\n\n")
|
||||
|
||||
return c.PrintDocuments([]Document{
|
||||
{
|
||||
Document: testPage,
|
||||
Name: "Test Page",
|
||||
Size: testPage.Len(),
|
||||
MimeType: MimeTypePostscript,
|
||||
},
|
||||
}, printer, map[string]interface{}{
|
||||
AttributeJobName: "Test Page",
|
||||
})
|
||||
}
|
||||
31
pkg/ipp/error.go
Normal file
31
pkg/ipp/error.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package ipp
|
||||
|
||||
import "fmt"
|
||||
|
||||
// IsNotExistsError checks a given error whether a printer or class does not exist
|
||||
func IsNotExistsError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return err.Error() == "The printer or class does not exist."
|
||||
}
|
||||
|
||||
// IPPError used for non ok ipp status codes
|
||||
type IPPError struct {
|
||||
Status int16
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e IPPError) Error() string {
|
||||
return fmt.Sprintf("ipp status: %d, message: %s", e.Status, e.Message)
|
||||
}
|
||||
|
||||
// HTTPError used for non 200 http codes
|
||||
type HTTPError struct {
|
||||
Code int
|
||||
}
|
||||
|
||||
func (e HTTPError) Error() string {
|
||||
return fmt.Sprintf("got http code %d", e.Code)
|
||||
}
|
||||
329
pkg/ipp/ipp-client.go
Normal file
329
pkg/ipp/ipp-client.go
Normal file
@@ -0,0 +1,329 @@
|
||||
package ipp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
// Document wraps an io.Reader with more information, needed for encoding
|
||||
type Document struct {
|
||||
Document io.Reader
|
||||
Size int
|
||||
Name string
|
||||
MimeType string
|
||||
}
|
||||
|
||||
// IPPClient implements a generic ipp client
|
||||
type IPPClient struct {
|
||||
username string
|
||||
adapter Adapter
|
||||
}
|
||||
|
||||
// NewIPPClient creates a new generic ipp client (used HttpAdapter internally)
|
||||
func NewIPPClient(host string, port int, username, password string, useTLS bool) *IPPClient {
|
||||
adapter := NewHttpAdapter(host, port, username, password, useTLS)
|
||||
|
||||
return &IPPClient{
|
||||
username: username,
|
||||
adapter: adapter,
|
||||
}
|
||||
}
|
||||
|
||||
// NewIPPClientWithAdapter creates a new generic ipp client with given Adapter
|
||||
func NewIPPClientWithAdapter(username string, adapter Adapter) *IPPClient {
|
||||
return &IPPClient{
|
||||
username: username,
|
||||
adapter: adapter,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (c *IPPClient) getClassUri(printer string) string {
|
||||
return fmt.Sprintf("ipp://localhost/classes/%s", printer)
|
||||
}
|
||||
|
||||
// SendRequest sends a request to a remote uri end returns the response
|
||||
func (c *IPPClient) SendRequest(url string, req *Request, additionalResponseData io.Writer) (*Response, error) {
|
||||
if _, ok := req.OperationAttributes[AttributeRequestingUserName]; !ok {
|
||||
req.OperationAttributes[AttributeRequestingUserName] = c.username
|
||||
}
|
||||
|
||||
return c.adapter.SendRequest(url, req, additionalResponseData)
|
||||
}
|
||||
|
||||
// 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
|
||||
func (c *IPPClient) PrintDocuments(docs []Document, printer string, jobAttributes map[string]interface{}) (int, error) {
|
||||
printerURI := c.getPrinterUri(printer)
|
||||
|
||||
req := NewRequest(OperationCreateJob, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = printerURI
|
||||
req.OperationAttributes[AttributeRequestingUserName] = c.username
|
||||
|
||||
// set defaults for some attributes, may get overwritten
|
||||
req.OperationAttributes[AttributeJobName] = docs[0].Name
|
||||
req.OperationAttributes[AttributeCopies] = 1
|
||||
req.OperationAttributes[AttributeJobPriority] = DefaultJobPriority
|
||||
|
||||
for key, value := range jobAttributes {
|
||||
req.JobAttributes[key] = value
|
||||
}
|
||||
|
||||
resp, err := c.SendRequest(c.adapter.GetHttpUri("printers", printer), req, nil)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if len(resp.JobAttributes) == 0 {
|
||||
return 0, errors.New("server doesn't returned a job id")
|
||||
}
|
||||
|
||||
jobID := resp.JobAttributes[0][AttributeJobID][0].Value.(int)
|
||||
|
||||
documentCount := len(docs) - 1
|
||||
|
||||
for docID, doc := range docs {
|
||||
req = NewRequest(OperationSendDocument, 2)
|
||||
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
|
||||
req.File = doc.Document
|
||||
req.FileSize = doc.Size
|
||||
|
||||
_, err = c.SendRequest(c.adapter.GetHttpUri("printers", printer), req, nil)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
}
|
||||
|
||||
return jobID, nil
|
||||
}
|
||||
|
||||
// PrintJob prints a document using a Print-Job operation. custom job settings can be specified via the jobAttributes parameter
|
||||
func (c *IPPClient) PrintJob(doc Document, printer string, jobAttributes map[string]interface{}) (int, error) {
|
||||
printerURI := c.getPrinterUri(printer)
|
||||
|
||||
req := NewRequest(OperationPrintJob, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = printerURI
|
||||
req.OperationAttributes[AttributeRequestingUserName] = c.username
|
||||
req.OperationAttributes[AttributeJobName] = doc.Name
|
||||
req.OperationAttributes[AttributeDocumentFormat] = doc.MimeType
|
||||
|
||||
// set defaults for some attributes, may get overwritten
|
||||
req.OperationAttributes[AttributeCopies] = 1
|
||||
req.OperationAttributes[AttributeJobPriority] = DefaultJobPriority
|
||||
|
||||
for key, value := range jobAttributes {
|
||||
req.JobAttributes[key] = value
|
||||
}
|
||||
|
||||
req.File = doc.Document
|
||||
req.FileSize = doc.Size
|
||||
|
||||
resp, err := c.SendRequest(c.adapter.GetHttpUri("printers", printer), req, nil)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if len(resp.JobAttributes) == 0 {
|
||||
return 0, errors.New("server doesn't returned a job id")
|
||||
}
|
||||
|
||||
jobID := resp.JobAttributes[0][AttributeJobID][0].Value.(int)
|
||||
|
||||
return jobID, nil
|
||||
}
|
||||
|
||||
// PrintFile prints a local file on the file system. custom job settings can be specified via the jobAttributes parameter
|
||||
func (c *IPPClient) PrintFile(filePath, printer string, jobAttributes map[string]interface{}) (int, error) {
|
||||
fileStats, err := os.Stat(filePath)
|
||||
if os.IsNotExist(err) {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
fileName := path.Base(filePath)
|
||||
|
||||
document, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer document.Close()
|
||||
|
||||
jobAttributes[AttributeJobName] = fileName
|
||||
|
||||
return c.PrintDocuments([]Document{
|
||||
{
|
||||
Document: document,
|
||||
Name: fileName,
|
||||
Size: int(fileStats.Size()),
|
||||
MimeType: MimeTypeOctetStream,
|
||||
},
|
||||
}, printer, jobAttributes)
|
||||
}
|
||||
|
||||
// GetPrinterAttributes returns the requested attributes for the specified printer, if attributes is nil the default attributes will be used
|
||||
func (c *IPPClient) GetPrinterAttributes(printer string, attributes []string) (Attributes, error) {
|
||||
req := NewRequest(OperationGetPrinterAttributes, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer)
|
||||
req.OperationAttributes[AttributeRequestingUserName] = c.username
|
||||
|
||||
if attributes == nil {
|
||||
req.OperationAttributes[AttributeRequestedAttributes] = DefaultPrinterAttributes
|
||||
} else {
|
||||
req.OperationAttributes[AttributeRequestedAttributes] = attributes
|
||||
}
|
||||
|
||||
resp, err := c.SendRequest(c.adapter.GetHttpUri("printers", printer), req, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(resp.PrinterAttributes) == 0 {
|
||||
return nil, errors.New("server doesn't return any printer attributes")
|
||||
}
|
||||
|
||||
return resp.PrinterAttributes[0], nil
|
||||
}
|
||||
|
||||
// ResumePrinter resumes a printer
|
||||
func (c *IPPClient) ResumePrinter(printer string) error {
|
||||
req := NewRequest(OperationResumePrinter, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer)
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// PausePrinter pauses a printer
|
||||
func (c *IPPClient) PausePrinter(printer string) error {
|
||||
req := NewRequest(OperationPausePrinter, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer)
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetJobAttributes returns the requested attributes for the specified job, if attributes is nil the default job will be used
|
||||
func (c *IPPClient) GetJobAttributes(jobID int, attributes []string) (Attributes, error) {
|
||||
req := NewRequest(OperationGetJobAttributes, 1)
|
||||
req.OperationAttributes[AttributeJobURI] = c.getJobUri(jobID)
|
||||
|
||||
if attributes == nil {
|
||||
req.OperationAttributes[AttributeRequestedAttributes] = DefaultJobAttributes
|
||||
} else {
|
||||
req.OperationAttributes[AttributeRequestedAttributes] = attributes
|
||||
}
|
||||
|
||||
resp, err := c.SendRequest(c.adapter.GetHttpUri("jobs", jobID), req, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(resp.JobAttributes) == 0 {
|
||||
return nil, errors.New("server doesn't return any job attributes")
|
||||
}
|
||||
|
||||
return resp.JobAttributes[0], nil
|
||||
}
|
||||
|
||||
// GetJobs returns jobs from a printer or class
|
||||
func (c *IPPClient) GetJobs(printer, class string, whichJobs string, myJobs bool, firstJobId, limit int, attributes []string) (map[int]Attributes, error) {
|
||||
req := NewRequest(OperationGetJobs, 1)
|
||||
req.OperationAttributes[AttributeWhichJobs] = whichJobs
|
||||
req.OperationAttributes[AttributeMyJobs] = myJobs
|
||||
|
||||
if printer != "" {
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer)
|
||||
} else if class != "" {
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getClassUri(printer)
|
||||
} else {
|
||||
req.OperationAttributes[AttributePrinterURI] = "ipp://localhost/"
|
||||
}
|
||||
|
||||
if firstJobId > 0 {
|
||||
req.OperationAttributes[AttributeFirstJobID] = firstJobId
|
||||
}
|
||||
|
||||
if limit > 0 {
|
||||
req.OperationAttributes[AttributeLimit] = limit
|
||||
}
|
||||
|
||||
if myJobs {
|
||||
req.OperationAttributes[AttributeRequestingUserName] = c.username
|
||||
}
|
||||
|
||||
if attributes == nil {
|
||||
req.OperationAttributes[AttributeRequestedAttributes] = DefaultJobAttributes
|
||||
} else {
|
||||
req.OperationAttributes[AttributeRequestedAttributes] = append(attributes, AttributeJobID)
|
||||
}
|
||||
|
||||
resp, err := c.SendRequest(c.adapter.GetHttpUri("", nil), req, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
jobIDMap := make(map[int]Attributes)
|
||||
|
||||
for _, jobAttributes := range resp.JobAttributes {
|
||||
jobIDMap[jobAttributes[AttributeJobID][0].Value.(int)] = jobAttributes
|
||||
}
|
||||
|
||||
return jobIDMap, nil
|
||||
}
|
||||
|
||||
// CancelJob cancels a job. if purge is true, the job will also be removed
|
||||
func (c *IPPClient) CancelJob(jobID int, purge bool) error {
|
||||
req := NewRequest(OperationCancelJob, 1)
|
||||
req.OperationAttributes[AttributeJobURI] = c.getJobUri(jobID)
|
||||
req.OperationAttributes[AttributePurgeJobs] = purge
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("jobs", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// CancelAllJob cancels all jobs for a specified printer. if purge is true, the jobs will also be removed
|
||||
func (c *IPPClient) CancelAllJob(printer string, purge bool) error {
|
||||
req := NewRequest(OperationCancelJobs, 1)
|
||||
req.OperationAttributes[AttributePrinterURI] = c.getPrinterUri(printer)
|
||||
req.OperationAttributes[AttributePurgeJobs] = purge
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("admin", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// RestartJob restarts a job
|
||||
func (c *IPPClient) RestartJob(jobID int) error {
|
||||
req := NewRequest(OperationRestartJob, 1)
|
||||
req.OperationAttributes[AttributeJobURI] = c.getJobUri(jobID)
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("jobs", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// HoldJobUntil holds a job
|
||||
func (c *IPPClient) HoldJobUntil(jobID int, holdUntil string) error {
|
||||
req := NewRequest(OperationRestartJob, 1)
|
||||
req.OperationAttributes[AttributeJobURI] = c.getJobUri(jobID)
|
||||
req.JobAttributes[AttributeHoldJobUntil] = holdUntil
|
||||
|
||||
_, err := c.SendRequest(c.adapter.GetHttpUri("jobs", ""), req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// TestConnection tests if a tcp connection to the remote server is possible
|
||||
func (c *IPPClient) TestConnection() error {
|
||||
return c.adapter.TestConnection()
|
||||
}
|
||||
299
pkg/ipp/request.go
Normal file
299
pkg/ipp/request.go
Normal file
@@ -0,0 +1,299 @@
|
||||
package ipp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Request defines a ipp request
|
||||
type Request struct {
|
||||
ProtocolVersionMajor int8
|
||||
ProtocolVersionMinor int8
|
||||
|
||||
Operation int16
|
||||
RequestId int32
|
||||
|
||||
OperationAttributes map[string]interface{}
|
||||
JobAttributes map[string]interface{}
|
||||
PrinterAttributes map[string]interface{}
|
||||
SubscriptionAttributes map[string]interface{} // Added for subscription operations
|
||||
|
||||
File io.Reader
|
||||
FileSize int
|
||||
}
|
||||
|
||||
// NewRequest creates a new ipp request
|
||||
func NewRequest(op int16, reqID int32) *Request {
|
||||
return &Request{
|
||||
ProtocolVersionMajor: ProtocolVersionMajor,
|
||||
ProtocolVersionMinor: ProtocolVersionMinor,
|
||||
Operation: op,
|
||||
RequestId: reqID,
|
||||
OperationAttributes: make(map[string]interface{}),
|
||||
JobAttributes: make(map[string]interface{}),
|
||||
PrinterAttributes: make(map[string]interface{}),
|
||||
SubscriptionAttributes: make(map[string]interface{}),
|
||||
File: nil,
|
||||
FileSize: -1,
|
||||
}
|
||||
}
|
||||
|
||||
// Encode encodes the request to a byte slice
|
||||
func (r *Request) Encode() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
enc := NewAttributeEncoder(buf)
|
||||
|
||||
if err := binary.Write(buf, binary.BigEndian, r.ProtocolVersionMajor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Write(buf, binary.BigEndian, r.ProtocolVersionMinor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Write(buf, binary.BigEndian, r.Operation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Write(buf, binary.BigEndian, r.RequestId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Write(buf, binary.BigEndian, TagOperation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.OperationAttributes == nil {
|
||||
r.OperationAttributes = make(map[string]interface{}, 2)
|
||||
}
|
||||
|
||||
if _, found := r.OperationAttributes[AttributeCharset]; !found {
|
||||
r.OperationAttributes[AttributeCharset] = Charset
|
||||
}
|
||||
|
||||
if _, found := r.OperationAttributes[AttributeNaturalLanguage]; !found {
|
||||
r.OperationAttributes[AttributeNaturalLanguage] = CharsetLanguage
|
||||
}
|
||||
|
||||
if err := r.encodeOperationAttributes(enc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(r.JobAttributes) > 0 {
|
||||
if err := binary.Write(buf, binary.BigEndian, TagJob); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for attr, value := range r.JobAttributes {
|
||||
if err := enc.Encode(attr, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.PrinterAttributes) > 0 {
|
||||
if err := binary.Write(buf, binary.BigEndian, TagPrinter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for attr, value := range r.PrinterAttributes {
|
||||
if err := enc.Encode(attr, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.SubscriptionAttributes) > 0 {
|
||||
if err := binary.Write(buf, binary.BigEndian, TagSubscription); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.encodeSubscriptionAttributes(enc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := binary.Write(buf, binary.BigEndian, TagEnd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (r *Request) encodeOperationAttributes(enc *AttributeEncoder) error {
|
||||
ordered := []string{
|
||||
AttributeCharset,
|
||||
AttributeNaturalLanguage,
|
||||
AttributePrinterURI,
|
||||
AttributeJobID,
|
||||
}
|
||||
|
||||
for _, attr := range ordered {
|
||||
if value, ok := r.OperationAttributes[attr]; ok {
|
||||
delete(r.OperationAttributes, attr)
|
||||
if err := enc.Encode(attr, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for attr, value := range r.OperationAttributes {
|
||||
if err := enc.Encode(attr, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Request) encodeSubscriptionAttributes(enc *AttributeEncoder) error {
|
||||
// Encode subscription attributes in proper order
|
||||
// notify-pull-method and notify-lease-duration must come before notify-events
|
||||
ordered := []string{
|
||||
"notify-pull-method",
|
||||
"notify-lease-duration",
|
||||
"notify-events",
|
||||
}
|
||||
|
||||
for _, attr := range ordered {
|
||||
if value, ok := r.SubscriptionAttributes[attr]; ok {
|
||||
delete(r.SubscriptionAttributes, attr)
|
||||
if err := enc.Encode(attr, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Encode any remaining subscription attributes
|
||||
for attr, value := range r.SubscriptionAttributes {
|
||||
if err := enc.Encode(attr, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RequestDecoder reads and decodes a request from a stream
|
||||
type RequestDecoder struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
// NewRequestDecoder returns a new decoder that reads from r
|
||||
func NewRequestDecoder(r io.Reader) *RequestDecoder {
|
||||
return &RequestDecoder{
|
||||
reader: r,
|
||||
}
|
||||
}
|
||||
|
||||
// Decode decodes a ipp request into a request struct. additional data will be written to an io.Writer if data is not nil
|
||||
func (d *RequestDecoder) Decode(data io.Writer) (*Request, error) {
|
||||
req := new(Request)
|
||||
|
||||
if err := binary.Read(d.reader, binary.BigEndian, &req.ProtocolVersionMajor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Read(d.reader, binary.BigEndian, &req.ProtocolVersionMinor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Read(d.reader, binary.BigEndian, &req.Operation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Read(d.reader, binary.BigEndian, &req.RequestId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
startByteSlice := make([]byte, 1)
|
||||
|
||||
tag := TagCupsInvalid
|
||||
previousAttributeName := ""
|
||||
tagSet := false
|
||||
|
||||
attribDecoder := NewAttributeDecoder(d.reader)
|
||||
|
||||
// decode attribute buffer
|
||||
for {
|
||||
if _, err := d.reader.Read(startByteSlice); err != nil {
|
||||
// when we read from a stream, we may get an EOF if we want to read the end tag
|
||||
// all data should be read and we can ignore the error
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
startByte := int8(startByteSlice[0])
|
||||
|
||||
// check if attributes are completed
|
||||
if startByte == TagEnd {
|
||||
break
|
||||
}
|
||||
|
||||
if startByte == TagOperation {
|
||||
if req.OperationAttributes == nil {
|
||||
req.OperationAttributes = make(map[string]interface{})
|
||||
}
|
||||
|
||||
tag = TagOperation
|
||||
tagSet = true
|
||||
|
||||
}
|
||||
|
||||
if startByte == TagJob {
|
||||
if req.JobAttributes == nil {
|
||||
req.JobAttributes = make(map[string]interface{})
|
||||
}
|
||||
tag = TagJob
|
||||
tagSet = true
|
||||
}
|
||||
|
||||
if startByte == TagPrinter {
|
||||
if req.PrinterAttributes == nil {
|
||||
req.PrinterAttributes = make(map[string]interface{})
|
||||
}
|
||||
tag = TagPrinter
|
||||
tagSet = true
|
||||
}
|
||||
|
||||
if tagSet {
|
||||
if _, err := d.reader.Read(startByteSlice); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
startByte = int8(startByteSlice[0])
|
||||
}
|
||||
|
||||
attrib, err := attribDecoder.Decode(startByte)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if attrib.Name != "" {
|
||||
appendAttributeToRequest(req, tag, attrib.Name, attrib.Value)
|
||||
previousAttributeName = attrib.Name
|
||||
} else {
|
||||
appendAttributeToRequest(req, tag, previousAttributeName, attrib.Value)
|
||||
}
|
||||
|
||||
tagSet = false
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
if _, err := io.Copy(data, d.reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func appendAttributeToRequest(req *Request, tag int8, name string, value interface{}) {
|
||||
switch tag {
|
||||
case TagOperation:
|
||||
req.OperationAttributes[name] = value
|
||||
case TagPrinter:
|
||||
req.PrinterAttributes[name] = value
|
||||
case TagJob:
|
||||
req.JobAttributes[name] = value
|
||||
}
|
||||
}
|
||||
383
pkg/ipp/response.go
Normal file
383
pkg/ipp/response.go
Normal file
@@ -0,0 +1,383 @@
|
||||
package ipp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Attributes is a wrapper for a set of attributes
|
||||
type Attributes map[string][]Attribute
|
||||
|
||||
// Response defines a ipp response
|
||||
type Response struct {
|
||||
ProtocolVersionMajor int8
|
||||
ProtocolVersionMinor int8
|
||||
|
||||
StatusCode int16
|
||||
RequestId int32
|
||||
|
||||
OperationAttributes Attributes
|
||||
PrinterAttributes []Attributes
|
||||
JobAttributes []Attributes
|
||||
SubscriptionAttributes []Attributes // Added for subscription responses
|
||||
}
|
||||
|
||||
// CheckForErrors checks the status code and returns a error if it is not zero. it also returns the status message if provided by the server
|
||||
func (r *Response) CheckForErrors() error {
|
||||
if r.StatusCode != StatusOk {
|
||||
err := IPPError{
|
||||
Status: r.StatusCode,
|
||||
Message: "no status message returned",
|
||||
}
|
||||
|
||||
if len(r.OperationAttributes["status-message"]) > 0 {
|
||||
err.Message = r.OperationAttributes["status-message"][0].Value.(string)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewResponse creates a new ipp response
|
||||
func NewResponse(statusCode int16, reqID int32) *Response {
|
||||
return &Response{
|
||||
ProtocolVersionMajor: ProtocolVersionMajor,
|
||||
ProtocolVersionMinor: ProtocolVersionMinor,
|
||||
StatusCode: statusCode,
|
||||
RequestId: reqID,
|
||||
OperationAttributes: make(Attributes),
|
||||
PrinterAttributes: make([]Attributes, 0),
|
||||
JobAttributes: make([]Attributes, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// Encode encodes the response to a byte slice
|
||||
func (r *Response) Encode() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
enc := NewAttributeEncoder(buf)
|
||||
|
||||
if err := binary.Write(buf, binary.BigEndian, r.ProtocolVersionMajor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Write(buf, binary.BigEndian, r.ProtocolVersionMinor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Write(buf, binary.BigEndian, r.StatusCode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Write(buf, binary.BigEndian, r.RequestId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Write(buf, binary.BigEndian, TagOperation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.OperationAttributes == nil {
|
||||
r.OperationAttributes = make(Attributes, 0)
|
||||
}
|
||||
|
||||
if _, found := r.OperationAttributes[AttributeCharset]; !found {
|
||||
r.OperationAttributes[AttributeCharset] = []Attribute{
|
||||
{
|
||||
Value: Charset,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if _, found := r.OperationAttributes[AttributeNaturalLanguage]; !found {
|
||||
r.OperationAttributes[AttributeNaturalLanguage] = []Attribute{
|
||||
{
|
||||
Value: CharsetLanguage,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.encodeOperationAttributes(enc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(r.PrinterAttributes) > 0 {
|
||||
for _, printerAttr := range r.PrinterAttributes {
|
||||
if err := binary.Write(buf, binary.BigEndian, TagPrinter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for name, attr := range printerAttr {
|
||||
if len(attr) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
values := make([]interface{}, len(attr))
|
||||
for i, v := range attr {
|
||||
values[i] = v.Value
|
||||
}
|
||||
|
||||
if len(values) == 1 {
|
||||
if err := enc.Encode(name, values[0]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := enc.Encode(name, values); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.JobAttributes) > 0 {
|
||||
for _, jobAttr := range r.JobAttributes {
|
||||
if err := binary.Write(buf, binary.BigEndian, TagJob); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for name, attr := range jobAttr {
|
||||
if len(attr) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
values := make([]interface{}, len(attr))
|
||||
for i, v := range attr {
|
||||
values[i] = v.Value
|
||||
}
|
||||
|
||||
if len(values) == 1 {
|
||||
if err := enc.Encode(name, values[0]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := enc.Encode(name, values); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := binary.Write(buf, binary.BigEndian, TagEnd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (r *Response) encodeOperationAttributes(enc *AttributeEncoder) error {
|
||||
ordered := []string{
|
||||
AttributeCharset,
|
||||
AttributeNaturalLanguage,
|
||||
AttributePrinterURI,
|
||||
AttributeJobID,
|
||||
}
|
||||
|
||||
for _, name := range ordered {
|
||||
if attr, ok := r.OperationAttributes[name]; ok {
|
||||
delete(r.OperationAttributes, name)
|
||||
if err := encodeOperationAttribute(enc, name, attr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for name, attr := range r.OperationAttributes {
|
||||
if err := encodeOperationAttribute(enc, name, attr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeOperationAttribute(enc *AttributeEncoder, name string, attr []Attribute) error {
|
||||
if len(attr) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
values := make([]interface{}, len(attr))
|
||||
for i, v := range attr {
|
||||
values[i] = v.Value
|
||||
}
|
||||
|
||||
if len(values) == 1 {
|
||||
return enc.Encode(name, values[0])
|
||||
}
|
||||
|
||||
return enc.Encode(name, values)
|
||||
}
|
||||
|
||||
// ResponseDecoder reads and decodes a response from a stream
|
||||
type ResponseDecoder struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
// NewResponseDecoder returns a new decoder that reads from r
|
||||
func NewResponseDecoder(r io.Reader) *ResponseDecoder {
|
||||
return &ResponseDecoder{
|
||||
reader: r,
|
||||
}
|
||||
}
|
||||
|
||||
// Decode decodes a ipp response into a response struct. additional data will be written to an io.Writer if data is not nil
|
||||
func (d *ResponseDecoder) Decode(data io.Writer) (*Response, error) {
|
||||
/*
|
||||
1 byte: Protocol Major Version - b
|
||||
1 byte: Protocol Minor Version - b
|
||||
2 byte: Status ID - h
|
||||
4 byte: Request ID - i
|
||||
1 byte: Operation Attribute Byte (\0x01)
|
||||
N times: Attributes
|
||||
1 byte: Attribute End Byte (\0x03)
|
||||
*/
|
||||
|
||||
resp := new(Response)
|
||||
|
||||
// wrap the reader so we have more functionality
|
||||
// reader := bufio.NewReader(d.reader)
|
||||
|
||||
if err := binary.Read(d.reader, binary.BigEndian, &resp.ProtocolVersionMajor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Read(d.reader, binary.BigEndian, &resp.ProtocolVersionMinor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Read(d.reader, binary.BigEndian, &resp.StatusCode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := binary.Read(d.reader, binary.BigEndian, &resp.RequestId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
startByteSlice := make([]byte, 1)
|
||||
|
||||
tag := TagCupsInvalid
|
||||
previousAttributeName := ""
|
||||
tempAttributes := make(Attributes)
|
||||
tagSet := false
|
||||
|
||||
attribDecoder := NewAttributeDecoder(d.reader)
|
||||
|
||||
// decode attribute buffer
|
||||
for {
|
||||
if _, err := d.reader.Read(startByteSlice); err != nil {
|
||||
// when we read from a stream, we may get an EOF if we want to read the end tag
|
||||
// all data should be read and we can ignore the error
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
startByte := int8(startByteSlice[0])
|
||||
|
||||
// check if attributes are completed
|
||||
if startByte == TagEnd {
|
||||
break
|
||||
}
|
||||
|
||||
if startByte == TagOperation {
|
||||
if len(tempAttributes) > 0 && tag != TagCupsInvalid {
|
||||
appendAttributeToResponse(resp, tag, tempAttributes)
|
||||
tempAttributes = make(Attributes)
|
||||
}
|
||||
|
||||
tag = TagOperation
|
||||
tagSet = true
|
||||
}
|
||||
|
||||
if startByte == TagJob {
|
||||
if len(tempAttributes) > 0 && tag != TagCupsInvalid {
|
||||
appendAttributeToResponse(resp, tag, tempAttributes)
|
||||
tempAttributes = make(Attributes)
|
||||
}
|
||||
|
||||
tag = TagJob
|
||||
tagSet = true
|
||||
}
|
||||
|
||||
if startByte == TagPrinter {
|
||||
if len(tempAttributes) > 0 && tag != TagCupsInvalid {
|
||||
appendAttributeToResponse(resp, tag, tempAttributes)
|
||||
tempAttributes = make(Attributes)
|
||||
}
|
||||
|
||||
tag = TagPrinter
|
||||
tagSet = true
|
||||
}
|
||||
|
||||
if startByte == TagSubscription {
|
||||
if len(tempAttributes) > 0 && tag != TagCupsInvalid {
|
||||
appendAttributeToResponse(resp, tag, tempAttributes)
|
||||
tempAttributes = make(Attributes)
|
||||
}
|
||||
|
||||
tag = TagSubscription
|
||||
tagSet = true
|
||||
}
|
||||
|
||||
if startByte == TagEventNotification {
|
||||
if len(tempAttributes) > 0 && tag != TagCupsInvalid {
|
||||
appendAttributeToResponse(resp, tag, tempAttributes)
|
||||
tempAttributes = make(Attributes)
|
||||
}
|
||||
|
||||
tag = TagEventNotification
|
||||
tagSet = true
|
||||
}
|
||||
|
||||
if tagSet {
|
||||
if _, err := d.reader.Read(startByteSlice); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
startByte = int8(startByteSlice[0])
|
||||
}
|
||||
|
||||
attrib, err := attribDecoder.Decode(startByte)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if attrib.Name != "" {
|
||||
tempAttributes[attrib.Name] = append(tempAttributes[attrib.Name], *attrib)
|
||||
previousAttributeName = attrib.Name
|
||||
} else {
|
||||
tempAttributes[previousAttributeName] = append(tempAttributes[previousAttributeName], *attrib)
|
||||
}
|
||||
|
||||
tagSet = false
|
||||
}
|
||||
|
||||
if len(tempAttributes) > 0 && tag != TagCupsInvalid {
|
||||
appendAttributeToResponse(resp, tag, tempAttributes)
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
if _, err := io.Copy(data, d.reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func appendAttributeToResponse(resp *Response, tag int8, attr map[string][]Attribute) {
|
||||
switch tag {
|
||||
case TagOperation:
|
||||
resp.OperationAttributes = attr
|
||||
case TagPrinter:
|
||||
resp.PrinterAttributes = append(resp.PrinterAttributes, attr)
|
||||
case TagJob:
|
||||
resp.JobAttributes = append(resp.JobAttributes, attr)
|
||||
case TagSubscription, TagEventNotification:
|
||||
// Both subscription and event notification attributes go to SubscriptionAttributes
|
||||
resp.SubscriptionAttributes = append(resp.SubscriptionAttributes, attr)
|
||||
}
|
||||
}
|
||||
28
pkg/ipp/utils.go
Normal file
28
pkg/ipp/utils.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package ipp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
// ParseControlFile reads and decodes a cups control file into a response
|
||||
func ParseControlFile(jobID int, spoolDirectory string) (*Response, error) {
|
||||
if spoolDirectory == "" {
|
||||
spoolDirectory = "/var/spool/cups"
|
||||
}
|
||||
|
||||
controlFilePath := path.Join(spoolDirectory, fmt.Sprintf("c%d", jobID))
|
||||
|
||||
if _, err := os.Stat(controlFilePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
controlFile, err := os.Open(controlFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer controlFile.Close()
|
||||
|
||||
return NewResponseDecoder(controlFile).Decode(nil)
|
||||
}
|
||||
Reference in New Issue
Block a user