-
Notifications
You must be signed in to change notification settings - Fork 15
/
escl.go
277 lines (233 loc) · 6.79 KB
/
escl.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/* ipp-usb - HTTP reverse proxy, backed by IPP-over-USB connection to device
*
* Copyright (C) 2020 and up by Alexander Pevzner ([email protected])
* See LICENSE for license terms and conditions
*
* ESCL service registration
*/
package main
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"sort"
"strings"
)
// EsclService queries eSCL ScannerCapabilities using provided
// http.Client and decodes received information into the form
// suitable for DNS-SD registration
//
// Discovered services will be added to the services collection
func EsclService(log *LogMessage, services *DNSSdServices,
port int, usbinfo UsbDeviceInfo, ippinfo *IppPrinterInfo,
c *http.Client) (httpstatus int, err error) {
uri := fmt.Sprintf("http://localhost:%d/eSCL/ScannerCapabilities", port)
decoder := newEsclCapsDecoder(ippinfo)
svc := DNSSdSvcInfo{
Type: "_uscan._tcp",
Port: port,
}
var xmlData []byte
var list []string
// Query ScannerCapabilities
resp, err := c.Get(uri)
if err != nil {
goto ERROR
}
if resp.StatusCode/100 != 2 {
resp.Body.Close()
httpstatus = resp.StatusCode
err = fmt.Errorf("HTTP status: %s", resp.Status)
goto ERROR
}
xmlData, err = ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
goto ERROR
}
log.Add(LogTraceESCL, '<', "ESCL Scanner Capabilities:")
log.LineWriter(LogTraceESCL, '<').WriteClose(xmlData)
log.Nl(LogTraceESCL)
log.Flush()
// Decode the XML
err = decoder.decode(bytes.NewBuffer(xmlData))
if err != nil {
goto ERROR
}
if decoder.uuid == "" {
decoder.uuid = usbinfo.UUID()
}
// If we have no data, assume eSCL response was invalud
// If we miss some essential data, assume eSCL response was invalid
switch {
case decoder.version == "":
err = errors.New("missed pwg:Version")
case len(decoder.cs) == 0:
err = errors.New("missed scan:ColorMode")
case len(decoder.pdl) == 0:
err = errors.New("missed pwg:DocumentFormat")
case !(decoder.platen || decoder.adf):
err = errors.New("missed pwg:DocumentFormat")
}
if err != nil {
goto ERROR
}
// Build eSCL DNSSdInfo
if decoder.duplex {
svc.Txt.Add("duplex", "T")
} else {
svc.Txt.Add("duplex", "F")
}
switch {
case decoder.platen && !decoder.adf:
svc.Txt.Add("is", "platen")
case !decoder.platen && decoder.adf:
svc.Txt.Add("is", "adf")
case decoder.platen && decoder.adf:
svc.Txt.Add("is", "platen,adf")
}
list = []string{}
for c := range decoder.cs {
list = append(list, c)
}
sort.Strings(list)
svc.Txt.IfNotEmpty("cs", strings.Join(list, ","))
svc.Txt.IfNotEmpty("UUID", decoder.uuid)
svc.Txt.URLIfNotEmpty("adminurl", decoder.adminurl)
svc.Txt.URLIfNotEmpty("representation", decoder.representation)
list = []string{}
for p := range decoder.pdl {
list = append(list, p)
}
sort.Strings(list)
svc.Txt.AddPDL("pdl", strings.Join(list, ","))
svc.Txt.Add("ty", usbinfo.ProductName)
svc.Txt.Add("rs", "eSCL")
svc.Txt.IfNotEmpty("vers", decoder.version)
svc.Txt.IfNotEmpty("txtvers", "1")
// Add to services
services.Add(svc)
return
// Handle a error
ERROR:
err = fmt.Errorf("eSCL: %s", err)
return
}
// esclCapsDecoder represents eSCL ScannerCapabilities decoder
type esclCapsDecoder struct {
uuid string // Device UUID
adminurl string // Admin URL
representation string // Icon URL
version string // eSCL Version
platen, adf bool // Has platen/ADF
duplex bool // Has duplex
pdl, cs map[string]struct{} // Formats/colors
}
// newesclCapsDecoder creates new esclCapsDecoder
func newEsclCapsDecoder(ippinfo *IppPrinterInfo) *esclCapsDecoder {
decoder := &esclCapsDecoder{
pdl: make(map[string]struct{}),
cs: make(map[string]struct{}),
}
if ippinfo != nil {
decoder.uuid = ippinfo.UUID
decoder.adminurl = ippinfo.AdminURL
decoder.representation = ippinfo.IconURL
}
return decoder
}
// Decode scanner capabilities
func (decoder *esclCapsDecoder) decode(in io.Reader) error {
xmlDecoder := xml.NewDecoder(in)
var path bytes.Buffer
var lenStack []int
for {
token, err := xmlDecoder.RawToken()
if err != nil {
break
}
switch t := token.(type) {
case xml.StartElement:
lenStack = append(lenStack, path.Len())
path.WriteByte('/')
path.WriteString(t.Name.Space)
path.WriteByte(':')
path.WriteString(t.Name.Local)
decoder.element(path.String())
case xml.EndElement:
last := len(lenStack) - 1
path.Truncate(lenStack[last])
lenStack = lenStack[:last]
case xml.CharData:
data := bytes.TrimSpace(t)
if len(data) > 0 {
decoder.data(path.String(), string(data))
}
}
}
return nil
}
const (
// Relative to root
esclPlaten = "/scan:ScannerCapabilities/scan:Platen"
esclAdf = "/scan:ScannerCapabilities/scan:Adf"
esclPlatenInputCaps = esclPlaten + "/scan:PlatenInputCaps"
esclAdfSimplexCaps = esclAdf + "/scan:AdfSimplexInputCaps"
esclAdfDuplexCaps = esclAdf + "/scan:AdfDuplexInputCaps"
// Relative to esclPlatenInputCaps, esclAdfSimplexCaps or esclAdfDuplexCaps
esclSettingProfile = "/scan:SettingProfiles/scan:SettingProfile"
esclColorMode = esclSettingProfile + "/scan:ColorModes/scan:ColorMode"
esclDocumentFormat = esclSettingProfile + "/scan:DocumentFormats/pwg:DocumentFormat"
esclDocumentFormatExt = esclSettingProfile + "/scan:DocumentFormats/scan:DocumentFormatExt"
)
// handle beginning of XML element
func (decoder *esclCapsDecoder) element(path string) {
switch path {
case esclPlaten:
decoder.platen = true
case esclAdf:
decoder.adf = true
case esclAdfDuplexCaps:
decoder.duplex = true
}
}
// handle XML element data
func (decoder *esclCapsDecoder) data(path, data string) {
switch path {
case "/scan:ScannerCapabilities/scan:UUID":
uuid := UUIDNormalize(data)
if uuid != "" && decoder.uuid == "" {
decoder.uuid = data
}
case "/scan:ScannerCapabilities/scan:AdminURI":
decoder.adminurl = data
case "/scan:ScannerCapabilities/scan:IconURI":
decoder.representation = data
case "/scan:ScannerCapabilities/pwg:Version":
decoder.version = data
case esclPlatenInputCaps + esclColorMode,
esclAdfSimplexCaps + esclColorMode,
esclAdfDuplexCaps + esclColorMode:
data = strings.ToLower(data)
switch {
case strings.HasPrefix(data, "rgb"):
decoder.cs["color"] = struct{}{}
case strings.HasPrefix(data, "grayscale"):
decoder.cs["grayscale"] = struct{}{}
case strings.HasPrefix(data, "blackandwhite"):
decoder.cs["binary"] = struct{}{}
}
case esclPlatenInputCaps + esclDocumentFormat,
esclAdfSimplexCaps + esclDocumentFormat,
esclAdfDuplexCaps + esclDocumentFormat:
decoder.pdl[data] = struct{}{}
case esclPlatenInputCaps + esclDocumentFormatExt,
esclAdfSimplexCaps + esclDocumentFormatExt,
esclAdfDuplexCaps + esclDocumentFormatExt:
decoder.pdl[data] = struct{}{}
}
}