lp.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  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
  11. local io = require("io")
  12. local base = _G
  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")
  18. module("socket.lp")
  19. -- default port
  20. PORT = 515
  21. SERVER = os.getenv("SERVER_NAME") or os.getenv("COMPUTERNAME") or "localhost"
  22. PRINTER = os.getenv("PRINTER") or "printer"
  23. local function connect(localhost, option)
  24. local host = option.host or SERVER
  25. local port = option.port or PORT
  26. local skt
  27. local try = socket.newtry(function() if skt then skt:close() end end)
  28. if option.localbind then
  29. -- bind to a local port (if we can)
  30. local localport = 721
  31. local done, err
  32. repeat
  33. skt = socket.try(socket.tcp())
  34. try(skt:settimeout(30))
  35. done, err = skt:bind(localhost, localport)
  36. if not done then
  37. localport = localport + 1
  38. skt:close()
  39. skt = nil
  40. else break end
  41. until localport > 731
  42. socket.try(skt, err)
  43. else skt = socket.try(socket.tcp()) end
  44. try(skt:connect(host, port))
  45. return { skt = skt, try = try }
  46. end
  47. --[[
  48. RFC 1179
  49. 5.3 03 - Send queue state (short)
  50. +----+-------+----+------+----+
  51. | 03 | Queue | SP | List | LF |
  52. +----+-------+----+------+----+
  53. Command code - 3
  54. Operand 1 - Printer queue name
  55. Other operands - User names or job numbers
  56. If the user names or job numbers or both are supplied then only those
  57. jobs for those users or with those numbers will be sent.
  58. The response is an ASCII stream which describes the printer queue.
  59. The stream continues until the connection closes. Ends of lines are
  60. indicated with ASCII LF control characters. The lines may also
  61. contain ASCII HT control characters.
  62. 5.4 04 - Send queue state (long)
  63. +----+-------+----+------+----+
  64. | 04 | Queue | SP | List | LF |
  65. +----+-------+----+------+----+
  66. Command code - 4
  67. Operand 1 - Printer queue name
  68. Other operands - User names or job numbers
  69. If the user names or job numbers or both are supplied then only those
  70. jobs for those users or with those numbers will be sent.
  71. The response is an ASCII stream which describes the printer queue.
  72. The stream continues until the connection closes. Ends of lines are
  73. indicated with ASCII LF control characters. The lines may also
  74. contain ASCII HT control characters.
  75. ]]
  76. -- gets server acknowledement
  77. local function recv_ack(con)
  78. local ack = con.skt:receive(1)
  79. con.try(string.char(0) == ack, "failed to receive server acknowledgement")
  80. end
  81. -- sends client acknowledement
  82. local function send_ack(con)
  83. local sent = con.skt:send(string.char(0))
  84. con.try(sent == 1, "failed to send acknowledgement")
  85. end
  86. -- sends queue request
  87. -- 5.2 02 - Receive a printer job
  88. --
  89. -- +----+-------+----+
  90. -- | 02 | Queue | LF |
  91. -- +----+-------+----+
  92. -- Command code - 2
  93. -- Operand - Printer queue name
  94. --
  95. -- Receiving a job is controlled by a second level of commands. The
  96. -- daemon is given commands by sending them over the same connection.
  97. -- The commands are described in the next section (6).
  98. --
  99. -- After this command is sent, the client must read an acknowledgement
  100. -- octet from the daemon. A positive acknowledgement is an octet of
  101. -- zero bits. A negative acknowledgement is an octet of any other
  102. -- pattern.
  103. local function send_queue(con, queue)
  104. queue = queue or PRINTER
  105. local str = string.format("\2%s\10", queue)
  106. local sent = con.skt:send(str)
  107. con.try(sent == string.len(str), "failed to send print request")
  108. recv_ack(con)
  109. end
  110. -- sends control file
  111. -- 6.2 02 - Receive control file
  112. --
  113. -- +----+-------+----+------+----+
  114. -- | 02 | Count | SP | Name | LF |
  115. -- +----+-------+----+------+----+
  116. -- Command code - 2
  117. -- Operand 1 - Number of bytes in control file
  118. -- Operand 2 - Name of control file
  119. --
  120. -- The control file must be an ASCII stream with the ends of lines
  121. -- indicated by ASCII LF. The total number of bytes in the stream is
  122. -- sent as the first operand. The name of the control file is sent as
  123. -- the second. It should start with ASCII "cfA", followed by a three
  124. -- digit job number, followed by the host name which has constructed the
  125. -- control file. Acknowledgement processing must occur as usual after
  126. -- the command is sent.
  127. --
  128. -- The next "Operand 1" octets over the same TCP connection are the
  129. -- intended contents of the control file. Once all of the contents have
  130. -- been delivered, an octet of zero bits is sent as an indication that
  131. -- the file being sent is complete. A second level of acknowledgement
  132. -- processing must occur at this point.
  133. -- sends data file
  134. -- 6.3 03 - Receive data file
  135. --
  136. -- +----+-------+----+------+----+
  137. -- | 03 | Count | SP | Name | LF |
  138. -- +----+-------+----+------+----+
  139. -- Command code - 3
  140. -- Operand 1 - Number of bytes in data file
  141. -- Operand 2 - Name of data file
  142. --
  143. -- The data file may contain any 8 bit values at all. The total number
  144. -- of bytes in the stream may be sent as the first operand, otherwise
  145. -- the field should be cleared to 0. The name of the data file should
  146. -- start with ASCII "dfA". This should be followed by a three digit job
  147. -- number. The job number should be followed by the host name which has
  148. -- constructed the data file. Interpretation of the contents of the
  149. -- data file is determined by the contents of the corresponding control
  150. -- file. If a data file length has been specified, the next "Operand 1"
  151. -- octets over the same TCP connection are the intended contents of the
  152. -- data file. In this case, once all of the contents have been
  153. -- delivered, an octet of zero bits is sent as an indication that the
  154. -- file being sent is complete. A second level of acknowledgement
  155. -- processing must occur at this point.
  156. local function send_hdr(con, control)
  157. local sent = con.skt:send(control)
  158. con.try(sent and sent >= 1 , "failed to send header file")
  159. recv_ack(con)
  160. end
  161. local function send_control(con, control)
  162. local sent = con.skt:send(control)
  163. con.try(sent and sent >= 1, "failed to send control file")
  164. send_ack(con)
  165. end
  166. local function send_data(con,fh,size)
  167. local buf
  168. while size > 0 do
  169. buf,message = fh:read(8192)
  170. if buf then
  171. st = con.try(con.skt:send(buf))
  172. size = size - st
  173. else
  174. con.try(size == 0, "file size mismatch")
  175. end
  176. end
  177. recv_ack(con) -- note the double acknowledgement
  178. send_ack(con)
  179. recv_ack(con)
  180. return size
  181. end
  182. --[[
  183. local control_dflt = {
  184. "H"..string.sub(socket.hostname,1,31).."\10", -- host
  185. "C"..string.sub(socket.hostname,1,31).."\10", -- class
  186. "J"..string.sub(filename,1,99).."\10", -- jobname
  187. "L"..string.sub(user,1,31).."\10", -- print banner page
  188. "I"..tonumber(indent).."\10", -- indent column count ('f' only)
  189. "M"..string.sub(mail,1,128).."\10", -- mail when printed user@host
  190. "N"..string.sub(filename,1,131).."\10", -- name of source file
  191. "P"..string.sub(user,1,31).."\10", -- user name
  192. "T"..string.sub(title,1,79).."\10", -- title for banner ('p' only)
  193. "W"..tonumber(width or 132).."\10", -- width of print f,l,p only
  194. "f"..file.."\10", -- formatted print (remove control chars)
  195. "l"..file.."\10", -- print
  196. "o"..file.."\10", -- postscript
  197. "p"..file.."\10", -- pr format - requires T, L
  198. "r"..file.."\10", -- fortran format
  199. "U"..file.."\10", -- Unlink (data file only)
  200. }
  201. ]]
  202. -- generate a varying job number
  203. local seq = 0
  204. local function newjob(connection)
  205. seq = seq + 1
  206. return math.floor(socket.gettime() * 1000 + seq)%1000
  207. end
  208. local format_codes = {
  209. binary = 'l',
  210. text = 'f',
  211. ps = 'o',
  212. pr = 'p',
  213. fortran = 'r',
  214. l = 'l',
  215. r = 'r',
  216. o = 'o',
  217. p = 'p',
  218. f = 'f'
  219. }
  220. -- lp.send{option}
  221. -- requires option.file
  222. send = socket.protect(function(option)
  223. socket.try(option and base.type(option) == "table", "invalid options")
  224. local file = option.file
  225. socket.try(file, "invalid file name")
  226. local fh = socket.try(io.open(file,"rb"))
  227. local datafile_size = fh:seek("end") -- get total size
  228. fh:seek("set") -- go back to start of file
  229. local localhost = socket.dns.gethostname() or os.getenv("COMPUTERNAME")
  230. or "localhost"
  231. local con = connect(localhost, option)
  232. -- format the control file
  233. local jobno = newjob()
  234. local localip = socket.dns.toip(localhost)
  235. localhost = string.sub(localhost,1,31)
  236. local user = string.sub(option.user or os.getenv("LPRUSER") or
  237. os.getenv("USERNAME") or os.getenv("USER") or "anonymous", 1,31)
  238. local lpfile = string.format("dfA%3.3d%-s", jobno, localhost);
  239. local fmt = format_codes[option.format] or 'l'
  240. local class = string.sub(option.class or localip or localhost,1,31)
  241. local _,_,ctlfn = string.find(file,".*[%/%\\](.*)")
  242. ctlfn = string.sub(ctlfn or file,1,131)
  243. local cfile =
  244. string.format("H%-s\nC%-s\nJ%-s\nP%-s\n%.1s%-s\nU%-s\nN%-s\n",
  245. localhost,
  246. class,
  247. option.job or "LuaSocket",
  248. user,
  249. fmt, lpfile,
  250. lpfile,
  251. ctlfn); -- mandatory part of ctl file
  252. if (option.banner) then cfile = cfile .. 'L'..user..'\10' end
  253. if (option.indent) then cfile = cfile .. 'I'..base.tonumber(option.indent)..'\10' end
  254. if (option.mail) then cfile = cfile .. 'M'..string.sub((option.mail),1,128)..'\10' end
  255. if (fmt == 'p' and option.title) then cfile = cfile .. 'T'..string.sub((option.title),1,79)..'\10' end
  256. if ((fmt == 'p' or fmt == 'l' or fmt == 'f') and option.width) then
  257. cfile = cfile .. 'W'..base.tonumber(option,width)..'\10'
  258. end
  259. con.skt:settimeout(option.timeout or 65)
  260. -- send the queue header
  261. send_queue(con, option.queue)
  262. -- send the control file header
  263. local cfilecmd = string.format("\2%d cfA%3.3d%-s\n",string.len(cfile), jobno, localhost);
  264. send_hdr(con,cfilecmd)
  265. -- send the control file
  266. send_control(con,cfile)
  267. -- send the data file header
  268. local dfilecmd = string.format("\3%d dfA%3.3d%-s\n",datafile_size, jobno, localhost);
  269. send_hdr(con,dfilecmd)
  270. -- send the data file
  271. send_data(con,fh,datafile_size)
  272. fh:close()
  273. con.skt:close();
  274. return jobno, datafile_size
  275. end)
  276. --
  277. -- lp.query({host=,queue=printer|'*', format='l'|'s', list=})
  278. --
  279. query = socket.protect(function(p)
  280. p = p or {}
  281. local localhost = socket.dns.gethostname() or os.getenv("COMPUTERNAME")
  282. or "localhost"
  283. local con = connect(localhost,p)
  284. local fmt
  285. if string.sub(p.format or 's',1,1) == 's' then fmt = 3 else fmt = 4 end
  286. con.try(con.skt:send(string.format("%c%s %s\n", fmt, p.queue or "*",
  287. p.list or "")))
  288. local data = con.try(con.skt:receive("*a"))
  289. con.skt:close()
  290. return data
  291. end)