I've made a new version of freenetlib.py.  It is just slightly modified
from version 0.02.  The main differences is that all instances of "FreeNet"
in the module (including the class name) have been changed to Freenet,
which appears to be the correct spelling.  The other change is that there
is optional data length autocalculation, which makes it easier to use.
This is used by simply omitting the datalength parameter of the insert
method of the class Freenet.

-- 
Travis Bemann
Sendmail is still screwed up on my box.
My email address is really bemann at execpc.com.
-------------- next part --------------
#!/usr/bin/env python

# A Freenet client module.  May rapidly change during the development of
# the Freenet protocal.
#
# freenetlib.py 0.03
#
# Copyright (C) 2000 Travis Bemann, Itamar Shtull-Trauring
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# more information on Freenet is availiable at http://freenet.sourceforge.net/

import re
import socket
import string
import os
import whrandom
import sha
import struct
import sys

# Default values for Freenet init parameters

FREENET_DEFAULT_HOST = 'localhost'
FREENET_DEFAULT_PORT = 19114
FREENET_DEFAULT_SEARCH_DEPTH = 10
FREENET_VERSION = '1.2'
FREENET_DEFAULT_MIN_DEPTH = 5
FREENET_DEFAULT_MAX_DEPTH = 30
FREENET_DEFAULT_KEEP_ALIVE = 1
FREENET_DEFAULT_AUTO_CONNECT = 1

DEBUG = 0

error_reply = 'freenetlib.error_reply'
error_uniqueid = 'freenetlib.error_uniqueid'
error_version = 'freenetlib.error_version'
error_nodata = 'freenetlib.error_nodata'
error_keyexists = 'freenetlib.error_keyexists'

# Note -  This is a slightly modified version of a bit of Python
# code sent to me by Bill Trost <trost at cloud.rain.com>.
# This is fast as in it is fast to use in a program, not that the
# implementation used is necessarily fast.
def quick_sha1(s):
    return "%X%X%X%X%X" % struct.unpack("!lllll", sha.new(s).digest())

class Freenet:

    # Initialize an instance
    # - host: host of FreeNet server
    # - port: port of FreeNet server
    # - search_depth: maximum key search depth
    # - min_depth: minimum starting message depth
    # - max_depth: maximum starting message depth
    # - keep_alive: whether to keep the connection open
    # - auto_connect: whether to handshake automatically

    def __init__(self,
            host = FREENET_DEFAULT_HOST,
            port = FREENET_DEFAULT_PORT,
            search_depth = FREENET_DEFAULT_SEARCH_DEPTH,
            min_depth = FREENET_DEFAULT_MIN_DEPTH,
            max_depth = FREENET_DEFAULT_MAX_DEPTH,
            keep_alive = FREENET_DEFAULT_KEEP_ALIVE,
            auto_connect = FREENET_DEFAULT_AUTO_CONNECT):
        self.host = host
        self.port = port
        if search_depth < 1:
            raise ValueError, \
                'search_depth must be larger than zero'
        self.search_depth = search_depth
        if min_depth < 1:
            raise ValueError, 'min_depth must be larger than zero'
        if max_depth < 1:
            raise ValueError, 'max_depth must be larger than zero'
        if min_depth > max_depth:
            raise ValueError, \
                'min_depth may not be larger than max_depth'
        self.min_depth = min_depth
        self.max_depth = max_depth
        self.keep_alive = keep_alive
        self.response_closed = 1        
        if auto_connect:
            self.connect()

    def sendmesg(self, type, items, datafile=None, datalength=0):
        """ Construct message from dictionary, with optional file for data """
        if DEBUG:
            print "Sent:", type, items
        f = self.file
        f.write(type + '\n')
        for key, value in items.items():
            f.write("%s=%s\n" % (key, value))

        if datafile:
            if datalength:
                f.write("DataLength=%s\n" % datalength)
                f.write("Data\n")
                line = datafile.readline()
                while line:
                    f.write(line)
                    line = datafile.readline()
            else:
                datafile.seek(0, 2)
                datalength = datafile.tell()
                datafile.seek(0, 0)
                f.write('DataLength=%s\n' % datalength)
                f.write('Data\n')
                line = datafile.readline()
                while line:
                    f.write(line)
                    line = datafile.readline()
        else:
            f.write("EndMessage\n")

        f.flush()            

    def recvmesg(self, savefile=None):
        """ Read a message, writing it to a given file if need be """
        f = self.file
        dict = {}
        messagetype = string.rstrip(f.readline())

        line = string.rstrip(f.readline())
        while line and (line not in ['Data', 'EndMessage']):
            key, value = string.split(line, '=')
            dict[key] = value
            line = string.rstrip(f.readline())
        if line == 'Data':            
            #if not dict.has_key('DataLength'):
            #    raise error_nodatalength, "Message has Data but no DataLength."
            line = f.readline()
            readlength = 0
            while line:
                savefile.write(line)
                readlength = readlength + len(line)
                line = f.readline()
            #if readlength != int(dict['DataLength']):
            #    raise error_nodata, """DataLength supposed to be %s, but
            #        actual Data length is %s.""" % (dict['DataLength'], 
