aboutsummaryrefslogtreecommitdiff
path: root/samples/lp.lua
diff options
context:
space:
mode:
authorCaleb Maclennan <caleb@alerque.com>2023-11-10 09:12:04 +0300
committerCaleb Maclennan <caleb@alerque.com>2023-11-10 09:12:04 +0300
commit5c4fc93d5f4137bf4c22ddf1a048c907a4a26727 (patch)
treea9a68e1f6a9c3bfe2b64fa1c3a4098865b7d3b5d /samples/lp.lua
parentccef3bc4e2aa6ee5b997a80aabb58f4ff0b0e98f (diff)
parent43a97b7f0053313b43906371dbdc226271e6c8ab (diff)
downloadluasocket-hjelmeland-patch-1.tar.gz
luasocket-hjelmeland-patch-1.tar.bz2
luasocket-hjelmeland-patch-1.zip
Merge branch 'master' into hjelmeland-patch-1hjelmeland-patch-1
Diffstat (limited to 'samples/lp.lua')
-rw-r--r--samples/lp.lua323
1 files changed, 323 insertions, 0 deletions
diff --git a/samples/lp.lua b/samples/lp.lua
new file mode 100644
index 0000000..25f0b95
--- /dev/null
+++ b/samples/lp.lua
@@ -0,0 +1,323 @@
1-----------------------------------------------------------------------------
2-- LPD support for the Lua language
3-- LuaSocket toolkit.
4-- Author: David Burgess
5-- Modified by Diego Nehab, but David is in charge
6-----------------------------------------------------------------------------
7--[[
8 if you have any questions: RFC 1179
9]]
10-- make sure LuaSocket is loaded
11local io = require("io")
12local base = _G
13local os = require("os")
14local math = require("math")
15local string = require("string")
16local socket = require("socket")
17local ltn12 = require("ltn12")
18module("socket.lp")
19
20-- default port
21PORT = 515
22SERVER = os.getenv("SERVER_NAME") or os.getenv("COMPUTERNAME") or "localhost"
23PRINTER = os.getenv("PRINTER") or "printer"
24
25local function connect(localhost, option)
26 local host = option.host or SERVER
27 local port = option.port or PORT
28 local skt
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)
32 local localport = 721
33 local done, err
34 repeat
35 skt = socket.try(socket.tcp())
36 try(skt:settimeout(30))
37 done, err = skt:bind(localhost, localport)
38 if not done then
39 localport = localport + 1
40 skt:close()
41 skt = nil
42 else break end
43 until localport > 731
44 socket.try(skt, err)
45 else skt = socket.try(socket.tcp()) end
46 try(skt:connect(host, port))
47 return { skt = skt, try = try }
48end
49
50--[[
51RFC 1179
525.3 03 - Send queue state (short)
53
54 +----+-------+----+------+----+
55 | 03 | Queue | SP | List | LF |
56 +----+-------+----+------+----+
57 Command code - 3
58 Operand 1 - Printer queue name
59 Other operands - User names or job numbers
60
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.
63
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.
68
695.4 04 - Send queue state (long)
70
71 +----+-------+----+------+----+
72 | 04 | Queue | SP | List | LF |
73 +----+-------+----+------+----+
74 Command code - 4
75 Operand 1 - Printer queue name
76 Other operands - User names or job numbers
77
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.
80
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.
85]]
86
87-- gets server acknowledement
88local function recv_ack(con)
89 local ack = con.skt:receive(1)
90 con.try(string.char(0) == ack, "failed to receive server acknowledgement")
91end
92
93-- sends client acknowledement
94local function send_ack(con)
95 local sent = con.skt:send(string.char(0))
96 con.try(sent == 1, "failed to send acknowledgement")
97end
98
99-- sends queue request
100-- 5.2 02 - Receive a printer job
101--
102-- +----+-------+----+
103-- | 02 | Queue | LF |
104-- +----+-------+----+
105-- Command code - 2
106-- Operand - Printer queue name
107--
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).
111--
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
115-- pattern.
116local 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")
121 recv_ack(con)
122end
123
124-- sends control file
125-- 6.2 02 - Receive control file
126--
127-- +----+-------+----+------+----+
128-- | 02 | Count | SP | Name | LF |
129-- +----+-------+----+------+----+
130-- Command code - 2
131-- Operand 1 - Number of bytes in control file
132-- Operand 2 - Name of control file
133--
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.
141--
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.
147
148-- sends data file
149-- 6.3 03 - Receive data file
150--
151-- +----+-------+----+------+----+
152-- | 03 | Count | SP | Name | LF |
153-- +----+-------+----+------+----+
154-- Command code - 3
155-- Operand 1 - Number of bytes in data file
156-- Operand 2 - Name of data file
157--
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.
171
172
173local function send_hdr(con, control)
174 local sent = con.skt:send(control)
175 con.try(sent and sent >= 1 , "failed to send header file")
176 recv_ack(con)
177end
178
179local function send_control(con, control)
180 local sent = con.skt:send(control)
181 con.try(sent and sent >= 1, "failed to send control file")
182 send_ack(con)
183end
184
185local function send_data(con,fh,size)
186 local buf
187 while size > 0 do
188 buf,message = fh:read(8192)
189 if buf then
190 st = con.try(con.skt:send(buf))
191 size = size - st
192 else
193 con.try(size == 0, "file size mismatch")
194 end
195 end
196 recv_ack(con) -- note the double acknowledgement
197 send_ack(con)
198 recv_ack(con)
199 return size
200end
201
202
203--[[
204local 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
215
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)
222}
223]]
224
225-- generate a varying job number
226local seq = 0
227local function newjob(connection)
228 seq = seq + 1
229 return math.floor(socket.gettime() * 1000 + seq)%1000
230end
231
232
233local format_codes = {
234 binary = 'l',
235 text = 'f',
236 ps = 'o',
237 pr = 'p',
238 fortran = 'r',
239 l = 'l',
240 r = 'r',
241 o = 'o',
242 p = 'p',
243 f = 'f'
244}
245
246-- lp.send{option}
247-- requires option.file
248
249send = 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")
257 or "localhost"
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)
270 local cfile =
271 string.format("H%-s\nC%-s\nJ%-s\nP%-s\n%.1s%-s\nU%-s\nN%-s\n",
272 localhost,
273 class,
274 option.job or "LuaSocket",
275 user,
276 fmt, lpfile,
277 lpfile,
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'
285 end
286
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)
293
294-- send the control file
295 send_control(con,cfile)
296
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)
300
301-- send the data file
302 send_data(con,fh,datafile_size)
303 fh:close()
304 con.skt:close();
305 return jobno, datafile_size
306end)
307
308--
309-- lp.query({host=,queue=printer|'*', format='l'|'s', list=})
310--
311query = socket.protect(function(p)
312 p = p or {}
313 local localhost = socket.dns.gethostname() or os.getenv("COMPUTERNAME")
314 or "localhost"
315 local con = connect(localhost,p)
316 local fmt
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 "*",
319 p.list or "")))
320 local data = con.try(con.skt:receive("*a"))
321 con.skt:close()
322 return data
323end)