#!/usr/bin/env python from __future__ import print_function import argparse import base64 import errno import json import os import platform import random import socket import string import subprocess import sys import tempfile import time import uuid try: import httplib except ImportError: import http.client as httplib try: import thread except ImportError: import _thread as thread try: from urllib2 import quote as urllib_quote except ImportError: from urllib.parse import quote as urllib_quote try: from urlparse import urlparse except ImportError: from urllib.parse import urlparse try: input_func = raw_input except NameError: input_func = input if platform.system() == 'Windows': try: import urllib.request as url_req # for Python 3 except ImportError: import urllib as url_req # for Python 2 class NotAuthorizedException(Exception): pass class RequestTooLargeException(Exception): pass def id_generator(size=18, chars=string.ascii_letters + string.digits): return ''.join(random.choice(chars) for x in range(size)) def terminal_size(): cols = subprocess.Popen('tput cols', shell=True, stdout=subprocess.PIPE) rows = subprocess.Popen('tput lines', shell=True, stdout=subprocess.PIPE) cols = int(cols.stdout.read().strip()) rows = int(rows.stdout.read().strip()) return {'cols': cols, 'rows': rows} def post(conn, url, message, room, password): is_successful = lambda status: status >= 200 and status < 300 headers = {'Content-type': 'application/json', 'Authorization': password} data = json.dumps({'message': message, 'size': terminal_size()}) try: conn.request('POST', '/%s' % room, data, headers) res = conn.getresponse() res.read() if res.status == 401: raise NotAuthorizedException() elif res.status == 413: raise RequestTooLargeException() else: return is_successful(res.status) except httplib.HTTPException: pass except socket.error as e: if e.errno != errno.ECONNREFUSED: raise e def stream_file(f, url, room, password): retries = 3 try: conn = create_connection(url) success = True while success: time.sleep(1) # osx wants this because EOF is cached f.seek(0, os.SEEK_CUR) data = f.read(4096) if not (data == ""): urlencoded = urllib_quote(data).encode('utf-8') encoded_str = base64.b64encode(urlencoded).decode('utf-8') for _ in range(retries): success = post(conn, url, encoded_str, room, password) if success: break else: time.sleep(1) conn = create_connection(url) error('There was an error connecting to the server.') except NotAuthorizedException: error('You\'re not authorized to share on %s/%s.' % (url, room)) except RequestTooLargeException: error('You\'ve wrote too much too fast. Please, slow down.') def create_connection(url): parsed_url = urlparse(url) host = parsed_url.netloc if parsed_url.scheme.lower() == 'https': return httplib.HTTPSConnection(host) else: return httplib.HTTPConnection(host) def error(*args): print('\r\nERROR:', *args, file=sys.stderr) print('\rERROR: Exit shellshare and try again later.', file=sys.stderr) def delete(url, room, password): headers = {'Authorization': password} try: conn = create_connection(url) conn.request('DELETE', '/%s' % room, {}, headers) res = conn.getresponse() res.read() except Exception: pass def parse_args(): description = 'Transmits the current shell to shellshare' parser = argparse.ArgumentParser(description=description) parser.add_argument('-v', '--version', action='version', version='%(prog)s 1.1.0') parser.add_argument('-s', '--server', dest='server', help=('shellshare instance URL' ' (default: https://shellshare.net)'), default='https://shellshare.net') parser.add_argument('-r', '--room', dest='room', help='room to share into (default: random room)', default=id_generator()) parser.add_argument('-p', '--password', dest='password', help='room\'s broadcasting password (default: network card\'s MAC address)', default=uuid.getnode()) args = parser.parse_args() if not urlparse(args.server).scheme: args.server = 'http://' + args.server args.server = args.server.strip('/') return args args = parse_args() room = 'r/%s' % args.room if platform.system() == 'Darwin': shell_args = '-qt 0' else: shell_args = '-qf' if platform.system() == 'Windows': # Download and install MSYS2 version of script bindir = os.path.join(os.path.expanduser('~'), '.shellshare') if not os.path.exists(bindir): os.mkdir(bindir) script_path = os.path.join(bindir, 'script.exe') if not os.path.exists(script_path): script_url = '{0}/bin/script.exe'.format(args.server) should_download = input_func( 'Confirm download of dependency from {} into {}? (Y/n) '.format( script_url, script_path ) ) if should_download.lower() not in {'', 'y', 'yes'}: exit(0) url_req.urlretrieve(script_url, script_path) else: # Use OS version of script script_path = 'script' size = terminal_size() if (size['rows'] > 30 or size['cols'] > 160): print('Current terminal size is %dx%d.' % (size['rows'], size['cols'])) print('It\'s too big to be viewed on smaller screens.') print('You can resize it anytime.') print('Sharing terminal in %s/%s' % (args.server, room)) sys.stdout.flush() with tempfile.NamedTemporaryFile(mode='r+b') as tmp: thread.start_new_thread(stream_file, (tmp, args.server, room, args.password)) subprocess.call('%s %s %s' % (script_path, shell_args, tmp.name), shell=True) delete(args.server, room, args.password) print('End of transmission.')