readlength)
        if DEBUG:
            print "Received:", messagetype, dict
        return messagetype, dict

    def getranduniqueid(self):
        """ Generate 64 bit hex id number """
        max = pow(2, 16) - 1
        result = ""
        for i in range(0, 4):
            result = result + ("%04x" % whrandom.randint(0, max))
        # the server strip leading 0 from uniqueids, so we do the same
        while result[0] == '0': 
            result = result[1:]
        return result

    def getranddepth(self):
        """ Returns a random number between self.min_depth 
            and self.max_depth.
        """
        return whrandom.randint(self.min_depth, self.max_depth)

    def connect(self):
        """ Connect to server """
        self.sock = socket.socket(socket.AF_INET,
            socket.SOCK_STREAM)
        self.sock.connect(self.host, self.port)
        self.response_closed = 0
        self.file = self.sock.makefile('rb+')            
        self._handshake()

    def close(self):
        """ close from server """
        self.response_closed = 1
        self.sock.close()

    def _handshake(self):
        """ Handshake with server.  """

        uid = self.getranduniqueid()
        if self.keep_alive:
            keep_alive = 'true'
        else:
            keep_alive = 'false'
        self.sendmesg('HandshakeRequest',
            {'UniqueID': uid,
            'Depth': 1,
            'HopsToLive': self.search_depth,
            'KeepAlive': keep_alive})
        type, items = self.recvmesg()
        if type == 'HandshakeRequest':
            try:
                self.sendmesg('HandshakeReply',
                    {'UniqueID': items['UniqueID'],
                    'Depth': items['Depth'],
                    'HopsToLive': items['HopsToLive'],
                    'KeepAlive': keep_alive,
                    'Version': FREENET_VERSION})
            except KeyError:
                self.close()
                raise error_reply, \
                    'Necessary request fields are missing'
        elif type == 'HandshakeReply':
            try:
                if items['Version'] != FREENET_VERSION:
                    self.close()
                    raise error_version, \
                        'Incompatible FreeNet version'
                if items['UniqueID'] != uid:
                    self.close()
                    raise error_uniqueid, \
                        'Incorrect unique ID'
            except KeyError:

                raise error_version, \
                    'Necessary reply fields are missing'
        else:
            self.close()
            raise error_reply, 'Unexpected message type'


    def insert(self, key, datafile, datalength = None):
        """ Insert file under key
            key: key to insert file under (unencrypted)
            data: file data
        """
        if not self.keep_alive and self.response_closed:
            self.connect()
        uid = self.getranduniqueid()
        if self.keep_alive:
            keep_alive = 'true'
        else:
            keep_alive = 'false'
        self.sendmesg('InsertRequest',
            {'UniqueID': uid,
            'Depth': self.getranddepth(),
            'HopsToLive': self.search_depth,
            'KeepAlive': keep_alive,
            'Source': 'tcp/127.0.0.1:19114',
            'SearchKey': quick_sha1(key)})
        type, items = self.recvmesg()
        if type == 'RequestFailed':
            if not self.keep_alive:
                self.close()
            raise error_reply, 'Request failed'
        elif type == 'DataReply':
            if not self.keep_alive:
                self.close()
            raise error_keyexists, 'Key already exists'
        elif type == 'TimedOut':
            if not self.keep_alive:
                self.close()
            raise error_nodata, \
                'Key already exists but no data was found'
        elif type == 'InsertReply':
            if items['UniqueID'] != uid:
                if not self.keep_alive:
                    self.close()
                raise error_uniqueid, \
                    'Unique ID mismatch'
            self.sendmesg('DataInsert',
                {'UniqueID': uid,
                'Depth': self.getranddepth(),
                'HopsToLive': self.search_depth,
                'KeepAlive': keep_alive,
                'Source': 'tcp/127.0.0.1:19114',
                'DataSource': 'tcp/127.0.0.1:19114'},
                datafile, datalength)
        else:
            if not self.keep_alive:
                self.close()
            raise error_reply, 'Unexpected message type'
        if not self.keep_alive:
            self.close()



    def request(self, key, savefile=sys.stdout):
        """ Request file under key
            key: key to request file from (unencrypted)
            returns: file data
        """
        if not self.keep_alive and self.response_closed:
            self.connect()
        uid = self.getranduniqueid()
        if self.keep_alive:
            keep_alive = 'true'
        else:
            keep_alive = 'false'
        self.sendmesg('DataRequest',
            {'UniqueID': uid,
            'Depth': self.getranddepth(),
            'HopsToLive': self.search_depth,
            'KeepAlive': keep_alive,
            'Source': 'tcp/127.0.0.1:19114',
            'SearchKey': quick_sha1(key)})
        type, items = self.recvmesg(savefile)
        if type == 'RequestFailed':
            if not self.keep_alive:
                self.close()
            raise error_reply, 'Request failed'
        elif type == 'TimedOut':
            if not self.keep_alive:
                self.close()
            raise error_keyexists, \
                'Key exists but no data was found'
        elif type == 'DataReply':
            if items['UniqueID'] != uid:
                if not self.keep_alive:
                    self.close()
                raise error_uniqueid, \
                    'Unique ID mismatch'
            return None
        else:
            if not self.keep_alive:
                self.close()
            raise error_reply, 'Unexpected message type'
        return None # shouldn't happen(?)

if __name__ == '__main__':
    fn = Freenet()
    fn.request("keyindex.pl")

Reply via email to