#!/usr/bin/env python # client configuration nickname = 'anon' delpass = 'lofasz' handler = 'http://port70.net/cgi/post.cgi' editor = 'vim "+set tw=70" -c startinsert' # # below, the urllib2_file library is embedded # got it from: http://fabien.seisen.org/python/urllib2_multipart.html # ############################# # # Version: 0.2.0 # - UTF-8 filenames are now allowed (Eli Golovinsky)
# - File object is no more mandatory, Object only needs to have seek() read() attributes (Eli Golovinsky)
# # Version: 0.1.0 # - upload is now done with chunks (Adam Ambrose) # # Version: older # THANKS TO: # bug fix: kosh @T aesaeion.com # HTTPS support : Ryan Grow # Copyright (C) 2004,2005,2006 Fabien SEISEN # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # you can contact me at: # http://fabien.seisen.org/python/ # # Also modified by Adam Ambrose (aambrose @T pacbell.net) to write data in # chunks (hardcoded to CHUNK_SIZE for now), so the entire contents of the file # don't need to be kept in memory. # """ enable to upload files using multipart/form-data idea from: upload files in python: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306 timeoutsocket.py: overriding Python socket API: http://www.timo-tasi.org/python/timeoutsocket.py http://mail.python.org/pipermail/python-announce-list/2001-December/001095.html import urllib2_files import urllib2 u = urllib2.urlopen('http://site.com/path' [, data]) data can be a mapping object or a sequence of two-elements tuples (like in original urllib2.urlopen()) varname still need to be a string and value can be string of a file object eg: ((varname, value), (varname2, value), ) or { name: value, name2: value2 } """ import os import socket import sys import stat import mimetypes import mimetools import httplib import urllib import urllib2 CHUNK_SIZE = 65536 def get_content_type(filename): return mimetypes.guess_type(filename)[0] or 'application/octet-stream' # if sock is None, juste return the estimate size def send_data(v_vars, v_files, boundary, sock=None): l = 0 for (k, v) in v_vars: buffer='' buffer += '--%s\r\n' % boundary buffer += 'Content-Disposition: form-data; name="%s"\r\n' % k buffer += '\r\n' buffer += v + '\r\n' if sock: sock.send(buffer) l += len(buffer) for (k, v) in v_files: fd = v file_size = os.fstat(fd.fileno())[stat.ST_SIZE] name = fd.name.split('/')[-1] if isinstance(name, unicode): name = name.encode('UTF-8') buffer='' buffer += '--%s\r\n' % boundary buffer += 'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' \ % (k, name) buffer += 'Content-Type: %s\r\n' % get_content_type(name) buffer += 'Content-Length: %s\r\n' % file_size buffer += '\r\n' l += len(buffer) if sock: sock.send(buffer) if hasattr(fd, 'seek'): fd.seek(0) while True: chunk = fd.read(CHUNK_SIZE) if not chunk: break sock.send(chunk) l += file_size buffer='\r\n' buffer += '--%s--\r\n' % boundary buffer += '\r\n' if sock: sock.send(buffer) l += len(buffer) return l # mainly a copy of HTTPHandler from urllib2 class newHTTPHandler(urllib2.BaseHandler): def http_open(self, req): return self.do_open(httplib.HTTP, req) def do_open(self, http_class, req): data = req.get_data() v_files=[] v_vars=[] # mapping object (dict) if req.has_data() and type(data) != str: if hasattr(data, 'items'): data = data.items() else: try: if len(data) and not isinstance(data[0], tuple): raise TypeError except TypeError: ty, va, tb = sys.exc_info() raise TypeError, "not a valid non-string sequence or mapping object", tb for (k, v) in data: if hasattr(v, 'read'): v_files.append((k, v)) else: v_vars.append( (k, v) ) # no file ? convert to string if len(v_vars) > 0 and len(v_files) == 0: data = urllib.urlencode(v_vars) v_files=[] v_vars=[] host = req.get_host() if not host: raise urllib2.URLError('no host given') h = http_class(host) # will parse host:port if req.has_data(): h.putrequest('POST', req.get_selector()) if not 'Content-type' in req.headers: if len(v_files) > 0: boundary = mimetools.choose_boundary() l = send_data(v_vars, v_files, boundary) h.putheader('Content-Type', 'multipart/form-data; boundary=%s' % boundary) h.putheader('Content-length', str(l)) else: h.putheader('Content-type', 'application/x-www-form-urlencoded') if not 'Content-length' in req.headers: h.putheader('Content-length', '%d' % len(data)) else: h.putrequest('GET', req.get_selector()) scheme, sel = urllib.splittype(req.get_selector()) sel_host, sel_path = urllib.splithost(sel) h.putheader('Host', sel_host or host) for name, value in self.parent.addheaders: name = name.capitalize() if name not in req.headers: h.putheader(name, value) for k, v in req.headers.items(): h.putheader(k, v) # httplib will attempt to connect() here. be prepared # to convert a socket error to a URLError. try: h.endheaders() except socket.error, err: raise urllib2.URLError(err) if req.has_data(): if len(v_files) >0: l = send_data(v_vars, v_files, boundary, h) elif len(v_vars) > 0: # if data is passed as dict ... data = urllib.urlencode(v_vars) h.send(data) else: # "normal" urllib2.urlopen() h.send(data) code, msg, hdrs = h.getreply() fp = h.getfile() if code == 200: resp = urllib.addinfourl(fp, hdrs, req.get_full_url()) resp.code = code resp.msg = msg return resp else: return self.parent.error('http', req, fp, code, msg, hdrs) urllib2._old_HTTPHandler = urllib2.HTTPHandler urllib2.HTTPHandler = newHTTPHandler class newHTTPSHandler(newHTTPHandler): def https_open(self, req): return self.do_open(httplib.HTTPS, req) urllib2.HTTPSHandler = newHTTPSHandler #################################################################### # # Here comes the gopherchan client code. # # if __name__ == '__main__': import getopt import urllib2 import string import sys import os import tempfile # print short usage instructions def usage(progname): print """ Usage: %s [-n nickname] [-d delpass] [-m message] [-f file] [-k killpostno] (-b board | -t threadname) """ % progname board = False message = False file = False thread = False kill = False # read command line args try: opts, args = getopt.getopt(sys.argv[1:], 'hn:d:m:f:b:k:t:M:') except getopt.GetoptError, errmsg: print "ERROR:", errmsg sys.exit(1) for name, value in opts: if name == '-h': usage(sys.argv[0]) sys.exit(0) elif name == '-n': nickname = value elif name == '-d': delpass = value elif name == '-m': message = value elif name == '-f': file = value elif name == '-b': board = value elif name == '-k': kill = value elif name == '-t': thread = value elif name == '-M': try: fn = open(value, 'r') message = fn.read() fn.close() except IOError: print "Failed to read message from file" sys.exit(1) else: print "invalid argument:", name sys.exit(2) if(args != []): print "Superfluous arguments" sys.exit(2) if(kill != False): if(board == False): print "To kill a post, you must specify the board as well" sys.exit(1) if(thread == False and board == False): print "Either the thread or the board has to be specified." usage(sys.argv[0]) sys.exit(1) if(thread != False and board != False): print "The thread and the board can't be both specified at the same time" sys.exit(1) # open image file fileobj = False if(file != False): try: fileobj = open(file, 'rb') except IOError: print "Failed to open image file." # launch external editor to edit message if(message == False and kill == False): tf = tempfile.mkstemp('.txt', 'chan-'); if(os.system(editor + " '" + tf[1] + "'") != 0): print "Calling the editor failed:", editor + " '" + tf[1] + "'" sys.exit(1) mf = open(tf[1], 'r') message = mf.read().strip() mf.close() if(message == ''): print "Empty message, aborted" sys.exit(1) os.unlink(tf[1]) try: opts = { 'author': nickname, 'pass': delpass, 'text': message, 'board': '', 'thread': '' } if(fileobj != False): opts['image'] = fileobj if(board != False): opts['board'] = board if(thread != False): opts['thread'] = thread if(kill != False): opts['kill'] = kill u = urllib2.urlopen(handler, opts) except urllib2.HTTPError, errobj: print "HTTP error:", errobj.code else: print u.read()