• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Gallopsled / pwntools / 1

28 Jan 2022 10:58AM UTC coverage: 0.0% (-73.8%) from 73.823%
1

push

github

web-flow
Fix CI after Groovy Gorilla went away for libc unstrip test (#2025)

* Fix CI after Groovy Gorilla went away for libc unstrip test

Build elfutils 0.181 from source since we can't use builds
from a newer ubuntu version anymore.

* Install python wheels in CI

0 of 1 new or added line in 1 file covered. (0.0%)

13713 existing lines in 142 files now uncovered.

0 of 16559 relevant lines covered (0.0%)

0.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/pwnlib/filesystem/ssh.py
1
# -*- coding: utf-8 -*-
2
"""
3
Handles file abstraction for remote SSH files
4

5
Emulates pathlib as much as possible, but does so through duck typing.
6
"""
UNCOV
7
import os
×
UNCOV
8
import six
×
UNCOV
9
import sys
×
UNCOV
10
import tempfile
×
UNCOV
11
import time
×
12

UNCOV
13
from pwnlib.context import context
×
UNCOV
14
from pwnlib.util.misc import read, write
×
UNCOV
15
from pwnlib.util.packing import _encode, _decode
×
16

UNCOV
17
if six.PY3:
×
UNCOV
18
    from pathlib import *
×
19
else:
UNCOV
20
    from pathlib2 import *
×
21

UNCOV
22
class SSHPath(PosixPath):
×
23
    r"""Represents a file that exists on a remote filesystem.
24

25
    See :class:`.ssh` for more information on how to set up an SSH connection.
26
    See :py:class:`pathlib.Path` for documentation on what members and
27
    properties this object has.
28

29
    Arguments:
30
        name(str): Name of the file
31
        ssh(ssh): :class:`.ssh` object for manipulating remote files
32

33
    Note:
34

35
        You can avoid having to supply ``ssh=`` on every ``SSHPath`` by setting
36
        :data:`.context.ssh_session`.  
37
        In these examples we provide ``ssh=`` for clarity.
38

39
    Examples:
40

41
        First, create an SSH connection to the server.
42

43
        >>> ssh_conn = ssh('travis', 'example.pwnme')
44

45
        Let's use a temporary directory for our tests
46
    
47
        >>> _ = ssh_conn.set_working_directory()
48

49
        Next, you can create SSHPath objects to represent the paths to files
50
        on the remote system.
51

52
        >>> f = SSHPath('filename', ssh=ssh_conn)
53
        >>> f.touch()
54
        >>> f.exists()
55
        True
56
        >>> f.resolve().path # doctests: +ELLIPSIS
57
        '/tmp/.../filename'
58
        >>> f.write_text('asdf ❤️')
59
        >>> f.read_bytes()
60
        b'asdf \xe2\x9d\xa4\xef\xb8\x8f'
61

62
        ``context.ssh_session`` must be set to use the :meth:`.SSHPath.mktemp`
63
        or :meth:`.SSHPath.mkdtemp` methods.
64

65
        >>> context.ssh_session = ssh_conn
66
        >>> SSHPath.mktemp() # doctest: +ELLIPSIS
67
        SSHPath('...', ssh=ssh(user='travis', host='127.0.0.1'))
68
    """
69

UNCOV
70
    sep = '/'
×
71

UNCOV
72
    def __init__(self, path, ssh=None):
×
73
        self.path = self._s(path)
×
UNCOV
74
        self.ssh = ssh or context.ssh_session
×
75

UNCOV
76
        if self.ssh is None:
×
UNCOV
77
            raise ValueError('SSHPath requires an ssh session.  Provide onee or set context.ssh_session.')
×
78

UNCOV
79
    def _s(self, other):
×
80
        # We want strings
UNCOV
81
        if isinstance(other, str):
×
UNCOV
82
            return other
×
83

84
        # We don't want unicode
UNCOV
85
        if isinstance(other, six.text_type):
×
UNCOV
86
            return str(other)
×
87

88
        # We also don't want binary
UNCOV
89
        if isinstance(other, six.binary_type):
×
UNCOV
90
            return str(_decode(other))
×
91

UNCOV
92
    def _new(self, path, *a, **kw):
×
UNCOV
93
        kw['ssh'] = self.ssh
×
UNCOV
94
        path = self._s(path)
×
UNCOV
95
        return SSHPath(path, *a, **kw)
×
96

97
    def _run(self, *a, **kw):
×
UNCOV
98
        with context.silent:
×
UNCOV
99
            return self.ssh.run(*a, **kw)
×
100

101
#---------------------------------- PUREPATH ----------------------------------
UNCOV
102
    def __str__(self):
×
103
        return self.path
×
104

UNCOV
105
    def __fspath__(self):
×
UNCOV
106
        return str(self)
×
107

UNCOV
108
    def as_posix(self):
×
109
        return self.path
×
110

UNCOV
111
    def __bytes__(self):
×
UNCOV
112
        return os.fsencode(self)
×
113

UNCOV
114
    def __repr__(self):
×
UNCOV
115
        return "{}({!r}, ssh={!r})".format(self.__class__.__name__, self.as_posix(), self.ssh)
×
116

UNCOV
117
    def as_uri(self):
×
UNCOV
118
        raise NotImplementedError()
×
119

UNCOV
120
    def __eq__(self, other):
×
UNCOV
121
        if not isinstance(other, SSHPath):
×
UNCOV
122
            return str(self) == str(other)
×
123

UNCOV
124
        if self.ssh.host != other.ssh.host:
×
UNCOV
125
            return False
×
126

UNCOV
127
        if self.path != other.path:
×
UNCOV
128
            return False
×
129

UNCOV
130
        return True
×
131

132
    def __hash__(*a, **kw): ""; raise NotImplementedError
133
    def __lt__(*a, **kw): ""; raise NotImplementedError
134
    def __le__(*a, **kw): ""; raise NotImplementedError
135
    def __gt__(*a, **kw): ""; raise NotImplementedError
136
    def __ge__(*a, **kw): ""; raise NotImplementedError
137

UNCOV
138
    @property
×
UNCOV
139
    def anchor(self):
×
UNCOV
140
        raise NotImplementedError()
×
141

UNCOV
142
    @property
×
UNCOV
143
    def name(self):
×
144
        """Returns the name of the file.
145

146
        >>> f = SSHPath('hello', ssh=ssh_conn)
147
        >>> f.name
148
        'hello'
149
        """
UNCOV
150
        return os.path.basename(self.path)
×
151

152
    @property
×
UNCOV
153
    def suffix(self):
×
154
        """Returns the suffix of the file.
155

156
        >>> f = SSHPath('hello.tar.gz', ssh=ssh_conn)
157
        >>> f.suffix
158
        '.gz'
159
        """
UNCOV
160
        if '.' not in self.name:
×
UNCOV
161
            return ''
×
UNCOV
162
        return self.name[self.name.rindex('.'):]
×
163

UNCOV
164
    @property
×
UNCOV
165
    def suffixes(self):
×
166
        """Returns the suffixes of a file
167

168
        >>> f = SSHPath('hello.tar.gz', ssh=ssh_conn)
169
        >>> f.suffixes
170
        '.tar.gz'
171
        """
172

UNCOV
173
        basename = self.name
×
UNCOV
174
        if '.' not in basename:
×
UNCOV
175
            return ''
×
UNCOV
176
        return '.' + self.name.split('.', 1)[1]
×
177

178
    @property
×
UNCOV
179
    def stem(self):
×
180
        """Returns the stem of a file without any extension
181
        
182
        >>> f = SSHPath('hello.tar.gz', ssh=ssh_conn)
183
        >>> f.stem
184
        'hello'
185
        """
UNCOV
186
        if '.' not in self.name:
×
UNCOV
187
            return self.name
×
UNCOV
188
        return self.name[:self.name.index('.')]
×
189

UNCOV
190
    def with_name(self, name):
×
191
        """Return a new path with the file name changed
192

193
        >>> f = SSHPath('hello/world', ssh=ssh_conn)
194
        >>> f.path
195
        'hello/world'
196
        >>> f.with_name('asdf').path
197
        'hello/asdf'
198
        """
UNCOV
199
        if '/' not in self.path:
×
UNCOV
200
            return name
×
201

UNCOV
202
        path, _ = self.path.split(self.sep, 1)
×
UNCOV
203
        path = self._new(path)
×
UNCOV
204
        return path.joinpath(name)
×
205

UNCOV
206
    def with_stem(self, name):
×
207
        """Return a new path with the stem changed.
208

209
        >>> f = SSHPath('hello/world.tar.gz', ssh=ssh_conn)
210
        >>> f.with_stem('asdf').path
211
        'hello/asdf.tar.gz'
212
        """
UNCOV
213
        return self.with_name(name + self.suffixes)
×
214

UNCOV
215
    def with_suffix(self, suffix):
×
216
        """Return a new path with the file suffix changed
217

218
        >>> f = SSHPath('hello/world.tar.gz', ssh=ssh_conn)
219
        >>> f.with_suffix('.tgz').path
220
        'hello/world.tgz'
221
        """
UNCOV
222
        return self.with_name(self.stem + suffix)
×
223

UNCOV
224
    def relative_to(self, *other):
×
UNCOV
225
        raise NotImplementedError()
×
226

UNCOV
227
    def is_relative_to(self, *other):
×
UNCOV
228
        raise NotImplementedError()       
×
229

UNCOV
230
    @property
×
UNCOV
231
    def parts(self):
×
232
        """Return the individual parts of the path
233
    
234
        >>> f = SSHPath('hello/world.tar.gz', ssh=ssh_conn)
235
        >>> f.parts
236
        ['hello', 'world.tar.gz']
237
        """
UNCOV
238
        return self.path.split(self.sep)
×
239

UNCOV
240
    def joinpath(self, *args):
×
241
        """Combine this path with one or several arguments.
242

243
        >>> f = SSHPath('hello', ssh=ssh_conn)
244
        >>> f.joinpath('world').path
245
        'hello/world'
246
        """
UNCOV
247
        newpath = os.path.join(self.path, *args)
×
UNCOV
248
        return SSHPath(newpath, ssh=self.ssh)
×
249
    
250
    # __truediv__
251
    # __rtruediv__
252

UNCOV
253
    @property
×
UNCOV
254
    def parent(self):
×
255
        """Return the parent of this path
256

257
        >>> f = SSHPath('hello/world/file.txt', ssh=ssh_conn)
258
        >>> f.parent.path
259
        'hello/world'
260
        """
UNCOV
261
        a, b = self.path.rsplit(self.sep, 1)
×
UNCOV
262
        if a:
×
UNCOV
263
            return self._new(a)
×
UNCOV
264
        return self
×
265

266
    @property
×
UNCOV
267
    def parents(self):
×
268
        """Return the parents of this path, as individual parts
269

270
        >>> f = SSHPath('hello/world/file.txt', ssh=ssh_conn)
271
        >>> list(p.path for p in f.parents)
272
        ['hello', 'world']
273
        """
UNCOV
274
        if '/' not in self.path:
×
UNCOV
275
            return self._new('.')
×
276

UNCOV
277
        return [self._new(p) for p in self.parent.path.split(self.sep)]
×
278

UNCOV
279
    def is_absolute(self):
×
280
        """Returns whether a path is absolute or not.
281

282
        >>> f = SSHPath('hello/world/file.txt', ssh=ssh_conn)
283
        >>> f.is_absolute()
284
        False
285

286
        >>> f = SSHPath('/hello/world/file.txt', ssh=ssh_conn)
287
        >>> f.is_absolute()
288
        True
289
        """
UNCOV
290
        return self.path.startswith(self.sep)
×
291

UNCOV
292
    def is_reserved(self):
×
293
        return False
×
294

UNCOV
295
    def match(self, path_pattern):
×
UNCOV
296
        raise NotImplementedError()
×
297

298
#------------------------------------ PATH ------------------------------------
299

UNCOV
300
    @property
×
UNCOV
301
    def cwd(self):
×
UNCOV
302
        return self._new(self.ssh.cwd)
×
303

UNCOV
304
    @property
×
UNCOV
305
    def home(self):
×
306
        """Returns the home directory for the SSH connection
307

308
        >>> f = SSHPath('...', ssh=ssh_conn)
309
        >>> f.home # doctest: +ELLIPSIS
310
        SSHPath('/home/...', ssh=ssh(user='...', host='127.0.0.1'))
311
        """
UNCOV
312
        path = self._run('echo ~').recvall().rstrip()
×
UNCOV
313
        return self._new(path)
×
314

UNCOV
315
    def samefile(self, other_path):
×
316
        """Returns whether two files are the same
317

318
        >>> a = SSHPath('a', ssh=ssh_conn)
319
        >>> A = SSHPath('a', ssh=ssh_conn)
320
        >>> x = SSHPath('x', ssh=ssh_conn)
321

322
        >>> a.samefile(A)
323
        True
324
        >>> a.samefile(x)
325
        False
326
        """
UNCOV
327
        if not isinstance(other_path, SSHPath):
×
UNCOV
328
            return False
×
329

UNCOV
330
        return self.absolute() == other_path.absolute()
×
331

UNCOV
332
    def iterdir(self):
×
333
        """Iterates over the contents of the directory
334

335
        >>> directory = SSHPath('iterdir', ssh=ssh_conn)
336
        >>> directory.mkdir()
337
        >>> fileA = directory.joinpath('fileA')
338
        >>> fileA.touch()
339
        >>> fileB = directory.joinpath('fileB')
340
        >>> fileB.touch()
341
        >>> dirC = directory.joinpath('dirC')
342
        >>> dirC.mkdir()
343
        >>> [p.name for p in directory.iterdir()]
344
        ['dirC', 'fileA', 'fileB']
345
        """
UNCOV
346
        for directory in sorted(self.ssh.sftp.listdir(self.path)):
×
UNCOV
347
            yield self._new(directory)
×
348

UNCOV
349
    def glob(self, pattern):
×
UNCOV
350
        raise NotImplementedError()
×
351

UNCOV
352
    def rglob(self, pattern):
×
UNCOV
353
        raise NotImplementedError()
×
354

UNCOV
355
    def absolute(self):
×
356
        """Return the absolute path to a file, preserving e.g. "../".
357
        The current working directory is determined via the :class:`.ssh`
358
        member :attr:`.ssh.cwd`.
359

360
        Example:
361
            
362
            >>> f = SSHPath('absA/../absB/file', ssh=ssh_conn)
363
            >>> f.absolute().path # doctest: +ELLIPSIS
364
            '/.../absB/file'
365
        """
UNCOV
366
        path = os.path.normpath(self.path)
×
367

UNCOV
368
        if self.is_absolute():
×
UNCOV
369
            return self._new(path)
×
370

UNCOV
371
        return self._new(os.path.join(self.ssh.cwd, path))
×
372

UNCOV
373
    def resolve(self, strict=False):
×
374
        """Return the absolute path to a file, resolving any '..' or symlinks.
375
        The current working directory is determined via the :class:`.ssh`
376
        member :attr:`.ssh.cwd`.
377

378
        Note:
379

380
            The file must exist to call resolve().
381

382
        Examples:
383

384
            >>> f = SSHPath('resA/resB/../resB/file', ssh=ssh_conn)
385

386
            >>> f.resolve().path # doctest: +ELLIPSIS
387
            Traceback (most recent call last):
388
            ...
389
            ValueError: Could not normalize path: '/.../resA/resB/file'
390

391
            >>> f.parent.absolute().mkdir(parents=True)
392
            >>> list(f.parent.iterdir())
393
            []
394

395
            >>> f.touch()
396
            >>> f.resolve() # doctest: +ELLIPSIS
397
            SSHPath('/.../resA/resB/file', ssh=ssh(user='...', host='127.0.0.1'))
398
        """
UNCOV
399
        path = self.absolute().path
×
UNCOV
400
        path = os.path.normpath(path)
×
401

UNCOV
402
        if six.PY2:
×
UNCOV
403
            error_type = IOError
×
404
        else:
UNCOV
405
            error_type = FileNotFoundError
×
406

UNCOV
407
        try:
×
UNCOV
408
            return self._new(self.ssh.sftp.normalize(path))
×
UNCOV
409
        except error_type as e:
×
UNCOV
410
            raise ValueError("Could not normalize path: %r" % path)
×
411

412
    def stat(self):
×
413
        """Returns the permissions and other information about the file
414

415
        >>> f = SSHPath('filename', ssh=ssh_conn)
416
        >>> f.touch()
417
        >>> stat = f.stat()
418
        >>> stat.st_size
419
        0
420
        >>> '%o' % stat.st_mode # doctest: +ELLIPSIS
421
        '...664'
422
        """
UNCOV
423
        return self.ssh.sftp.stat(self.path)
×
424

UNCOV
425
    def owner(self):
×
UNCOV
426
        raise NotImplementedError()
×
427

UNCOV
428
    def group(self):
×
UNCOV
429
        raise NotImplementedError()
×
430

UNCOV
431
    def open(self, *a, **kw):
×
432
        """Return a file-like object for this path.
433

434
        This currently seems to be broken in Paramiko.
435

436
        >>> f = SSHPath('filename', ssh=ssh_conn)
437
        >>> f.write_text('Hello')
438
        >>> fo = f.open(mode='r+')
439
        >>> fo                      # doctest: +ELLIPSIS
440
        <paramiko.sftp_file.SFTPFile object at ...>
441
        >>> fo.read('asdfasdf')     # doctest: +SKIP
442
        b'Hello'
443
        """
UNCOV
444
        return self.ssh.sftp.open(self.path, *a, **kw)
×
445

UNCOV
446
    def read_bytes(self):
×
447
        """Read bytes from the file at this path
448

449
        >>> f = SSHPath('/etc/passwd', ssh=ssh_conn)
450
        >>> f.read_bytes()[:10]
451
        b'root:x:0:0'
452
        """
UNCOV
453
        return self.ssh.read(str(self.absolute()))
×
454

UNCOV
455
    def read_text(self):
×
456
        """Read text from the file at this path
457

458
        >>> f = SSHPath('/etc/passwd', ssh=ssh_conn)
459
        >>> f.read_text()[:10]
460
        'root:x:0:0'
461
        """
UNCOV
462
        return self._s(self.read_bytes())
×
463

UNCOV
464
    def write_bytes(self, data):
×
465
        r"""Write bytes to the file at this path
466

467
        >>> f = SSHPath('somefile', ssh=ssh_conn)
468
        >>> f.write_bytes(b'\x00HELLO\x00')
469
        >>> f.read_bytes()
470
        b'\x00HELLO\x00'
471
        """
UNCOV
472
        self.ssh.write(str(self.absolute()), data)
×
473

474
    def write_text(self, data):
×
475
        r"""Write text to the file at this path
476

477
        >>> f = SSHPath('somefile', ssh=ssh_conn)
478
        >>> f.write_text("HELLO 😭")
479
        >>> f.read_bytes()
480
        b'HELLO \xf0\x9f\x98\xad'
481
        >>> f.read_text()
482
        'HELLO 😭'
483
        """
UNCOV
484
        data = _encode(data)
×
UNCOV
485
        self.write_bytes(data)
×
486

UNCOV
487
    def readlink(self):
×
UNCOV
488
        data = self.ssh.readlink(self.path)
×
UNCOV
489
        if data == b'':
×
UNCOV
490
            data = self.path
×
UNCOV
491
        return self._new(data)
×
492

UNCOV
493
    def touch(self):
×
494
        """Touch a file (i.e. make it exist)
495

496
        >>> f = SSHPath('touchme', ssh=ssh_conn)
497
        >>> f.exists()
498
        False
499
        >>> f.touch()
500
        >>> f.exists()
501
        True
502
        """
UNCOV
503
        self.ssh.write(self.path, b'')
×
504
        # self.ssh.sftp.truncate(self.path, 0)
505

UNCOV
506
    def mkdir(self, mode=0o777, parents=False, exist_ok=True):
×
507
        r"""Make a directory at the specified path
508

509
        >>> f = SSHPath('dirname', ssh=ssh_conn)
510
        >>> f.mkdir()
511
        >>> f.exists()
512
        True
513

514
        >>> f = SSHPath('dirA/dirB/dirC', ssh=ssh_conn)
515
        >>> f.mkdir(parents=True)
516
        >>> ssh_conn.run(['ls', '-la', f.absolute().path], env={'LC_ALL': 'C.UTF-8'}).recvline()
517
        b'total 8\n'
518
        """
UNCOV
519
        if exist_ok and self.is_dir():
×
UNCOV
520
            return
×
521

522
        if not parents:
×
UNCOV
523
            self.ssh.sftp.mkdir(self.path, mode=mode)
×
UNCOV
524
            return
×
525

UNCOV
526
        if not self.is_absolute():
×
UNCOV
527
            path = self._new(self.ssh.cwd)
×
528
        else:
529
            path = self._new('/')
×
530

UNCOV
531
        parts = self.path.split(self.sep)
×
532

UNCOV
533
        for part in parts:
×
534
            # Catch against common case, need to normalize path
UNCOV
535
            if part == '..':
×
UNCOV
536
                raise ValueError("Cannot create directory '..'")
×
537

UNCOV
538
            path = path.joinpath(part)
×
539

540
            # Don't create directories that already exist            
UNCOV
541
            try:
×
UNCOV
542
                path.mkdir(mode=mode)
×
UNCOV
543
            except OSError:
×
UNCOV
544
                raise OSError("Could not create directory %r" % path)
×
545

546
    def chmod(self, mode):
×
547
        """Change the permissions of a file
548

549
        >>> f = SSHPath('chmod_me', ssh=ssh_conn)
550
        >>> f.touch() # E
551
        >>> '0o%o' % f.stat().st_mode
552
        '0o100664'
553
        >>> f.chmod(0o777)
554
        >>> '0o%o' % f.stat().st_mode
555
        '0o100777'
556
        """
UNCOV
557
        self.ssh.sftp.chmod(self.path, mode)
×
558

UNCOV
559
    def lchmod(*a, **kw):
×
UNCOV
560
        raise NotImplementedError()
×
561

UNCOV
562
    def unlink(self, missing_ok=False):
×
563
        """Remove an existing file.
564

565
        TODO:
566

567
            This test fails catastrophically if the file name is unlink_me
568
            (note the underscore)
569

570
        Example:
571

572
            >>> f = SSHPath('unlink_me', ssh=ssh_conn)
573
            >>> f.exists()
574
            False
575
            >>> f.touch()
576
            >>> f.exists()
577
            True
578
            >>> f.unlink()
579
            >>> f.exists()
580
            False
581

582
            Note that unlink only works on files.
583

584
            >>> f.mkdir()
585
            >>> f.unlink()
586
            Traceback (most recent call last):
587
            ...
588
            ValueError: Cannot unlink SSHPath(...)): is not a file
589
        """
UNCOV
590
        try:
×
UNCOV
591
            self.ssh.sftp.remove(str(self))
×
UNCOV
592
        except (IOError, OSError) as e:
×
UNCOV
593
            if self.exists() and not self.is_file():
×
UNCOV
594
                raise ValueError("Cannot unlink %r: is not a file" % self)
×
UNCOV
595
            if not missing_ok:
×
UNCOV
596
                raise e
×
597

598
    def rmdir(self):
×
599
        """Remove an existing directory.
600

601
        Example:
602

603
            >>> f = SSHPath('rmdir_me', ssh=ssh_conn)
604
            >>> f.mkdir()
605
            >>> f.is_dir()
606
            True
607
            >>> f.rmdir()
608
            >>> f.exists()
609
            False
610
        """
UNCOV
611
        if not self.exists():
×
UNCOV
612
            return
×
613

UNCOV
614
        if not self.is_dir():
×
UNCOV
615
            raise ValueError("Cannot rmdir %r: not a directory" % self)
×
616

UNCOV
617
        self.ssh.sftp.rmdir(self.path)
×
618

UNCOV
619
    def link_to(self, target):
×
UNCOV
620
        raise NotImplementedError()
×
621

UNCOV
622
    def symlink_to(self, target):
×
623
        r"""Create a symlink at this path to the provided target
624

625
        Todo:
626

627
            Paramiko's documentation is wrong and inverted.
628
            https://github.com/paramiko/paramiko/issues/1821
629

630
        Example:
631

632
            >>> a = SSHPath('link_name', ssh=ssh_conn)
633
            >>> b = SSHPath('link_target', ssh=ssh_conn)
634
            >>> a.symlink_to(b)
635
            >>> a.write_text("Hello")
636
            >>> b.read_text()
637
            'Hello'
638
        """
UNCOV
639
        if isinstance(target, SSHPath):
×
UNCOV
640
            target = target.path
×
641

UNCOV
642
        self.ssh.sftp.symlink(target, self.path)
×
643

UNCOV
644
    def rename(self, target):
×
645
        """Rename a file to the target path
646

647
        Example:
648

649
            >>> a = SSHPath('rename_from', ssh=ssh_conn)
650
            >>> b = SSHPath('rename_to', ssh=ssh_conn)
651
            >>> a.touch()
652
            >>> b.exists()
653
            False
654
            >>> a.rename(b)
655
            >>> b.exists()
656
            True
657
        """
UNCOV
658
        if isinstance(target, SSHPath):
×
UNCOV
659
            target = target.path
×
660

UNCOV
661
        self.ssh.sftp.rename(self.path, target)
×
662

UNCOV
663
    def replace(self, target):
×
664
        """Replace target file with file at this path
665

666
        Example:
667

668
            >>> a = SSHPath('rename_from', ssh=ssh_conn)
669
            >>> a.write_text('A')
670
            >>> b = SSHPath('rename_to', ssh=ssh_conn)
671
            >>> b.write_text('B')
672
            >>> a.replace(b)
673
            >>> b.read_text()
674
            'A'
675
        """
UNCOV
676
        if isinstance(target, SSHPath):
×
UNCOV
677
            target = target.path
×
678

UNCOV
679
        self._new(target).unlink(missing_ok=True)
×
UNCOV
680
        self.rename(target)
×
681

UNCOV
682
    def exists(self):
×
683
        """Returns True if the path exists
684

685
        Example:
686

687
            >>> a = SSHPath('exists', ssh=ssh_conn)
688
            >>> a.exists()
689
            False
690
            >>> a.touch()
691
            >>> a.exists()
692
            True
693
            >>> a.unlink()
694
            >>> a.exists()
695
            False
696
        """
UNCOV
697
        try:
×
UNCOV
698
            self.stat()
×
UNCOV
699
            return True
×
UNCOV
700
        except IOError:
×
UNCOV
701
            return False
×
702

UNCOV
703
    def is_dir(self):
×
704
        """Returns True if the path exists and is a directory
705
        
706
        Example:
707

708
            >>> f = SSHPath('is_dir', ssh=ssh_conn)
709
            >>> f.is_dir()
710
            False
711
            >>> f.touch()
712
            >>> f.is_dir()
713
            False
714
            >>> f.unlink()
715
            >>> f.mkdir()
716
            >>> f.is_dir()
717
            True
718
        """
UNCOV
719
        if not self.exists():
×
UNCOV
720
            return False
×
721

UNCOV
722
        if self.stat().st_mode & 0o040000:
×
UNCOV
723
            return True
×
724

UNCOV
725
        return False
×
726

UNCOV
727
    def is_file(self):
×
728
        """Returns True if the path exists and is a file
729
        
730
        Example:
731

732
            >>> f = SSHPath('is_file', ssh=ssh_conn)
733
            >>> f.is_file()
734
            False
735
            >>> f.touch()
736
            >>> f.is_file()
737
            True
738
            >>> f.unlink()
739
            >>> f.mkdir()
740
            >>> f.is_file()
741
            False
742
        """
UNCOV
743
        if not self.exists():
×
744
            return False
×
745

UNCOV
746
        if self.stat().st_mode & 0o040000:
×
747
            return False
×
748

UNCOV
749
        return True
×
750

UNCOV
751
    def is_symlink(self):
×
UNCOV
752
        raise NotImplementedError()
×
753

UNCOV
754
    def is_block_device(self):
×
UNCOV
755
        raise NotImplementedError()
×
756

UNCOV
757
    def is_char_device(self):
×
UNCOV
758
        raise NotImplementedError()
×
759

UNCOV
760
    def is_fifo(self):
×
UNCOV
761
        raise NotImplementedError()
×
762

UNCOV
763
    def is_socket(self):
×
764
        raise NotImplementedError()
×
765

UNCOV
766
    def expanduser(self):
×
767
        """Expands a path that starts with a tilde
768

769
        Example:
770

771
            >>> f = SSHPath('~/my-file', ssh=ssh_conn)
772
            >>> f.path
773
            '~/my-file'
774
            >>> f.expanduser().path # doctest: +ELLIPSIS
775
            '/home/.../my-file'
776
        """
UNCOV
777
        if not self.path.startswith('~/'):
×
778
            return self
×
779
        
UNCOV
780
        home = self.home
×
UNCOV
781
        subpath = self.path.replace('~/', '')
×
UNCOV
782
        return home.joinpath(subpath)
×
783

784
#----------------------------- PWNTOOLS ADDITIONS -----------------------------
UNCOV
785
    @classmethod
×
UNCOV
786
    def mktemp(cls):
×
UNCOV
787
        temp = _decode(context.ssh_session.mktemp())
×
UNCOV
788
        return SSHPath(temp, ssh=context.ssh_session)
×
789

UNCOV
790
    @classmethod
×
UNCOV
791
    def mkdtemp(self):
×
UNCOV
792
        temp = _decode(context.ssh_session.mkdtemp())
×
UNCOV
793
        return SSHPath(temp, ssh=context.ssh_session)
×
794

UNCOV
795
__all__ = ['SSHPath']
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc