1 -----------------------------------------------------------------------------
2 -- LPD support for the Lua language
4 -- Author: David Burgess
5 -- Modified by Diego Nehab, but David is in charge
6 -----------------------------------------------------------------------------
8 if you have any questions: RFC 1179
10 -- make sure LuaSocket is loaded
11 local io
= require("io")
13 local os
= require("os")
14 local math
= require("math")
15 local string = require("string")
16 local socket
= require("socket")
17 local ltn12
= require("ltn12")
22 SERVER
= os
.getenv("SERVER_NAME") or os
.getenv("COMPUTERNAME") or "localhost"
23 PRINTER
= os
.getenv("PRINTER") or "printer"
25 local function connect(localhost
, option
)
26 local host
= option
.host
or SERVER
27 local port
= option
.port
or PORT
29 local try
= socket
.newtry(function() if skt
then skt
:close() end end)
30 if option
.localbind
then
31 -- bind to a local port (if we can)
35 skt
= socket
.try(socket
.tcp())
36 try(skt
:settimeout(30))
37 done
, err
= skt
:bind(localhost
, localport
)
39 localport
= localport
+ 1
45 else skt
= socket
.try(socket
.tcp()) end
46 try(skt
:connect(host
, port
))
47 return { skt
= skt
, try
= try
}
52 5.3 03 - Send queue state (short)
54 +----+-------+----+------+----+
55 | 03 | Queue | SP | List | LF |
56 +----+-------+----+------+----+
58 Operand 1 - Printer queue name
59 Other operands - User names or job numbers
61 If the user names or job numbers or both are supplied then only those
62 jobs for those users or with those numbers will be sent.
64 The response is an ASCII stream which describes the printer queue.
65 The stream continues until the connection closes. Ends of lines are
66 indicated with ASCII LF control characters. The lines may also
67 contain ASCII HT control characters.
69 5.4 04 - Send queue state (long)
71 +----+-------+----+------+----+
72 | 04 | Queue | SP | List | LF |
73 +----+-------+----+------+----+
75 Operand 1 - Printer queue name
76 Other operands - User names or job numbers
78 If the user names or job numbers or both are supplied then only those
79 jobs for those users or with those numbers will be sent.
81 The response is an ASCII stream which describes the printer queue.
82 The stream continues until the connection closes. Ends of lines are
83 indicated with ASCII LF control characters. The lines may also
84 contain ASCII HT control characters.
87 -- gets server acknowledement
88 local function recv_ack(con
)
89 local ack
= con
.skt
:receive(1)
90 con
.try(string.char(0) == ack
, "failed to receive server acknowledgement")
93 -- sends client acknowledement
94 local function send_ack(con
)
95 local sent
= con
.skt
:send(string.char(0))
96 con
.try(sent
== 1, "failed to send acknowledgement")
99 -- sends queue request
100 -- 5.2 02 - Receive a printer job
102 -- +----+-------+----+
103 -- | 02 | Queue | LF |
104 -- +----+-------+----+
106 -- Operand - Printer queue name
108 -- Receiving a job is controlled by a second level of commands. The
109 -- daemon is given commands by sending them over the same connection.
110 -- The commands are described in the next section (6).
112 -- After this command is sent, the client must read an acknowledgement
113 -- octet from the daemon. A positive acknowledgement is an octet of
114 -- zero bits. A negative acknowledgement is an octet of any other
116 local function send_queue(con
, queue
)
117 queue
= queue
or PRINTER
118 local str
= string.format("\2%s\10", queue
)
119 local sent
= con
.skt
:send(str
)
120 con
.try(sent
== string.len(str
), "failed to send print request")
124 -- sends control file
125 -- 6.2 02 - Receive control file
127 -- +----+-------+----+------+----+
128 -- | 02 | Count | SP | Name | LF |
129 -- +----+-------+----+------+----+
131 -- Operand 1 - Number of bytes in control file
132 -- Operand 2 - Name of control file
134 -- The control file must be an ASCII stream with the ends of lines
135 -- indicated by ASCII LF. The total number of bytes in the stream is
136 -- sent as the first operand. The name of the control file is sent as
137 -- the second. It should start with ASCII "cfA", followed by a three
138 -- digit job number, followed by the host name which has constructed the
139 -- control file. Acknowledgement processing must occur as usual after
140 -- the command is sent.
142 -- The next "Operand 1" octets over the same TCP connection are the
143 -- intended contents of the control file. Once all of the contents have
144 -- been delivered, an octet of zero bits is sent as an indication that
145 -- the file being sent is complete. A second level of acknowledgement
146 -- processing must occur at this point.
149 -- 6.3 03 - Receive data file
151 -- +----+-------+----+------+----+
152 -- | 03 | Count | SP | Name | LF |
153 -- +----+-------+----+------+----+
155 -- Operand 1 - Number of bytes in data file
156 -- Operand 2 - Name of data file
158 -- The data file may contain any 8 bit values at all. The total number
159 -- of bytes in the stream may be sent as the first operand, otherwise
160 -- the field should be cleared to 0. The name of the data file should
161 -- start with ASCII "dfA". This should be followed by a three digit job
162 -- number. The job number should be followed by the host name which has
163 -- constructed the data file. Interpretation of the contents of the
164 -- data file is determined by the contents of the corresponding control
165 -- file. If a data file length has been specified, the next "Operand 1"
166 -- octets over the same TCP connection are the intended contents of the
167 -- data file. In this case, once all of the contents have been
168 -- delivered, an octet of zero bits is sent as an indication that the
169 -- file being sent is complete. A second level of acknowledgement
170 -- processing must occur at this point.
173 local function send_hdr(con
, control
)
174 local sent
= con
.skt
:send(control
)
175 con
.try(sent
and sent
>= 1 , "failed to send header file")
179 local function send_control(con
, control
)
180 local sent
= con
.skt
:send(control
)
181 con
.try(sent
and sent
>= 1, "failed to send control file")
185 local function send_data(con
,fh
,size
)
188 buf
,message
= fh
:read(8192)
190 st
= con
.try(con
.skt
:send(buf
))
193 con
.try(size
== 0, "file size mismatch")
196 recv_ack(con
) -- note the double acknowledgement
204 local control_dflt = {
205 "H"..string.sub(socket.hostname,1,31).."\10", -- host
206 "C"..string.sub(socket.hostname,1,31).."\10", -- class
207 "J"..string.sub(filename,1,99).."\10", -- jobname
208 "L"..string.sub(user,1,31).."\10", -- print banner page
209 "I"..tonumber(indent).."\10", -- indent column count ('f' only)
210 "M"..string.sub(mail,1,128).."\10", -- mail when printed user@host
211 "N"..string.sub(filename,1,131).."\10", -- name of source file
212 "P"..string.sub(user,1,31).."\10", -- user name
213 "T"..string.sub(title,1,79).."\10", -- title for banner ('p' only)
214 "W"..tonumber(width or 132).."\10", -- width of print f,l,p only
216 "f"..file.."\10", -- formatted print (remove control chars)
217 "l"..file.."\10", -- print
218 "o"..file.."\10", -- postscript
219 "p"..file.."\10", -- pr format - requires T, L
220 "r"..file.."\10", -- fortran format
221 "U"..file.."\10", -- Unlink (data file only)
225 -- generate a varying job number
227 local function newjob(connection
)
229 return math
.floor(socket
.gettime() * 1000 + seq
)%1000
233 local format_codes
= {
247 -- requires option.file
249 send
= socket
.protect(function(option
)
250 socket
.try(option
and base
.type(option
) == "table", "invalid options")
251 local file
= option
.file
252 socket
.try(file
, "invalid file name")
253 local fh
= socket
.try(io
.open(file
,"rb"))
254 local datafile_size
= fh
:seek("end") -- get total size
255 fh
:seek("set") -- go back to start of file
256 local localhost
= socket
.dns
.gethostname() or os
.getenv("COMPUTERNAME")
258 local con
= connect(localhost
, option
)
259 -- format the control file
260 local jobno
= newjob()
261 local localip
= socket
.dns
.toip(localhost
)
262 localhost
= string.sub(localhost
,1,31)
263 local user
= string.sub(option
.user
or os
.getenv("LPRUSER") or
264 os
.getenv("USERNAME") or os
.getenv("USER") or "anonymous", 1,31)
265 local lpfile
= string.format("dfA%3.3d%-s", jobno
, localhost
);
266 local fmt
= format_codes
[option
.format] or 'l'
267 local class
= string.sub(option
.class
or localip
or localhost
,1,31)
268 local _
,_
,ctlfn
= string.find(file
,".*[%/%\\](.*)")
269 ctlfn
= string.sub(ctlfn
or file
,1,131)
271 string.format("H%-s\nC%-s\nJ%-s\nP%-s\n%.1s%-s\nU%-s\nN%-s\n",
274 option
.job
or "LuaSocket",
278 ctlfn
); -- mandatory part of ctl file
279 if (option
.banner
) then cfile
= cfile
.. 'L'..user
..'\10' end
280 if (option
.indent
) then cfile
= cfile
.. 'I'..base
.tonumber(option
.indent
)..'\10' end
281 if (option
.mail
) then cfile
= cfile
.. 'M'..string.sub((option
.mail
),1,128)..'\10' end
282 if (fmt
== 'p' and option
.title
) then cfile
= cfile
.. 'T'..string.sub((option
.title
),1,79)..'\10' end
283 if ((fmt
== 'p' or fmt
== 'l' or fmt
== 'f') and option
.width
) then
284 cfile
= cfile
.. 'W'..base
.tonumber(option
,width
)..'\10'
287 con
.skt
:settimeout(option
.timeout
or 65)
288 -- send the queue header
289 send_queue(con
, option
.queue
)
290 -- send the control file header
291 local cfilecmd
= string.format("\2%d cfA%3.3d%-s\n",string.len(cfile
), jobno
, localhost
);
292 send_hdr(con
,cfilecmd
)
294 -- send the control file
295 send_control(con
,cfile
)
297 -- send the data file header
298 local dfilecmd
= string.format("\3%d dfA%3.3d%-s\n",datafile_size
, jobno
, localhost
);
299 send_hdr(con
,dfilecmd
)
301 -- send the data file
302 send_data(con
,fh
,datafile_size
)
305 return jobno
, datafile_size
309 -- lp.query({host=,queue=printer|'*', format='l'|'s', list=})
311 query
= socket
.protect(function(p
)
313 local localhost
= socket
.dns
.gethostname() or os
.getenv("COMPUTERNAME")
315 local con
= connect(localhost
,p
)
317 if string.sub(p
.format or 's',1,1) == 's' then fmt
= 3 else fmt
= 4 end
318 con
.try(con
.skt
:send(string.format("%c%s %s\n", fmt
, p
.queue
or "*",
320 local data
= con
.try(con
.skt
:receive("*a"))