123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- from xmlrpc.client import ServerProxy, Fault
- from os.path import join, abspath, isfile
- from xmlrpc.server import SimpleXMLRPCServer
- from urllib.parse import urlparse
- import sys
- SimpleXMLRPCServer.allow_reuse_address = 1
- MAX_HISTORY_LENGTH = 6
- UNHANDLED = 100
- ACCESS_DENIED = 200
- class UnhandledQuery(Fault):
- """
- An exception that represents an unhandled query.
- """
- def __init__(self, message="Couldn't handle the query"):
- super().__init__(UNHANDLED, message)
- class AccessDenied(Fault):
- """
- An exception that is raised if a user tries to access a
- resource for which he or she is not authorized.
- """
- def __init__(self, message="Access denied"):
- super().__init__(ACCESS_DENIED, message)
- def inside(dir, name):
- """
- Checks whether a given file name lies within a given directory.
- """
- dir = abspath(dir)
- name = abspath(name)
- return name.startswith(join(dir, ''))
- def get_port(url):
- """
- Extracts the port number from a URL.
- """
- name = urlparse(url)[1]
- parts = name.split(':')
- return int(parts[-1])
- class Node:
- """
- A node in a peer-to-peer network.
- """
- def __init__(self, url, dirname, secret):
- self.url = url
- self.dirname = dirname
- self.secret = secret
- self.known = set()
- def query(self, query, history=[]):
- """
- Performs a query for a file, possibly asking other known Nodes for
- help. Returns the file as a string.
- """
- try:
- return self._handle(query)
- except UnhandledQuery:
- history = history + [self.url]
- if len(history) >= MAX_HISTORY_LENGTH: raise
- return self._broadcast(query, history)
- def hello(self, other):
- """
- Used to introduce the Node to other Nodes.
- """
- self.known.add(other)
- return 0
- def fetch(self, query, secret):
- """
- Used to make the Node find a file and download it.
- """
- if secret != self.secret: raise AccessDenied
- result = self.query(query)
- f = open(join(self.dirname, query), 'w')
- f.write(result)
- f.close()
- return 0
- def _start(self):
- """
- Used internally to start the XML-RPC server.
- """
- s = SimpleXMLRPCServer(("", get_port(self.url)), logRequests=False)
- s.register_instance(self)
- s.serve_forever()
- def _handle(self, query):
- """
- Used internally to handle queries.
- """
- dir = self.dirname
- name = join(dir, query)
- if not isfile(name): raise UnhandledQuery
- if not inside(dir, name): raise AccessDenied
- return open(name).read()
- def _broadcast(self, query, history):
- """
- Used internally to broadcast a query to all known Nodes.
- """
- for other in self.known.copy():
- if other in history: continue
- try:
- s = ServerProxy(other)
- return s.query(query, history)
- except Fault as f:
- if f.faultCode == UNHANDLED: pass
- else: self.known.remove(other)
- except:
- self.known.remove(other)
- raise UnhandledQuery
- def main():
- url, directory, secret = sys.argv[1:]
- n = Node(url, directory, secret)
- n._start()
- if __name__ == '__main__': main()
|