123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- -----------------------------------------------------------------------------
- -- LPD support for the Lua language
- -- LuaSocket toolkit.
- -- Author: David Burgess
- -- Modified by Diego Nehab, but David is in charge
- -----------------------------------------------------------------------------
- --[[
- if you have any questions: RFC 1179
- ]]
- -- make sure LuaSocket is loaded
- local io = require("io")
- local base = _G
- local os = require("os")
- local math = require("math")
- local string = require("string")
- local socket = require("socket")
- local ltn12 = require("ltn12")
- module("socket.lp")
- -- default port
- PORT = 515
- SERVER = os.getenv("SERVER_NAME") or os.getenv("COMPUTERNAME") or "localhost"
- PRINTER = os.getenv("PRINTER") or "printer"
- local function connect(localhost, option)
- local host = option.host or SERVER
- local port = option.port or PORT
- local skt
- local try = socket.newtry(function() if skt then skt:close() end end)
- if option.localbind then
- -- bind to a local port (if we can)
- local localport = 721
- local done, err
- repeat
- skt = socket.try(socket.tcp())
- try(skt:settimeout(30))
- done, err = skt:bind(localhost, localport)
- if not done then
- localport = localport + 1
- skt:close()
- skt = nil
- else break end
- until localport > 731
- socket.try(skt, err)
- else skt = socket.try(socket.tcp()) end
- try(skt:connect(host, port))
- return { skt = skt, try = try }
- end
- --[[
- RFC 1179
- 5.3 03 - Send queue state (short)
- +----+-------+----+------+----+
- | 03 | Queue | SP | List | LF |
- +----+-------+----+------+----+
- Command code - 3
- Operand 1 - Printer queue name
- Other operands - User names or job numbers
- If the user names or job numbers or both are supplied then only those
- jobs for those users or with those numbers will be sent.
- The response is an ASCII stream which describes the printer queue.
- The stream continues until the connection closes. Ends of lines are
- indicated with ASCII LF control characters. The lines may also
- contain ASCII HT control characters.
- 5.4 04 - Send queue state (long)
- +----+-------+----+------+----+
- | 04 | Queue | SP | List | LF |
- +----+-------+----+------+----+
- Command code - 4
- Operand 1 - Printer queue name
- Other operands - User names or job numbers
- If the user names or job numbers or both are supplied then only those
- jobs for those users or with those numbers will be sent.
- The response is an ASCII stream which describes the printer queue.
- The stream continues until the connection closes. Ends of lines are
- indicated with ASCII LF control characters. The lines may also
- contain ASCII HT control characters.
- ]]
- -- gets server acknowledement
- local function recv_ack(con)
- local ack = con.skt:receive(1)
- con.try(string.char(0) == ack, "failed to receive server acknowledgement")
- end
- -- sends client acknowledement
- local function send_ack(con)
- local sent = con.skt:send(string.char(0))
- con.try(sent == 1, "failed to send acknowledgement")
- end
- -- sends queue request
- -- 5.2 02 - Receive a printer job
- --
- -- +----+-------+----+
- -- | 02 | Queue | LF |
- -- +----+-------+----+
- -- Command code - 2
- -- Operand - Printer queue name
- --
- -- Receiving a job is controlled by a second level of commands. The
- -- daemon is given commands by sending them over the same connection.
- -- The commands are described in the next section (6).
- --
- -- After this command is sent, the client must read an acknowledgement
- -- octet from the daemon. A positive acknowledgement is an octet of
- -- zero bits. A negative acknowledgement is an octet of any other
- -- pattern.
- local function send_queue(con, queue)
- queue = queue or PRINTER
- local str = string.format("\2%s\10", queue)
- local sent = con.skt:send(str)
- con.try(sent == string.len(str), "failed to send print request")
- recv_ack(con)
- end
- -- sends control file
- -- 6.2 02 - Receive control file
- --
- -- +----+-------+----+------+----+
- -- | 02 | Count | SP | Name | LF |
- -- +----+-------+----+------+----+
- -- Command code - 2
- -- Operand 1 - Number of bytes in control file
- -- Operand 2 - Name of control file
- --
- -- The control file must be an ASCII stream with the ends of lines
- -- indicated by ASCII LF. The total number of bytes in the stream is
- -- sent as the first operand. The name of the control file is sent as
- -- the second. It should start with ASCII "cfA", followed by a three
- -- digit job number, followed by the host name which has constructed the
- -- control file. Acknowledgement processing must occur as usual after
- -- the command is sent.
- --
- -- The next "Operand 1" octets over the same TCP connection are the
- -- intended contents of the control file. Once all of the contents have
- -- been delivered, an octet of zero bits is sent as an indication that
- -- the file being sent is complete. A second level of acknowledgement
- -- processing must occur at this point.
- -- sends data file
- -- 6.3 03 - Receive data file
- --
- -- +----+-------+----+------+----+
- -- | 03 | Count | SP | Name | LF |
- -- +----+-------+----+------+----+
- -- Command code - 3
- -- Operand 1 - Number of bytes in data file
- -- Operand 2 - Name of data file
- --
- -- The data file may contain any 8 bit values at all. The total number
- -- of bytes in the stream may be sent as the first operand, otherwise
- -- the field should be cleared to 0. The name of the data file should
- -- start with ASCII "dfA". This should be followed by a three digit job
- -- number. The job number should be followed by the host name which has
- -- constructed the data file. Interpretation of the contents of the
- -- data file is determined by the contents of the corresponding control
- -- file. If a data file length has been specified, the next "Operand 1"
- -- octets over the same TCP connection are the intended contents of the
- -- data file. In this case, once all of the contents have been
- -- delivered, an octet of zero bits is sent as an indication that the
- -- file being sent is complete. A second level of acknowledgement
- -- processing must occur at this point.
- local function send_hdr(con, control)
- local sent = con.skt:send(control)
- con.try(sent and sent >= 1 , "failed to send header file")
- recv_ack(con)
- end
- local function send_control(con, control)
- local sent = con.skt:send(control)
- con.try(sent and sent >= 1, "failed to send control file")
- send_ack(con)
- end
- local function send_data(con,fh,size)
- local buf
- while size > 0 do
- buf,message = fh:read(8192)
- if buf then
- st = con.try(con.skt:send(buf))
- size = size - st
- else
- con.try(size == 0, "file size mismatch")
- end
- end
- recv_ack(con) -- note the double acknowledgement
- send_ack(con)
- recv_ack(con)
- return size
- end
- --[[
- local control_dflt = {
- "H"..string.sub(socket.hostname,1,31).."\10", -- host
- "C"..string.sub(socket.hostname,1,31).."\10", -- class
- "J"..string.sub(filename,1,99).."\10", -- jobname
- "L"..string.sub(user,1,31).."\10", -- print banner page
- "I"..tonumber(indent).."\10", -- indent column count ('f' only)
- "M"..string.sub(mail,1,128).."\10", -- mail when printed user@host
- "N"..string.sub(filename,1,131).."\10", -- name of source file
- "P"..string.sub(user,1,31).."\10", -- user name
- "T"..string.sub(title,1,79).."\10", -- title for banner ('p' only)
- "W"..tonumber(width or 132).."\10", -- width of print f,l,p only
- "f"..file.."\10", -- formatted print (remove control chars)
- "l"..file.."\10", -- print
- "o"..file.."\10", -- postscript
- "p"..file.."\10", -- pr format - requires T, L
- "r"..file.."\10", -- fortran format
- "U"..file.."\10", -- Unlink (data file only)
- }
- ]]
- -- generate a varying job number
- local seq = 0
- local function newjob(connection)
- seq = seq + 1
- return math.floor(socket.gettime() * 1000 + seq)%1000
- end
- local format_codes = {
- binary = 'l',
- text = 'f',
- ps = 'o',
- pr = 'p',
- fortran = 'r',
- l = 'l',
- r = 'r',
- o = 'o',
- p = 'p',
- f = 'f'
- }
- -- lp.send{option}
- -- requires option.file
- send = socket.protect(function(option)
- socket.try(option and base.type(option) == "table", "invalid options")
- local file = option.file
- socket.try(file, "invalid file name")
- local fh = socket.try(io.open(file,"rb"))
- local datafile_size = fh:seek("end") -- get total size
- fh:seek("set") -- go back to start of file
- local localhost = socket.dns.gethostname() or os.getenv("COMPUTERNAME")
- or "localhost"
- local con = connect(localhost, option)
- -- format the control file
- local jobno = newjob()
- local localip = socket.dns.toip(localhost)
- localhost = string.sub(localhost,1,31)
- local user = string.sub(option.user or os.getenv("LPRUSER") or
- os.getenv("USERNAME") or os.getenv("USER") or "anonymous", 1,31)
- local lpfile = string.format("dfA%3.3d%-s", jobno, localhost);
- local fmt = format_codes[option.format] or 'l'
- local class = string.sub(option.class or localip or localhost,1,31)
- local _,_,ctlfn = string.find(file,".*[%/%\\](.*)")
- ctlfn = string.sub(ctlfn or file,1,131)
- local cfile =
- string.format("H%-s\nC%-s\nJ%-s\nP%-s\n%.1s%-s\nU%-s\nN%-s\n",
- localhost,
- class,
- option.job or "LuaSocket",
- user,
- fmt, lpfile,
- lpfile,
- ctlfn); -- mandatory part of ctl file
- if (option.banner) then cfile = cfile .. 'L'..user..'\10' end
- if (option.indent) then cfile = cfile .. 'I'..base.tonumber(option.indent)..'\10' end
- if (option.mail) then cfile = cfile .. 'M'..string.sub((option.mail),1,128)..'\10' end
- if (fmt == 'p' and option.title) then cfile = cfile .. 'T'..string.sub((option.title),1,79)..'\10' end
- if ((fmt == 'p' or fmt == 'l' or fmt == 'f') and option.width) then
- cfile = cfile .. 'W'..base.tonumber(option,width)..'\10'
- end
- con.skt:settimeout(option.timeout or 65)
- -- send the queue header
- send_queue(con, option.queue)
- -- send the control file header
- local cfilecmd = string.format("\2%d cfA%3.3d%-s\n",string.len(cfile), jobno, localhost);
- send_hdr(con,cfilecmd)
- -- send the control file
- send_control(con,cfile)
- -- send the data file header
- local dfilecmd = string.format("\3%d dfA%3.3d%-s\n",datafile_size, jobno, localhost);
- send_hdr(con,dfilecmd)
- -- send the data file
- send_data(con,fh,datafile_size)
- fh:close()
- con.skt:close();
- return jobno, datafile_size
- end)
- --
- -- lp.query({host=,queue=printer|'*', format='l'|'s', list=})
- --
- query = socket.protect(function(p)
- p = p or {}
- local localhost = socket.dns.gethostname() or os.getenv("COMPUTERNAME")
- or "localhost"
- local con = connect(localhost,p)
- local fmt
- if string.sub(p.format or 's',1,1) == 's' then fmt = 3 else fmt = 4 end
- con.try(con.skt:send(string.format("%c%s %s\n", fmt, p.queue or "*",
- p.list or "")))
- local data = con.try(con.skt:receive("*a"))
- con.skt:close()
- return data
- end)
|