|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005 |
- # -*- test-case-name: twisted.conch.test.test_cftp -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
-
- """
- Implementation module for the I{cftp} command.
- """
- import fcntl
- import fnmatch
- import getpass
- import glob
- import os
- import pwd
- import stat
- import struct
- import sys
- import tty
- from typing import List, Optional, TextIO, Union
-
- from twisted.conch.client import connect, default, options
- from twisted.conch.ssh import channel, common, connection, filetransfer
- from twisted.internet import defer, reactor, stdio, utils
- from twisted.protocols import basic
- from twisted.python import failure, log, usage
- from twisted.python.filepath import FilePath
-
-
- class ClientOptions(options.ConchOptions):
-
- synopsis = """Usage: cftp [options] [user@]host
- cftp [options] [user@]host[:dir[/]]
- cftp [options] [user@]host[:file [localfile]]
- """
- longdesc = (
- "cftp is a client for logging into a remote machine and "
- "executing commands to send and receive file information"
- )
-
- optParameters: List[List[Optional[Union[str, int]]]] = [
- ["buffersize", "B", 32768, "Size of the buffer to use for sending/receiving."],
- ["batchfile", "b", None, "File to read commands from, or '-' for stdin."],
- ["requests", "R", 5, "Number of requests to make before waiting for a reply."],
- ["subsystem", "s", "sftp", "Subsystem/server program to connect to."],
- ]
-
- compData = usage.Completions(
- descriptions={"buffersize": "Size of send/receive buffer (default: 32768)"},
- extraActions=[
- usage.CompleteUserAtHost(),
- usage.CompleteFiles(descr="local file"),
- ],
- )
-
- def parseArgs(self, host, localPath=None):
- self["remotePath"] = ""
- if ":" in host:
- host, self["remotePath"] = host.split(":", 1)
- self["remotePath"].rstrip("/")
- self["host"] = host
- self["localPath"] = localPath
-
-
- def run():
- args = sys.argv[1:]
- if "-l" in args: # cvs is an idiot
- i = args.index("-l")
- args = args[i : i + 2] + args
- del args[i + 2 : i + 4]
- options = ClientOptions()
- try:
- options.parseOptions(args)
- except usage.UsageError as u:
- print("ERROR: %s" % u)
- sys.exit(1)
- if options["log"]:
- realout = sys.stdout
- log.startLogging(sys.stderr)
- sys.stdout = realout
- else:
- log.discardLogs()
- doConnect(options)
- reactor.run()
-
-
- def handleError():
- global exitStatus
- exitStatus = 2
- try:
- reactor.stop()
- except BaseException:
- pass
- log.err(failure.Failure())
- raise
-
-
- def doConnect(options):
- if "@" in options["host"]:
- options["user"], options["host"] = options["host"].split("@", 1)
- host = options["host"]
- if not options["user"]:
- options["user"] = getpass.getuser()
- if not options["port"]:
- options["port"] = 22
- else:
- options["port"] = int(options["port"])
- host = options["host"]
- port = options["port"]
- conn = SSHConnection()
- conn.options = options
- vhk = default.verifyHostKey
- uao = default.SSHUserAuthClient(options["user"], options, conn)
- connect.connect(host, port, options, vhk, uao).addErrback(_ebExit)
-
-
- def _ebExit(f):
- if hasattr(f.value, "value"):
- s = f.value.value
- else:
- s = str(f)
- print(s)
- try:
- reactor.stop()
- except BaseException:
- pass
-
-
- def _ignore(*args):
- pass
-
-
- class FileWrapper:
- def __init__(self, f):
- self.f = f
- self.total = 0.0
- f.seek(0, 2) # seek to the end
- self.size = f.tell()
-
- def __getattr__(self, attr):
- return getattr(self.f, attr)
-
-
- class StdioClient(basic.LineReceiver):
-
- _pwd = pwd
-
- ps = "cftp> "
- delimiter = b"\n"
-
- reactor = reactor
-
- def __init__(self, client, f=None):
- self.client = client
- self.currentDirectory = ""
- self.file = f
- self.useProgressBar = (not f and 1) or 0
-
- def connectionMade(self):
- self.client.realPath("").addCallback(self._cbSetCurDir)
-
- def _cbSetCurDir(self, path):
- self.currentDirectory = path
- self._newLine()
-
- def _writeToTransport(self, msg):
- if isinstance(msg, str):
- msg = msg.encode("utf-8")
- return self.transport.write(msg)
-
- def lineReceived(self, line):
- if self.client.transport.localClosed:
- return
- if isinstance(line, bytes):
- line = line.decode("utf-8")
- log.msg("got line %s" % line)
- line = line.lstrip()
- if not line:
- self._newLine()
- return
- if self.file and line.startswith("-"):
- self.ignoreErrors = 1
- line = line[1:]
- else:
- self.ignoreErrors = 0
- d = self._dispatchCommand(line)
- if d is not None:
- d.addCallback(self._cbCommand)
- d.addErrback(self._ebCommand)
-
- def _dispatchCommand(self, line):
- if " " in line:
- command, rest = line.split(" ", 1)
- rest = rest.lstrip()
- else:
- command, rest = line, ""
- if command.startswith("!"): # command
- f = self.cmd_EXEC
- rest = (command[1:] + " " + rest).strip()
- else:
- command = command.upper()
- log.msg("looking up cmd %s" % command)
- f = getattr(self, "cmd_%s" % command, None)
- if f is not None:
- return defer.maybeDeferred(f, rest)
- else:
- errMsg = "No command called `%s'" % (command)
- self._ebCommand(failure.Failure(NotImplementedError(errMsg)))
- self._newLine()
-
- def _printFailure(self, f):
- log.msg(f)
- e = f.trap(NotImplementedError, filetransfer.SFTPError, OSError, IOError)
- if e == NotImplementedError:
- self._writeToTransport(self.cmd_HELP(""))
- elif e == filetransfer.SFTPError:
- errMsg = "remote error %i: %s\n" % (f.value.code, f.value.message)
- self._writeToTransport(errMsg)
- elif e in (OSError, IOError):
- errMsg = "local error %i: %s\n" % (f.value.errno, f.value.strerror)
- self._writeToTransport(errMsg)
-
- def _newLine(self):
- if self.client.transport.localClosed:
- return
- self._writeToTransport(self.ps)
- self.ignoreErrors = 0
- if self.file:
- l = self.file.readline()
- if not l:
- self.client.transport.loseConnection()
- else:
- self._writeToTransport(l)
- self.lineReceived(l.strip())
-
- def _cbCommand(self, result):
- if result is not None:
- if isinstance(result, str):
- result = result.encode("utf-8")
- self._writeToTransport(result)
- if not result.endswith(b"\n"):
- self._writeToTransport(b"\n")
- self._newLine()
-
- def _ebCommand(self, f):
- self._printFailure(f)
- if self.file and not self.ignoreErrors:
- self.client.transport.loseConnection()
- self._newLine()
-
- def cmd_CD(self, path):
- path, rest = self._getFilename(path)
- if not path.endswith("/"):
- path += "/"
- newPath = path and os.path.join(self.currentDirectory, path) or ""
- d = self.client.openDirectory(newPath)
- d.addCallback(self._cbCd)
- d.addErrback(self._ebCommand)
- return d
-
- def _cbCd(self, directory):
- directory.close()
- d = self.client.realPath(directory.name)
- d.addCallback(self._cbCurDir)
- return d
-
- def _cbCurDir(self, path):
- self.currentDirectory = path
-
- def cmd_CHGRP(self, rest):
- grp, rest = rest.split(None, 1)
- path, rest = self._getFilename(rest)
- grp = int(grp)
- d = self.client.getAttrs(path)
- d.addCallback(self._cbSetUsrGrp, path, grp=grp)
- return d
-
- def cmd_CHMOD(self, rest):
- mod, rest = rest.split(None, 1)
- path, rest = self._getFilename(rest)
- mod = int(mod, 8)
- d = self.client.setAttrs(path, {"permissions": mod})
- d.addCallback(_ignore)
- return d
-
- def cmd_CHOWN(self, rest):
- usr, rest = rest.split(None, 1)
- path, rest = self._getFilename(rest)
- usr = int(usr)
- d = self.client.getAttrs(path)
- d.addCallback(self._cbSetUsrGrp, path, usr=usr)
- return d
-
- def _cbSetUsrGrp(self, attrs, path, usr=None, grp=None):
- new = {}
- new["uid"] = (usr is not None) and usr or attrs["uid"]
- new["gid"] = (grp is not None) and grp or attrs["gid"]
- d = self.client.setAttrs(path, new)
- d.addCallback(_ignore)
- return d
-
- def cmd_GET(self, rest):
- remote, rest = self._getFilename(rest)
- if "*" in remote or "?" in remote: # wildcard
- if rest:
- local, rest = self._getFilename(rest)
- if not os.path.isdir(local):
- return "Wildcard get with non-directory target."
- else:
- local = b""
- d = self._remoteGlob(remote)
- d.addCallback(self._cbGetMultiple, local)
- return d
- if rest:
- local, rest = self._getFilename(rest)
- else:
- local = os.path.split(remote)[1]
- log.msg((remote, local))
- lf = open(local, "wb", 0)
- path = FilePath(self.currentDirectory).child(remote)
- d = self.client.openFile(path.path, filetransfer.FXF_READ, {})
- d.addCallback(self._cbGetOpenFile, lf)
- d.addErrback(self._ebCloseLf, lf)
- return d
-
- def _cbGetMultiple(self, files, local):
- # XXX this can be optimized for times w/o progress bar
- return self._cbGetMultipleNext(None, files, local)
-
- def _cbGetMultipleNext(self, res, files, local):
- if isinstance(res, failure.Failure):
- self._printFailure(res)
- elif res:
- self._writeToTransport(res)
- if not res.endswith("\n"):
- self._writeToTransport("\n")
- if not files:
- return
- f = files.pop(0)[0]
- lf = open(os.path.join(local, os.path.split(f)[1]), "wb", 0)
- path = FilePath(self.currentDirectory).child(f)
- d = self.client.openFile(path.path, filetransfer.FXF_READ, {})
- d.addCallback(self._cbGetOpenFile, lf)
- d.addErrback(self._ebCloseLf, lf)
- d.addBoth(self._cbGetMultipleNext, files, local)
- return d
-
- def _ebCloseLf(self, f, lf):
- lf.close()
- return f
-
- def _cbGetOpenFile(self, rf, lf):
- return rf.getAttrs().addCallback(self._cbGetFileSize, rf, lf)
-
- def _cbGetFileSize(self, attrs, rf, lf):
- if not stat.S_ISREG(attrs["permissions"]):
- rf.close()
- lf.close()
- return "Can't get non-regular file: %s" % rf.name
- rf.size = attrs["size"]
- bufferSize = self.client.transport.conn.options["buffersize"]
- numRequests = self.client.transport.conn.options["requests"]
- rf.total = 0.0
- dList = []
- chunks = []
- startTime = self.reactor.seconds()
- for i in range(numRequests):
- d = self._cbGetRead("", rf, lf, chunks, 0, bufferSize, startTime)
- dList.append(d)
- dl = defer.DeferredList(dList, fireOnOneErrback=1)
- dl.addCallback(self._cbGetDone, rf, lf)
- return dl
-
- def _getNextChunk(self, chunks):
- end = 0
- for chunk in chunks:
- if end == "eof":
- return # nothing more to get
- if end != chunk[0]:
- i = chunks.index(chunk)
- chunks.insert(i, (end, chunk[0]))
- return (end, chunk[0] - end)
- end = chunk[1]
- bufSize = int(self.client.transport.conn.options["buffersize"])
- chunks.append((end, end + bufSize))
- return (end, bufSize)
-
- def _cbGetRead(self, data, rf, lf, chunks, start, size, startTime):
- if data and isinstance(data, failure.Failure):
- log.msg("get read err: %s" % data)
- reason = data
- reason.trap(EOFError)
- i = chunks.index((start, start + size))
- del chunks[i]
- chunks.insert(i, (start, "eof"))
- elif data:
- log.msg("get read data: %i" % len(data))
- lf.seek(start)
- lf.write(data)
- if len(data) != size:
- log.msg("got less than we asked for: %i < %i" % (len(data), size))
- i = chunks.index((start, start + size))
- del chunks[i]
- chunks.insert(i, (start, start + len(data)))
- rf.total += len(data)
- if self.useProgressBar:
- self._printProgressBar(rf, startTime)
- chunk = self._getNextChunk(chunks)
- if not chunk:
- return
- else:
- start, length = chunk
- log.msg("asking for %i -> %i" % (start, start + length))
- d = rf.readChunk(start, length)
- d.addBoth(self._cbGetRead, rf, lf, chunks, start, length, startTime)
- return d
-
- def _cbGetDone(self, ignored, rf, lf):
- log.msg("get done")
- rf.close()
- lf.close()
- if self.useProgressBar:
- self._writeToTransport("\n")
- return f"Transferred {rf.name} to {lf.name}"
-
- def cmd_PUT(self, rest):
- """
- Do an upload request for a single local file or a globing expression.
-
- @param rest: Requested command line for the PUT command.
- @type rest: L{str}
-
- @return: A deferred which fires with L{None} when transfer is done.
- @rtype: L{defer.Deferred}
- """
- local, rest = self._getFilename(rest)
-
- # FIXME: https://twistedmatrix.com/trac/ticket/7241
- # Use a better check for globbing expression.
- if "*" in local or "?" in local:
- if rest:
- remote, rest = self._getFilename(rest)
- remote = os.path.join(self.currentDirectory, remote)
- else:
- remote = ""
-
- files = glob.glob(local)
- return self._putMultipleFiles(files, remote)
-
- else:
- if rest:
- remote, rest = self._getFilename(rest)
- else:
- remote = os.path.split(local)[1]
- return self._putSingleFile(local, remote)
-
- def _putSingleFile(self, local, remote):
- """
- Perform an upload for a single file.
-
- @param local: Path to local file.
- @type local: L{str}.
-
- @param remote: Remote path for the request relative to current working
- directory.
- @type remote: L{str}
-
- @return: A deferred which fires when transfer is done.
- """
- return self._cbPutMultipleNext(None, [local], remote, single=True)
-
- def _putMultipleFiles(self, files, remote):
- """
- Perform an upload for a list of local files.
-
- @param files: List of local files.
- @type files: C{list} of L{str}.
-
- @param remote: Remote path for the request relative to current working
- directory.
- @type remote: L{str}
-
- @return: A deferred which fires when transfer is done.
- """
- return self._cbPutMultipleNext(None, files, remote)
-
- def _cbPutMultipleNext(self, previousResult, files, remotePath, single=False):
- """
- Perform an upload for the next file in the list of local files.
-
- @param previousResult: Result form previous file form the list.
- @type previousResult: L{str}
-
- @param files: List of local files.
- @type files: C{list} of L{str}
-
- @param remotePath: Remote path for the request relative to current
- working directory.
- @type remotePath: L{str}
-
- @param single: A flag which signals if this is a transfer for a single
- file in which case we use the exact remote path
- @type single: L{bool}
-
- @return: A deferred which fires when transfer is done.
- """
- if isinstance(previousResult, failure.Failure):
- self._printFailure(previousResult)
- elif previousResult:
- if isinstance(previousResult, str):
- previousResult = previousResult.encode("utf-8")
- self._writeToTransport(previousResult)
- if not previousResult.endswith(b"\n"):
- self._writeToTransport(b"\n")
-
- currentFile = None
- while files and not currentFile:
- try:
- currentFile = files.pop(0)
- localStream = open(currentFile, "rb")
- except BaseException:
- self._printFailure(failure.Failure())
- currentFile = None
-
- # No more files to transfer.
- if not currentFile:
- return None
-
- if single:
- remote = remotePath
- else:
- name = os.path.split(currentFile)[1]
- remote = os.path.join(remotePath, name)
- log.msg((name, remote, remotePath))
-
- d = self._putRemoteFile(localStream, remote)
- d.addBoth(self._cbPutMultipleNext, files, remotePath)
- return d
-
- def _putRemoteFile(self, localStream, remotePath):
- """
- Do an upload request.
-
- @param localStream: Local stream from where data is read.
- @type localStream: File like object.
-
- @param remotePath: Remote path for the request relative to current working directory.
- @type remotePath: L{str}
-
- @return: A deferred which fires when transfer is done.
- """
- remote = os.path.join(self.currentDirectory, remotePath)
- flags = filetransfer.FXF_WRITE | filetransfer.FXF_CREAT | filetransfer.FXF_TRUNC
- d = self.client.openFile(remote, flags, {})
- d.addCallback(self._cbPutOpenFile, localStream)
- d.addErrback(self._ebCloseLf, localStream)
- return d
-
- def _cbPutOpenFile(self, rf, lf):
- numRequests = self.client.transport.conn.options["requests"]
- if self.useProgressBar:
- lf = FileWrapper(lf)
- dList = []
- chunks = []
- startTime = self.reactor.seconds()
- for i in range(numRequests):
- d = self._cbPutWrite(None, rf, lf, chunks, startTime)
- if d:
- dList.append(d)
- dl = defer.DeferredList(dList, fireOnOneErrback=1)
- dl.addCallback(self._cbPutDone, rf, lf)
- return dl
-
- def _cbPutWrite(self, ignored, rf, lf, chunks, startTime):
- chunk = self._getNextChunk(chunks)
- start, size = chunk
- lf.seek(start)
- data = lf.read(size)
- if self.useProgressBar:
- lf.total += len(data)
- self._printProgressBar(lf, startTime)
- if data:
- d = rf.writeChunk(start, data)
- d.addCallback(self._cbPutWrite, rf, lf, chunks, startTime)
- return d
- else:
- return
-
- def _cbPutDone(self, ignored, rf, lf):
- lf.close()
- rf.close()
- if self.useProgressBar:
- self._writeToTransport("\n")
- return f"Transferred {lf.name} to {rf.name}"
-
- def cmd_LCD(self, path):
- os.chdir(path)
-
- def cmd_LN(self, rest):
- linkpath, rest = self._getFilename(rest)
- targetpath, rest = self._getFilename(rest)
- linkpath, targetpath = map(
- lambda x: os.path.join(self.currentDirectory, x), (linkpath, targetpath)
- )
- return self.client.makeLink(linkpath, targetpath).addCallback(_ignore)
-
- def cmd_LS(self, rest):
- # possible lines:
- # ls current directory
- # ls name_of_file that file
- # ls name_of_directory that directory
- # ls some_glob_string current directory, globbed for that string
- options = []
- rest = rest.split()
- while rest and rest[0] and rest[0][0] == "-":
- opts = rest.pop(0)[1:]
- for o in opts:
- if o == "l":
- options.append("verbose")
- elif o == "a":
- options.append("all")
- rest = " ".join(rest)
- path, rest = self._getFilename(rest)
- if not path:
- fullPath = self.currentDirectory + "/"
- else:
- fullPath = os.path.join(self.currentDirectory, path)
- d = self._remoteGlob(fullPath)
- d.addCallback(self._cbDisplayFiles, options)
- return d
-
- def _cbDisplayFiles(self, files, options):
- files.sort()
- if "all" not in options:
- files = [f for f in files if not f[0].startswith(b".")]
- if "verbose" in options:
- lines = [f[1] for f in files]
- else:
- lines = [f[0] for f in files]
- if not lines:
- return None
- else:
- return b"\n".join(lines)
-
- def cmd_MKDIR(self, path):
- path, rest = self._getFilename(path)
- path = os.path.join(self.currentDirectory, path)
- return self.client.makeDirectory(path, {}).addCallback(_ignore)
-
- def cmd_RMDIR(self, path):
- path, rest = self._getFilename(path)
- path = os.path.join(self.currentDirectory, path)
- return self.client.removeDirectory(path).addCallback(_ignore)
-
- def cmd_LMKDIR(self, path):
- os.system("mkdir %s" % path)
-
- def cmd_RM(self, path):
- path, rest = self._getFilename(path)
- path = os.path.join(self.currentDirectory, path)
- return self.client.removeFile(path).addCallback(_ignore)
-
- def cmd_LLS(self, rest):
- os.system("ls %s" % rest)
-
- def cmd_RENAME(self, rest):
- oldpath, rest = self._getFilename(rest)
- newpath, rest = self._getFilename(rest)
- oldpath, newpath = map(
- lambda x: os.path.join(self.currentDirectory, x), (oldpath, newpath)
- )
- return self.client.renameFile(oldpath, newpath).addCallback(_ignore)
-
- def cmd_EXIT(self, ignored):
- self.client.transport.loseConnection()
-
- cmd_QUIT = cmd_EXIT
-
- def cmd_VERSION(self, ignored):
- version = "SFTP version %i" % self.client.version
- if isinstance(version, str):
- version = version.encode("utf-8")
- return version
-
- def cmd_HELP(self, ignored):
- return """Available commands:
- cd path Change remote directory to 'path'.
- chgrp gid path Change gid of 'path' to 'gid'.
- chmod mode path Change mode of 'path' to 'mode'.
- chown uid path Change uid of 'path' to 'uid'.
- exit Disconnect from the server.
- get remote-path [local-path] Get remote file.
- help Get a list of available commands.
- lcd path Change local directory to 'path'.
- lls [ls-options] [path] Display local directory listing.
- lmkdir path Create local directory.
- ln linkpath targetpath Symlink remote file.
- lpwd Print the local working directory.
- ls [-l] [path] Display remote directory listing.
- mkdir path Create remote directory.
- progress Toggle progress bar.
- put local-path [remote-path] Put local file.
- pwd Print the remote working directory.
- quit Disconnect from the server.
- rename oldpath newpath Rename remote file.
- rmdir path Remove remote directory.
- rm path Remove remote file.
- version Print the SFTP version.
- ? Synonym for 'help'.
- """
-
- def cmd_PWD(self, ignored):
- return self.currentDirectory
-
- def cmd_LPWD(self, ignored):
- return os.getcwd()
-
- def cmd_PROGRESS(self, ignored):
- self.useProgressBar = not self.useProgressBar
- return "%ssing progess bar." % (self.useProgressBar and "U" or "Not u")
-
- def cmd_EXEC(self, rest):
- """
- Run C{rest} using the user's shell (or /bin/sh if they do not have
- one).
- """
- shell = self._pwd.getpwnam(getpass.getuser())[6]
- if not shell:
- shell = "/bin/sh"
- if rest:
- cmds = ["-c", rest]
- return utils.getProcessOutput(shell, cmds, errortoo=1)
- else:
- os.system(shell)
-
- # accessory functions
-
- def _remoteGlob(self, fullPath):
- log.msg("looking up %s" % fullPath)
- head, tail = os.path.split(fullPath)
- if "*" in tail or "?" in tail:
- glob = 1
- else:
- glob = 0
- if tail and not glob: # could be file or directory
- # try directory first
- d = self.client.openDirectory(fullPath)
- d.addCallback(self._cbOpenList, "")
- d.addErrback(self._ebNotADirectory, head, tail)
- else:
- d = self.client.openDirectory(head)
- d.addCallback(self._cbOpenList, tail)
- return d
-
- def _cbOpenList(self, directory, glob):
- files = []
- d = directory.read()
- d.addBoth(self._cbReadFile, files, directory, glob)
- return d
-
- def _ebNotADirectory(self, reason, path, glob):
- d = self.client.openDirectory(path)
- d.addCallback(self._cbOpenList, glob)
- return d
-
- def _cbReadFile(self, files, matchedFiles, directory, glob):
- if not isinstance(files, failure.Failure):
- if glob:
- glob = glob.encode("utf-8")
- matchedFiles.extend([f for f in files if fnmatch.fnmatch(f[0], glob)])
- else:
- matchedFiles.extend(files)
- d = directory.read()
- d.addBoth(self._cbReadFile, matchedFiles, directory, glob)
- return d
- else:
- reason = files
- reason.trap(EOFError)
- directory.close()
- return matchedFiles
-
- def _abbrevSize(self, size):
- # from http://mail.python.org/pipermail/python-list/1999-December/018395.html
- _abbrevs = [
- (1 << 50, "PB"),
- (1 << 40, "TB"),
- (1 << 30, "GB"),
- (1 << 20, "MB"),
- (1 << 10, "kB"),
- (1, "B"),
- ]
-
- for factor, suffix in _abbrevs:
- if size > factor:
- break
- return "%.1f" % (size / factor) + suffix
-
- def _abbrevTime(self, t):
- if t > 3600: # 1 hour
- hours = int(t / 3600)
- t -= 3600 * hours
- mins = int(t / 60)
- t -= 60 * mins
- return "%i:%02i:%02i" % (hours, mins, t)
- else:
- mins = int(t / 60)
- t -= 60 * mins
- return "%02i:%02i" % (mins, t)
-
- def _printProgressBar(self, f, startTime):
- """
- Update a console progress bar on this L{StdioClient}'s transport, based
- on the difference between the start time of the operation and the
- current time according to the reactor, and appropriate to the size of
- the console window.
-
- @param f: a wrapper around the file which is being written or read
- @type f: L{FileWrapper}
-
- @param startTime: The time at which the operation being tracked began.
- @type startTime: L{float}
- """
- diff = self.reactor.seconds() - startTime
- total = f.total
- try:
- winSize = struct.unpack("4H", fcntl.ioctl(0, tty.TIOCGWINSZ, "12345679"))
- except OSError:
- winSize = [None, 80]
- if diff == 0.0:
- speed = 0.0
- else:
- speed = total / diff
- if speed:
- timeLeft = (f.size - total) / speed
- else:
- timeLeft = 0
- front = f.name
- if f.size:
- percentage = (total / f.size) * 100
- else:
- percentage = 100
- back = "%3i%% %s %sps %s " % (
- percentage,
- self._abbrevSize(total),
- self._abbrevSize(speed),
- self._abbrevTime(timeLeft),
- )
- spaces = (winSize[1] - (len(front) + len(back) + 1)) * " "
- command = f"\r{front}{spaces}{back}"
- self._writeToTransport(command)
-
- def _getFilename(self, line):
- """
- Parse line received as command line input and return first filename
- together with the remaining line.
-
- @param line: Arguments received from command line input.
- @type line: L{str}
-
- @return: Tupple with filename and rest. Return empty values when no path was not found.
- @rtype: C{tupple}
- """
- line = line.strip()
- if not line:
- return "", ""
- if line[0] in "'\"":
- ret = []
- line = list(line)
- try:
- for i in range(1, len(line)):
- c = line[i]
- if c == line[0]:
- return "".join(ret), "".join(line[i + 1 :]).lstrip()
- elif c == "\\": # quoted character
- del line[i]
- if line[i] not in "'\"\\":
- raise IndexError(f"bad quote: \\{line[i]}")
- ret.append(line[i])
- else:
- ret.append(line[i])
- except IndexError:
- raise IndexError("unterminated quote")
- ret = line.split(None, 1)
- if len(ret) == 1:
- return ret[0], ""
- else:
- return ret[0], ret[1]
-
-
- setattr(StdioClient, "cmd_?", StdioClient.cmd_HELP)
-
-
- class SSHConnection(connection.SSHConnection):
- def serviceStarted(self):
- self.openChannel(SSHSession())
-
-
- class SSHSession(channel.SSHChannel):
-
- name: bytes = b"session"
- stderr: TextIO = sys.stderr
-
- def channelOpen(self, foo):
- log.msg("session %s open" % self.id)
- if self.conn.options["subsystem"].startswith("/"):
- request = "exec"
- else:
- request = "subsystem"
- d = self.conn.sendRequest(
- self, request, common.NS(self.conn.options["subsystem"]), wantReply=1
- )
- d.addCallback(self._cbSubsystem)
- d.addErrback(_ebExit)
-
- def _cbSubsystem(self, result):
- self.client = filetransfer.FileTransferClient()
- self.client.makeConnection(self)
- self.dataReceived = self.client.dataReceived
- f = None
- if self.conn.options["batchfile"]:
- fn = self.conn.options["batchfile"]
- if fn != "-":
- f = open(fn)
- self.stdio = stdio.StandardIO(StdioClient(self.client, f))
-
- def extReceived(self, t: int, data: bytes) -> None:
- if t == connection.EXTENDED_DATA_STDERR:
- log.msg("got %s stderr data" % len(data))
- # RFC 4251
- # ========
- # Strings are also used to store text. In that case, US-ASCII is
- # used for internal names, and ISO-10646 UTF-8 for text that might
- # be displayed to the user. The terminating null character SHOULD
- # NOT normally be stored in the string. For example: the US-ASCII
- # string "testing" is represented as 00 00 00 07 t e s t i n g.
- # The UTF-8 mapping does not alter the encoding of US-ASCII
- # characters.
- #
- # RFC 4254
- # ========
- # Additionally, some channels can transfer several types of data. An
- # example of this is stderr data from interactive sessions. Such data
- # can be passed with SSH_MSG_CHANNEL_EXTENDED_DATA messages, where a
- # separate integer specifies the type of data. The available types and
- # their interpretation depend on the type of channel.
- #
- # byte SSH_MSG_CHANNEL_EXTENDED_DATA
- # uint32 recipient channel
- # uint32 data_type_code
- # string data
- #
- # Data sent with these messages consumes the same window as ordinary
- # data.
- #
- # Currently, only the following type is defined. Note that the value
- # for the 'data_type_code' is given in decimal format for readability,
- # but the values are actually uint32 values.
- #
- # Symbolic name data_type_code
- # ------------- --------------
- # SSH_EXTENDED_DATA_STDERR 1
- #
- # (end of RFC quotations)
- #
- # Here we decode the stderr bytes as UTF-8 and handle errors by
- # representing undecodeable bytes with a certain escape scheme.
- # There is no guarantee that the peer is sending UTF-8 encoded
- # bytes but if they are not it is complex to determine what
- # encoding they _are_ sending. The standard says nothing about
- # how these bytes should be decoded because the standard probably
- # doesn't think they should be decoded at all - just handle them
- # as bytes! However, our stderr is a text-mode file so we *must*
- # decode them to be able to write them out at all. And even if we
- # had a binary-mode file we would still /probably/ want to write
- # bytes in a *known* encoding to it.
- #
- # Perhaps in the future we can somehow inspect LANG or LC_* in the
- # remote execution environment (but I'm not sure how) and use that
- # as a hint about which encoding to use for decoding here.
- # Meanwhile, UTF-8 is the de facto universal interoperable
- # encoding so: use it.
- self.stderr.write(data.decode("utf-8", "backslashreplace"))
- self.stderr.flush()
-
- def eofReceived(self):
- log.msg("got eof")
- self.stdio.loseWriteConnection()
-
- def closeReceived(self):
- log.msg("remote side closed %s" % self)
- self.conn.sendClose(self)
-
- def closed(self):
- try:
- reactor.stop()
- except BaseException:
- pass
-
- def stopWriting(self):
- self.stdio.pauseProducing()
-
- def startWriting(self):
- self.stdio.resumeProducing()
-
-
- if __name__ == "__main__":
- run()
|