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) 324