mirror of
				https://github.com/lunarmodules/luasocket.git
				synced 2025-10-31 10:25:55 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			403 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			403 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| -- make sure LuaSocket is loaded
 | |
| local socket = require("socket")
 | |
| local ltn12 = require("ltn12")
 | |
| local lp = {}
 | |
| --socket.lp = lp
 | |
| -- make all module globals fall into lp namespace
 | |
| setmetatable(lp, { __index = _G })
 | |
| setfenv(1, lp)
 | |
| 
 | |
| -- default port
 | |
| PORT = 515
 | |
| SERVER = os.getenv("SERVER_NAME") or os.getenv("COMPUTERNAME") or "localhost"
 | |
| PRINTER = os.getenv("PRINTER") or "printer"
 | |
| 
 | |
| 
 | |
| --[[
 | |
| 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(connection)
 | |
|   local code, current, separator, _
 | |
|   local ack = socket.try(connection:receive(1))
 | |
|   if string.char(0) ~= ack then
 | |
|     connection:close(); error"failed to receive server acknowledement"
 | |
|   end
 | |
| end
 | |
| 
 | |
| -- sends client acknowledement
 | |
| local function send_ack(connection)
 | |
|   local sent = socket.try(connection:send(string.char(0)))
 | |
|   if not sent or sent ~= 1 then
 | |
|     connection:close();
 | |
|     error"failed to send acknowledgement"
 | |
|   end
 | |
| 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(connection,queue)
 | |
|   if not queue then queue=PRINTER end
 | |
|   local str = string.format("\2%s\10",queue)
 | |
|   local sent = socket.try(connection:send(str))
 | |
|   if not sent or sent ~= string.len(str) then
 | |
|     error "failed to send print request"
 | |
|   end
 | |
|   recv_ack(connection)
 | |
| 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(connection,control)
 | |
|   local sent = socket.try(connection:send(control))
 | |
|   if not sent or sent < 1 then
 | |
|     error "failed to send file"
 | |
|   end
 | |
|   recv_ack(connection)
 | |
| end
 | |
| 
 | |
| 
 | |
| local function send_control(connection,control)
 | |
|   local sent = socket.try(connection:send(control))
 | |
|   if not sent or sent < 1 then
 | |
|     error "failed to send file"
 | |
|   end
 | |
|   send_ack(connection)
 | |
| end
 | |
| 
 | |
| local function send_data(connection,fh,size)
 | |
| --  local sink = socket.sink("keep-open", connection)
 | |
| --  ltn12.pump.all(source, sink)
 | |
|   local buf, st, message
 | |
|   st = true
 | |
|   while size > 0 do
 | |
|     buf,message = fh:read(8192)
 | |
|     if buf then
 | |
|       st = socket.try(connection:send(buf))
 | |
|       size = size - st
 | |
|     else
 | |
|       if size ~= 0 then
 | |
|         connection:close()
 | |
|         return nil, "file size mismatch"
 | |
|       end
 | |
|     end
 | |
|   end
 | |
|   send_ack(connection)
 | |
|   recv_ack(connection)
 | |
|   return size,nil
 | |
| 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 function getjobno(connection)
 | |
| --  print(math.mod(socket.time() * 1000, port)) -- ok for windows
 | |
| --  print(os.time() / port,math.random(0,999))
 | |
|   return  math.random(0,999)
 | |
| end
 | |
| 
 | |
| local function getcon(localhost,option)
 | |
|   local skt, st, message
 | |
|   local localport = 721
 | |
|   if not option then
 | |
|     error('no options',0)
 | |
|   end
 | |
|   if option.localbind then
 | |
|     repeat
 | |
|   -- bind to a local port (if we can)
 | |
|       skt = socket.try(socket.tcp())
 | |
|       skt:settimeout(30)
 | |
| 
 | |
|       st, message = skt:bind(localhost,localport,-1);
 | |
|   --    print("bind",st,message)
 | |
|       if st then
 | |
|         st,message = skt:connect(option.host or SERVER, option.port or PORT)
 | |
|   --      print("connect",st,message)
 | |
|       end
 | |
|   --    print(st,localport,message)
 | |
|       if not st then
 | |
|          localport = localport + 1
 | |
|          skt:close()
 | |
|       end
 | |
|     until st or localport > 731 or (not st and message ~= "local address already in use")
 | |
|     if st then return skt end
 | |
|   end
 | |
|   return socket.try(socket.connect(option.host or SERVER, option.port or PORT))
 | |
| 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 = socket.protect(function(file, option)
 | |
|   if not file  then error "invalid file name" end
 | |
|   if not option or type(option) ~= "table" then error "invalid options" end
 | |
|   local fh = socket.try(io.open(file,"rb"))
 | |
|   -- get total size
 | |
|   local datafile_size = fh:seek("end")
 | |
|   -- go back to start of file
 | |
|   fh:seek("set")
 | |
|   math.randomseed(socket.time() * 1000)
 | |
|   local localhost = socket.dns.gethostname() or os.getenv("COMPUTERNAME") or "localhost"
 | |
| 
 | |
| --  local connection, message = skt:connect(option.host or SERVER, option.port or PORT)
 | |
| 
 | |
|   local connection = getcon(localhost,option)
 | |
| 
 | |
| -- format the control file
 | |
|   local jobno = getjobno(connection)
 | |
|   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 ctlfn,
 | |
|     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'..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'..tonumber(option,width)..'\10'
 | |
|   end
 | |
| 
 | |
|   connection:settimeout(option.timeout or 65)
 | |
| 
 | |
| -- send the queue header
 | |
|   send_queue(connection,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(connection,cfilecmd)
 | |
| 
 | |
| -- send the control file
 | |
|   send_control(connection,cfile)
 | |
| 
 | |
| -- send the data file header
 | |
|   local dfilecmd = string.format("\3%d dfA%3.3d%-s\n",datafile_size, jobno, localhost);
 | |
|   send_hdr(connection,dfilecmd)
 | |
| 
 | |
| -- send the data file
 | |
|   send_data(connection,fh,datafile_size)
 | |
|   fh:close()
 | |
|   connection:close();
 | |
|   return datafile_size
 | |
| end)
 | |
| 
 | |
| 
 | |
| --socket.lpq({host=,queue=printer|'*', format='l'|'s', list=})
 | |
| lp.query = socket.protect(function(p)
 | |
|   if not p then p={} end
 | |
|   local localhost = socket.dns.gethostname() or os.getenv("COMPUTERNAME") or "localhost"
 | |
|   local connection = getcon(localhost,p)
 | |
|   local fmt,data
 | |
|   if string.sub(p.format or 's',1,1) == 's' then fmt = 3 else fmt = 4 end
 | |
|   local sent = socket.try(connection:send(string.format("%c%s %s\n", fmt, p.queue or "*", p.list or "")))
 | |
|   local data = socket.try(connection:receive("*a"))
 | |
|   io.write(data)
 | |
|   connection:close()
 | |
|   return tostring(string.len(data))
 | |
| end)
 | |
| 
 | |
| --for k,v in arg do print(k,v) end
 | |
| local function usage()
 | |
|   print('\nUsage: lp filename [keyword=val...]\n')
 | |
|   print('Valid keywords are :')
 | |
|   print(
 | |
|      '  host=remote host or IP address (default "localhost")\n' ..
 | |
|      '  queue=remote queue or printer name (default "printer")\n' ..
 | |
|      '  port=remote port number (default 515)\n' ..
 | |
|      '  user=sending user name\n' ..
 | |
|      '  format=["binary" | "text" | "ps" | "pr" | "fortran"] (default "binary")\n' ..
 | |
|      '  banner=true|false\n' ..
 | |
|      '  indent=number of columns to indent\n' ..
 | |
|      '  mail=email of address to notify when print is complete\n' ..
 | |
|      '  title=title to use for "pr" format\n' ..
 | |
|      '  width=width for "text" or "pr" formats\n' ..
 | |
|      '  class=\n' ..
 | |
|      '  job=\n' ..
 | |
|      '  name=\n' ..
 | |
|      '  localbind=true|false\n'
 | |
|      )
 | |
|   return nil
 | |
| end
 | |
| 
 | |
| if not arg or not arg[1] then
 | |
|   return usage()
 | |
| end
 | |
| 
 | |
| do
 | |
|     local s="opt = {"
 | |
|     for i = 2 , table.getn(arg), 1 do
 | |
|       s = s .. string.gsub(arg[i],"[%s%c%p]*([%w]*)=([\"]?[%w%s_!@#$%%^&*()<>:;]+[\"]\?\.?)","%1%=\"%2\",\n")
 | |
|     end
 | |
|     s = s .. "};\n"
 | |
|     assert(loadstring(s))();
 | |
|     if not arg[2] then
 | |
|       return usage()
 | |
|     end
 | |
|     if arg[1] ~= "query" then
 | |
|         r,e=lp.send(arg[1],opt)
 | |
|         io.stderr:write(tostring(r or e),'\n')
 | |
|     else
 | |
|         r,e=lp.query(opt)
 | |
|         io.stderr:write(tostring(r or e),'\n')
 | |
|     end
 | |
| end
 | |
| 
 | |
| -- trivial tests
 | |
| --lua lp.lua lp.lua queue=default host=localhost
 | |
| --lua lp.lua lp.lua queue=default host=localhost format=binary localbind=1
 | |
| --lua lp.lua query queue=default host=localhost
 | |
| collectgarbage()
 | |
| collectgarbage()
 | |
| --print(socket.lp.query{host='localhost', queue="default"})
 | |
| 
 | |
| return nil
 |