summaryrefslogtreecommitdiff
path: root/Build/source/texk/web2c/luatexdir/luasocket/etc/lp.lua
blob: 25f0b95e15e359b533cddbc6e1cc6d3ccf3e6660 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
-----------------------------------------------------------------------------
-- 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)