Exemplo n.º 1
0
    def load_from_url(url):
        """
        Create a TorrentDef with information from a remote source.
        :param url: The HTTP/HTTPS url where to fetch the torrent info from.
        """
        # Class method, no locking required
        def _on_response(data):
            return TorrentDef.load_from_memory(data)

        deferred = http_get(url)
        deferred.addCallback(_on_response)
        return deferred
Exemplo n.º 2
0
    def load_from_url(url):
        """
        Create a TorrentDef with information from a remote source.
        :param url: The HTTP/HTTPS url where to fetch the torrent info from.
        """

        # Class method, no locking required
        def _on_response(data):
            return TorrentDef.load_from_memory(data)

        deferred = http_get(url)
        deferred.addCallback(_on_response)
        return deferred
Exemplo n.º 3
0
    def test_http_get_expired(self):
        uri = "https://expired.badssl.com"

        def cbResponse(_):
            self.fail("Error was expected.")

        def cbErrorResponse(response):
            self.assertIsNotNone(response)

        http_deferred = http_get(uri)
        http_deferred.addCallback(cbResponse)
        http_deferred.addErrback(cbErrorResponse)

        return http_deferred
Exemplo n.º 4
0
    def test_http_get_expired(self):
        uri = "https://expired.badssl.com"

        def cbResponse(_):
            self.fail("Error was expected.")

        def cbErrorResponse(response):
            self.assertIsNotNone(response)

        http_deferred = http_get(uri)
        http_deferred.addCallback(cbResponse)
        http_deferred.addErrback(cbErrorResponse)

        return http_deferred
Exemplo n.º 5
0
    def load_from_url(url):
        """
        Load a BT .torrent or Tribler .tstream file from the URL and
        convert it into a TorrentDef.

        @param url URL
        @return Deferred
        """
        # Class method, no locking required
        def _on_response(data):
            return TorrentDef.load_from_memory(data)

        deferred = http_get(url)
        deferred.addCallback(_on_response)
        return deferred
Exemplo n.º 6
0
    def load_from_url(url):
        """
        Load a BT .torrent or Tribler .tstream file from the URL and
        convert it into a TorrentDef.

        @param url URL
        @return Deferred
        """
        # Class method, no locking required
        def _on_response(data):
            return TorrentDef.load_from_memory(data)

        deferred = http_get(url)
        deferred.addCallback(_on_response)
        return deferred
Exemplo n.º 7
0
    def test_http_get_with_redirect(self):
        """
        Test if http_get is working properly if url redirects to a magnet link.
        """
        def on_callback(response):
            self.assertEqual(response, magnet_link)

        # Setup a redirect server which redirects to a magnet link
        magnet_link = "magnet:?xt=urn:btih:DC4B96CF85A85CEEDB8ADC4B96CF85A85CEEDB8A"
        port = get_random_port()

        self.setUpHttpRedirectServer(port, magnet_link)

        test_url = "http://localhost:%d" % port
        http_deferred = http_get(test_url).addCallback(on_callback)

        return http_deferred
Exemplo n.º 8
0
    def test_http_get_with_redirect(self):
        """
        Test if http_get is working properly if url redirects to a magnet link.
        """

        def on_callback(response):
            self.assertEqual(response, magnet_link)

        # Setup a redirect server which redirects to a magnet link
        magnet_link = "magnet:?xt=urn:btih:DC4B96CF85A85CEEDB8ADC4B96CF85A85CEEDB8A"
        port = get_random_port()

        self.setUpHttpRedirectServer(port, magnet_link)

        test_url = "http://localhost:%d" % port
        http_deferred = http_get(test_url).addCallback(on_callback)

        return http_deferred
Exemplo n.º 9
0
    def check_new_version(self):
        def parse_body(body):
            if body is None:
                return
            version = json.loads(body)['name'][1:]
            if LooseVersion(version) > LooseVersion(version_id):
                self.session.notifier.notify(NTFY_NEW_VERSION, NTFY_INSERT, None, version)

        def on_request_error(failure):
            failure.trap(SchemeNotSupported, ConnectError, DNSLookupError)
            self._logger.error("Error when performing version check request: %s", failure)

        def on_response_error(failure):
            failure.trap(HttpError)
            self._logger.warning("Got response code %s when performing version check request",
                                 failure.value.response.code)

        deferred = http_get(VERSION_CHECK_URL)
        deferred.addErrback(on_response_error).addCallback(parse_body).addErrback(on_request_error)
        return deferred
Exemplo n.º 10
0
    def parse(self, url, cache):
        """
        Parses a RSS feed. This methods supports RSS 2.0 and Media RSS.
        """
        def on_rss_response(response):
            feed = feedparser.parse(response)
            feed_items = []

            for item in feed.entries:
                # ignore the ones that we have seen before
                link = item.get(u'link', None)
                if link is None or cache.has(link):
                    continue

                title = self._html2plaintext(item[u'title']).strip()
                description = self._html2plaintext(
                    item.get(u'media_description', u'')).strip()
                torrent_url = item[u'link']

                thumbnail_list = []
                media_thumbnail_list = item.get(u'media_thumbnail', None)
                if media_thumbnail_list:
                    for thumbnail in media_thumbnail_list:
                        thumbnail_list.append(thumbnail[u'url'])

                # assemble the information
                parsed_item = {
                    u'title': title,
                    u'description': description,
                    u'torrent_url': torrent_url,
                    u'thumbnail_list': thumbnail_list
                }

                feed_items.append(parsed_item)

            return feed_items

        def on_rss_error(failure):
            self._logger.error("Error when fetching RSS feed: %s", failure)

        return http_get(str(url)).addCallbacks(on_rss_response, on_rss_error)
Exemplo n.º 11
0
    def render_GET(self, request):
        """
        .. http:get:: /torrentinfo

        A GET request to this endpoint will return information from a torrent found at a provided URI.
        This URI can either represent a file location, a magnet link or a HTTP(S) url.
        - torrent: the URI of the torrent file that should be downloaded. This parameter is required.

            **Example request**:

                .. sourcecode:: none

                    curl -X PUT http://localhost:8085/torrentinfo?torrent=file:/home/me/test.torrent

            **Example response**:

                .. sourcecode:: javascript

                    {"metainfo": <torrent metainfo dictionary>}
        """
        metainfo_deferred = Deferred()

        def on_got_metainfo(metainfo):
            if not isinstance(metainfo, dict) or 'info' not in metainfo:
                self._logger.warning(
                    "Received metainfo is not a valid dictionary")
                request.setResponseCode(http.INTERNAL_SERVER_ERROR)
                request.write(json.dumps({"error": 'invalid response'}))
                self.finish_request(request)
                return

            infohash = hashlib.sha1(bencode(metainfo['info'])).digest()
            # Save the torrent to our store
            try:
                self.session.save_collected_torrent(infohash,
                                                    bencode(metainfo))
            except TypeError:
                # Note: in libtorrent 1.1.1, bencode throws a TypeError which is a known bug
                pass

            request.write(
                json.dumps({"metainfo": metainfo}, ensure_ascii=False))
            self.finish_request(request)

        def on_metainfo_timeout(_):
            if not request.finished:
                request.setResponseCode(http.REQUEST_TIMEOUT)
                request.write(json.dumps({"error": "timeout"}))
            # If the above request.write failed, the request will have already been finished
            if not request.finished:
                self.finish_request(request)

        def on_lookup_error(failure):
            failure.trap(ConnectError, DNSLookupError, HttpError)
            request.setResponseCode(http.INTERNAL_SERVER_ERROR)
            request.write(json.dumps({"error": failure.getErrorMessage()}))
            self.finish_request(request)

        if 'uri' not in request.args or len(request.args['uri']) == 0:
            request.setResponseCode(http.BAD_REQUEST)
            return json.dumps({"error": "uri parameter missing"})

        uri = unicode(request.args['uri'][0], 'utf-8')
        if uri.startswith('file:'):
            try:
                filename = url2pathname(uri[5:].replace("+", " "))
                metainfo_deferred.callback(bdecode(fix_torrent(filename)))
            except TypeError:
                request.setResponseCode(http.INTERNAL_SERVER_ERROR)
                return json.dumps(
                    {"error": "error while decoding torrent file"})
        elif uri.startswith('http'):

            def _on_loaded(metadata):
                metainfo_deferred.callback(bdecode(metadata))

            http_get(uri.encode('utf-8')).addCallback(_on_loaded).addErrback(
                on_lookup_error)
        elif uri.startswith('magnet'):
            infohash = parse_magnetlink(uri)[1]
            if infohash is None:
                request.setResponseCode(http.BAD_REQUEST)
                return json.dumps({"error": "missing infohash"})

            if self.session.has_collected_torrent(infohash):
                try:
                    tdef = TorrentDef.load_from_memory(
                        self.session.get_collected_torrent(infohash))
                except ValueError as exc:
                    request.setResponseCode(http.INTERNAL_SERVER_ERROR)
                    return json.dumps(
                        {"error": "invalid torrent file: %s" % str(exc)})
                on_got_metainfo(tdef.get_metainfo())
                return NOT_DONE_YET

            self.session.lm.ltmgr.get_metainfo(
                uri,
                callback=metainfo_deferred.callback,
                timeout=20,
                timeout_callback=on_metainfo_timeout,
                notify=True)
        else:
            request.setResponseCode(http.BAD_REQUEST)
            return json.dumps({"error": "invalid uri"})

        metainfo_deferred.addCallback(on_got_metainfo)

        return NOT_DONE_YET
Exemplo n.º 12
0
    def render_PUT(self, request):
        """
        .. http:put:: /channels/discovered/(string: channelid)/torrents/http%3A%2F%2Ftest.com%2Ftest.torrent

        Add a torrent by magnet or url to your channel. Returns error 500 if something is wrong with the torrent file
        and DuplicateTorrentFileError if already added to your channel (except with magnet links).

            **Example request**:

            .. sourcecode:: none

                curl -X PUT http://localhost:8085/channels/discovered/abcdefg/torrents/
                http%3A%2F%2Ftest.com%2Ftest.torrent --data "description=nice video"

            **Example response**:

            .. sourcecode:: javascript

                {
                    "added": "http://test.com/test.torrent"
                }

            :statuscode 404: if your channel does not exist.
            :statuscode 500: if the specified torrent is already in your channel.
        """
        my_key = self.session.trustchain_keypair
        my_channel_id = my_key.pub().key_to_bin()

        if self.is_chant_channel:
            if my_channel_id != self.cid:
                request.setResponseCode(http.NOT_ALLOWED)
                return json.dumps({
                    "error":
                    "you can only add torrents to your own chant channel"
                })
            channel = self.session.lm.mds.ChannelMetadata.get_channel_with_id(
                my_channel_id)
        else:
            channel = self.get_channel_from_db(self.cid)

        if channel is None:
            return BaseChannelsEndpoint.return_404(request)

        parameters = http.parse_qs(request.content.read(), 1)

        if 'description' not in parameters or len(
                parameters['description']) == 0:
            extra_info = {}
        else:
            extra_info = {'description': parameters['description'][0]}

        def _on_url_fetched(data):
            return TorrentDef.load_from_memory(data)

        def _on_magnet_fetched(meta_info):
            return TorrentDef.load_from_dict(meta_info)

        def _on_torrent_def_loaded(torrent_def):
            if self.is_chant_channel:
                # We have to get my channel again since we are in a different database session now
                with db_session:
                    channel = self.session.lm.mds.get_my_channel()
                    channel.add_torrent_to_channel(torrent_def, extra_info)
            else:
                channel = self.get_channel_from_db(self.cid)
                self.session.add_torrent_def_to_channel(channel[0],
                                                        torrent_def,
                                                        extra_info,
                                                        forward=True)
            return self.path

        def _on_added(added):
            request.write(json.dumps({"added": added}))
            request.finish()

        def _on_add_failed(failure):
            failure.trap(ValueError, DuplicateTorrentFileError,
                         SchemeNotSupported)
            self._logger.exception(failure.value)
            request.write(
                BaseChannelsEndpoint.return_500(self, request, failure.value))
            request.finish()

        def _on_timeout(_):
            request.write(
                BaseChannelsEndpoint.return_500(
                    self, request, RuntimeError("Metainfo timeout")))
            request.finish()

        if self.path.startswith("http:") or self.path.startswith("https:"):
            self.deferred = http_get(self.path)
            self.deferred.addCallback(_on_url_fetched)

        if self.path.startswith("magnet:"):
            try:
                self.session.lm.ltmgr.get_metainfo(
                    self.path,
                    callback=self.deferred.callback,
                    timeout=30,
                    timeout_callback=_on_timeout,
                    notify=True)
            except Exception as ex:
                self.deferred.errback(ex)

            self.deferred.addCallback(_on_magnet_fetched)

        self.deferred.addCallback(_on_torrent_def_loaded)
        self.deferred.addCallback(_on_added)
        self.deferred.addErrback(_on_add_failed)
        return NOT_DONE_YET
Exemplo n.º 13
0
    def render_PUT(self, request):
        """
        .. http:put:: /mychannel/torrents

        Add a torrent file to your own channel. Returns error 500 if something is wrong with the torrent file
        and DuplicateTorrentFileError if already added to your channel. The torrent data is passed as base-64 encoded
        string. The description is optional.

        Option torrents_dir adds all .torrent files from a chosen directory
        Option recursive enables recursive scanning of the chosen directory for .torrent files

            **Example request**:

            .. sourcecode:: none

                curl -X PUT http://localhost:8085/mychannel/torrents
                --data "torrent=...&description=funny video"

            **Example response**:

            .. sourcecode:: javascript

                {
                    "added": True
                }

            **Example request**:

            .. sourcecode:: none

                curl -X PUT http://localhost:8085/mychannel/torrents? --data "torrents_dir=some_dir&recursive=1"

            **Example response**:

            .. sourcecode:: javascript

                {
                    "added": 13
                }

            :statuscode 404: if your channel does not exist.
            :statuscode 500: if the passed torrent data is corrupt.
        """
        my_channel = self.session.lm.mds.ChannelMetadata.get_my_channel()
        if not my_channel:
            request.setResponseCode(http.NOT_FOUND)
            return json.dumps({"error": "your channel has not been created yet"})

        parameters = http.parse_qs(request.content.read(), 1)

        if 'description' not in parameters or not parameters['description']:
            extra_info = {}
        else:
            extra_info = {'description': parameters['description'][0]}

        def _on_url_fetched(data):
            return TorrentDef.load_from_memory(data)

        def _on_magnet_fetched(meta_info):
            return TorrentDef.load_from_dict(meta_info)

        def _on_torrent_def_loaded(torrent_def):
            with db_session:
                channel = self.session.lm.mds.get_my_channel()
                channel.add_torrent_to_channel(torrent_def, extra_info)
            return 1

        def _on_added(added):
            request.write(json.dumps({"added": added}))
            request.finish()

        def _on_add_failed(failure):
            failure.trap(ValueError, DuplicateTorrentFileError, SchemeNotSupported)
            self._logger.exception(failure.value)
            request.write(self.return_500(request, failure.value))
            request.finish()

        def _on_timeout(_):
            request.write(self.return_500(request, RuntimeError("Metainfo timeout")))
            request.finish()

        # First, check whether we did upload a magnet link or URL
        if 'uri' in parameters and parameters['uri']:
            deferred = Deferred()
            uri = parameters['uri'][0]
            if uri.startswith("http:") or uri.startswith("https:"):
                deferred = http_get(uri)
                deferred.addCallback(_on_url_fetched)
            elif uri.startswith("magnet:"):
                try:
                    self.session.lm.ltmgr.get_metainfo(uri, callback=deferred.callback,
                                                       timeout=30, timeout_callback=_on_timeout, notify=True)
                except Exception as ex:
                    deferred.errback(ex)

                deferred.addCallback(_on_magnet_fetched)
            else:
                request.setResponseCode(http.BAD_REQUEST)
                return json.dumps({"error": "unknown uri type"})

            deferred.addCallback(_on_torrent_def_loaded)
            deferred.addCallback(_on_added)
            deferred.addErrback(_on_add_failed)
            return NOT_DONE_YET

        torrents_dir = None
        if 'torrents_dir' in parameters and parameters['torrents_dir'] > 0:
            torrents_dir = parameters['torrents_dir'][0]
            if not os.path.isabs(torrents_dir):
                request.setResponseCode(http.BAD_REQUEST)
                return json.dumps({"error": "the torrents_dir should point to a directory"})

        recursive = False
        if 'recursive' in parameters and parameters['recursive'] > 0:
            recursive = parameters['recursive'][0]
            if not torrents_dir:
                request.setResponseCode(http.BAD_REQUEST)
                return json.dumps({"error": "the torrents_dir parameter should be provided when the recursive "
                                            "parameter is set"})

        if torrents_dir:
            torrents_list, errors_list = my_channel.add_torrents_from_dir(torrents_dir, recursive)
            return json.dumps({"added": len(torrents_list), "errors": errors_list})

        if 'torrent' not in parameters or not parameters['torrent']:
            request.setResponseCode(http.BAD_REQUEST)
            return json.dumps({"error": "torrent parameter missing"})

        # Try to parse the torrent data
        try:
            torrent = base64.b64decode(parameters['torrent'][0])
            torrent_def = TorrentDef.load_from_memory(torrent)
        except (TypeError, ValueError):
            request.setResponseCode(http.INTERNAL_SERVER_ERROR)
            return json.dumps({"error": "invalid torrent file"})

        try:
            my_channel.add_torrent_to_channel(torrent_def, extra_info)
        except DuplicateTorrentFileError:
            request.setResponseCode(http.INTERNAL_SERVER_ERROR)
            return json.dumps({"error": "this torrent already exists in your channel"})

        return json.dumps({"added": 1})
    def render_PUT(self, request):
        """
        .. http:put:: /channels/discovered/(string: channelid)/torrents/http%3A%2F%2Ftest.com%2Ftest.torrent

        Add a torrent by magnet or url to your channel. Returns error 500 if something is wrong with the torrent file
        and DuplicateTorrentFileError if already added to your channel (except with magnet links).

            **Example request**:

            .. sourcecode:: none

                curl -X PUT http://localhost:8085/channels/discovered/abcdefg/torrents/
                http%3A%2F%2Ftest.com%2Ftest.torrent --data "description=nice video"

            **Example response**:

            .. sourcecode:: javascript

                {
                    "added": "http://test.com/test.torrent"
                }

            :statuscode 404: if your channel does not exist.
            :statuscode 500: if the specified torrent is already in your channel.
        """
        channel = self.get_channel_from_db(self.cid)
        if channel is None:
            return BaseChannelsEndpoint.return_404(request)

        parameters = http.parse_qs(request.content.read(), 1)

        if 'description' not in parameters or len(parameters['description']) == 0:
            extra_info = {}
        else:
            extra_info = {'description': parameters['description'][0]}

        def _on_url_fetched(data):
            return TorrentDef.load_from_memory(data)

        def _on_magnet_fetched(meta_info):
            return TorrentDef.load_from_dict(meta_info)

        @blocking_call_on_reactor_thread
        def _on_torrent_def_loaded(torrent_def):
            self.session.add_torrent_def_to_channel(channel[0], torrent_def, extra_info, forward=True)
            return self.path

        def _on_added(added):
            request.write(json.dumps({"added": added}))
            request.finish()

        def _on_add_failed(failure):
            failure.trap(ValueError, DuplicateTorrentFileError)
            self._logger.exception(failure.value)
            request.write(BaseChannelsEndpoint.return_500(self, request, failure.value))
            request.finish()

        if self.path.startswith("http:") or self.path.startswith("https:"):
            self.deferred = http_get(self.path)
            self.deferred.addCallback(_on_url_fetched)

        if self.path.startswith("magnet:"):
            try:
                self.session.lm.ltmgr.get_metainfo(self.path, callback=self.deferred.callback,
                                                   timeout=30, timeout_callback=self.deferred.errback, notify=True)
            except Exception as ex:
                self.deferred.errback(ex)

            self.deferred.addCallback(_on_magnet_fetched)

        self.deferred.addCallback(_on_torrent_def_loaded)
        self.deferred.addCallback(_on_added)
        self.deferred.addErrback(_on_add_failed)
        return NOT_DONE_YET
Exemplo n.º 15
0
    def render_GET(self, request):
        """
        .. http:get:: /torrentinfo

        A GET request to this endpoint will return information from a torrent found at a provided URI.
        This URI can either represent a file location, a magnet link or a HTTP(S) url.
        - torrent: the URI of the torrent file that should be downloaded. This parameter is required.

            **Example request**:

                .. sourcecode:: none

                    curl -X PUT http://localhost:8085/torrentinfo?torrent=file:/home/me/test.torrent

            **Example response**:

                .. sourcecode:: javascript

                    {"metainfo": <torrent metainfo dictionary>}
        """
        def on_got_metainfo(metainfo):
            if not isinstance(metainfo, dict) or 'info' not in metainfo:
                self._logger.warning(
                    "Received metainfo is not a valid dictionary")
                request.setResponseCode(http.INTERNAL_SERVER_ERROR)
                request.write(json.dumps({"error": 'invalid response'}))
                self.finish_request(request)
                return

            # TODO(Martijn): store the stuff in a database!!!
            infohash = hashlib.sha1(bencode(metainfo['info'])).digest()

            # Check if the torrent is already in the downloads
            metainfo['download_exists'] = infohash in self.session.lm.downloads
            encoded_metainfo = hexlify(json.dumps(metainfo,
                                                  ensure_ascii=False))

            request.write(json.dumps({"metainfo": encoded_metainfo}))
            self.finish_request(request)

        def on_metainfo_timeout(_):
            if not request.finished:
                request.setResponseCode(http.REQUEST_TIMEOUT)
                request.write(json.dumps({"error": "timeout"}))
            # If the above request.write failed, the request will have already been finished
            if not request.finished:
                self.finish_request(request)

        def on_lookup_error(failure):
            failure.trap(ConnectError, DNSLookupError, HttpError,
                         ConnectionLost)
            request.setResponseCode(http.INTERNAL_SERVER_ERROR)
            request.write(
                json.dumps(
                    {"error": unichar_string(failure.getErrorMessage())}))
            self.finish_request(request)

        def _on_loaded(response):
            if response.startswith('magnet'):
                _, infohash, _ = parse_magnetlink(response)
                if infohash:
                    self.session.lm.ltmgr.get_metainfo(
                        response,
                        callback=metainfo_deferred.callback,
                        timeout=20,
                        timeout_callback=on_metainfo_timeout,
                        notify=True)
                    return
            metainfo_deferred.callback(bdecode(response))

        def on_mdblob(filename):
            try:
                with open(filename, 'rb') as f:
                    serialized_data = f.read()
                payload = read_payload(serialized_data)
                if payload.metadata_type not in [
                        REGULAR_TORRENT, CHANNEL_TORRENT
                ]:
                    request.setResponseCode(http.INTERNAL_SERVER_ERROR)
                    return json.dumps({"error": "Non-torrent metadata type"})
                magnet = payload.get_magnet()
            except InvalidSignatureException:
                request.setResponseCode(http.INTERNAL_SERVER_ERROR)
                return json.dumps(
                    {"error": "metadata has incorrect signature"})
            else:
                return on_magnet(magnet)

        def on_file():
            try:
                filename = url2pathname(uri[5:].encode('utf-8') if isinstance(
                    uri, text_type) else uri[5:])
                if filename.endswith(BLOB_EXTENSION):
                    return on_mdblob(filename)
                metainfo_deferred.callback(bdecode(fix_torrent(filename)))
                return NOT_DONE_YET
            except TypeError:
                request.setResponseCode(http.INTERNAL_SERVER_ERROR)
                return json.dumps(
                    {"error": "error while decoding torrent file"})

        def on_magnet(mlink=None):
            infohash = parse_magnetlink(mlink or uri)[1]
            if infohash is None:
                request.setResponseCode(http.BAD_REQUEST)
                return json.dumps({"error": "missing infohash"})

            self.session.lm.ltmgr.get_metainfo(
                mlink or uri,
                callback=metainfo_deferred.callback,
                timeout=20,
                timeout_callback=on_metainfo_timeout,
                notify=True)
            return NOT_DONE_YET

        metainfo_deferred = Deferred()
        metainfo_deferred.addCallback(on_got_metainfo)

        if 'uri' not in request.args or not request.args['uri']:
            request.setResponseCode(http.BAD_REQUEST)
            return json.dumps({"error": "uri parameter missing"})

        uri = cast_to_unicode_utf8(request.args['uri'][0])

        if uri.startswith('file:'):
            return on_file()
        elif uri.startswith('http'):
            http_get(uri.encode('utf-8')).addCallback(_on_loaded).addErrback(
                on_lookup_error)
        elif uri.startswith('magnet'):
            return on_magnet()
        else:
            request.setResponseCode(http.BAD_REQUEST)
            return json.dumps({"error": "invalid uri"})

        return NOT_DONE_YET
Exemplo n.º 16
0
    def render_GET(self, request):
        """
        .. http:get:: /torrentinfo

        A GET request to this endpoint will return information from a torrent found at a provided URI.
        This URI can either represent a file location, a magnet link or a HTTP(S) url.
        - torrent: the URI of the torrent file that should be downloaded. This parameter is required.

            **Example request**:

                .. sourcecode:: none

                    curl -X PUT http://localhost:8085/torrentinfo?torrent=file:/home/me/test.torrent

            **Example response**:

                .. sourcecode:: javascript

                    {"metainfo": <torrent metainfo dictionary>}
        """

        def on_got_metainfo(metainfo):
            if not isinstance(metainfo, dict) or 'info' not in metainfo:
                self._logger.warning("Received metainfo is not a valid dictionary")
                request.setResponseCode(http.INTERNAL_SERVER_ERROR)
                request.write(json.dumps({"error": 'invalid response'}))
                self.finish_request(request)
                return

            # TODO(Martijn): store the stuff in a database!!!
            infohash = hashlib.sha1(bencode(metainfo['info'])).digest()

            # Check if the torrent is already in the downloads
            metainfo['download_exists'] = infohash in self.session.lm.downloads
            encoded_metainfo = hexlify(json.dumps(metainfo, ensure_ascii=False))

            request.write(json.dumps({"metainfo": encoded_metainfo}))
            self.finish_request(request)

        def on_metainfo_timeout(_):
            if not request.finished:
                request.setResponseCode(http.REQUEST_TIMEOUT)
                request.write(json.dumps({"error": "timeout"}))
            # If the above request.write failed, the request will have already been finished
            if not request.finished:
                self.finish_request(request)

        def on_lookup_error(failure):
            failure.trap(ConnectError, DNSLookupError, HttpError, ConnectionLost)
            request.setResponseCode(http.INTERNAL_SERVER_ERROR)
            request.write(json.dumps({"error": unichar_string(failure.getErrorMessage())}))
            self.finish_request(request)

        def _on_loaded(response):
            if response.startswith('magnet'):
                _, infohash, _ = parse_magnetlink(response)
                if infohash:
                    self.session.lm.ltmgr.get_metainfo(response, callback=metainfo_deferred.callback, timeout=20,
                                                       timeout_callback=on_metainfo_timeout, notify=True)
                    return
            metainfo_deferred.callback(bdecode(response))

        def on_mdblob(filename):
            try:
                with open(filename, 'rb') as f:
                    serialized_data = f.read()
                payload = read_payload(serialized_data)
                if payload.metadata_type not in [REGULAR_TORRENT, CHANNEL_TORRENT]:
                    request.setResponseCode(http.INTERNAL_SERVER_ERROR)
                    return json.dumps({"error": "Non-torrent metadata type"})
                magnet = payload.get_magnet()
            except InvalidSignatureException:
                request.setResponseCode(http.INTERNAL_SERVER_ERROR)
                return json.dumps({"error": "metadata has incorrect signature"})
            else:
                return on_magnet(magnet)

        def on_file():
            try:
                filename = url2pathname(uri[5:].encode('utf-8') if isinstance(uri, text_type) else uri[5:])
                if filename.endswith(BLOB_EXTENSION):
                    return on_mdblob(filename)
                metainfo_deferred.callback(bdecode(fix_torrent(filename)))
                return NOT_DONE_YET
            except TypeError:
                request.setResponseCode(http.INTERNAL_SERVER_ERROR)
                return json.dumps({"error": "error while decoding torrent file"})

        def on_magnet(mlink=None):
            infohash = parse_magnetlink(mlink or uri)[1]
            if infohash is None:
                request.setResponseCode(http.BAD_REQUEST)
                return json.dumps({"error": "missing infohash"})

            self.session.lm.ltmgr.get_metainfo(mlink or uri, callback=metainfo_deferred.callback, timeout=20,
                                               timeout_callback=on_metainfo_timeout, notify=True)
            return NOT_DONE_YET

        metainfo_deferred = Deferred()
        metainfo_deferred.addCallback(on_got_metainfo)

        if 'uri' not in request.args or not request.args['uri']:
            request.setResponseCode(http.BAD_REQUEST)
            return json.dumps({"error": "uri parameter missing"})

        uri = cast_to_unicode_utf8(request.args['uri'][0])

        if uri.startswith('file:'):
            return on_file()
        elif uri.startswith('http'):
            http_get(uri.encode('utf-8')).addCallback(_on_loaded).addErrback(on_lookup_error)
        elif uri.startswith('magnet'):
            return on_magnet()
        else:
            request.setResponseCode(http.BAD_REQUEST)
            return json.dumps({"error": "invalid uri"})

        return NOT_DONE_YET
Exemplo n.º 17
0
    def render_PUT(self, request):
        """
        .. http:put:: /mychannel/torrents

        Add a torrent file to your own channel. Returns error 500 if something is wrong with the torrent file
        and DuplicateTorrentFileError if already added to your channel. The torrent data is passed as base-64 encoded
        string. The description is optional.

        Option torrents_dir adds all .torrent files from a chosen directory
        Option recursive enables recursive scanning of the chosen directory for .torrent files

            **Example request**:

            .. sourcecode:: none

                curl -X PUT http://localhost:8085/mychannel/torrents
                --data "torrent=...&description=funny video"

            **Example response**:

            .. sourcecode:: javascript

                {
                    "added": True
                }

            **Example request**:

            .. sourcecode:: none

                curl -X PUT http://localhost:8085/mychannel/torrents? --data "torrents_dir=some_dir&recursive=1"

            **Example response**:

            .. sourcecode:: javascript

                {
                    "added": 13
                }

            :statuscode 404: if your channel does not exist.
            :statuscode 500: if the passed torrent data is corrupt.
        """
        my_channel = self.session.lm.mds.ChannelMetadata.get_my_channel()
        if not my_channel:
            request.setResponseCode(http.NOT_FOUND)
            return json.dumps(
                {"error": "your channel has not been created yet"})

        parameters = http.parse_qs(request.content.read(), 1)

        if 'description' not in parameters or not parameters['description']:
            extra_info = {}
        else:
            extra_info = {'description': parameters['description'][0]}

        def _on_url_fetched(data):
            return TorrentDef.load_from_memory(data)

        def _on_magnet_fetched(meta_info):
            return TorrentDef.load_from_dict(meta_info)

        def _on_torrent_def_loaded(torrent_def):
            with db_session:
                channel = self.session.lm.mds.get_my_channel()
                channel.add_torrent_to_channel(torrent_def, extra_info)
            return 1

        def _on_added(added):
            request.write(json.dumps({"added": added}))
            request.finish()

        def _on_add_failed(failure):
            failure.trap(ValueError, DuplicateTorrentFileError,
                         SchemeNotSupported)
            self._logger.exception(failure.value)
            request.write(self.return_500(request, failure.value))
            request.finish()

        def _on_timeout(_):
            request.write(
                self.return_500(request, RuntimeError("Metainfo timeout")))
            request.finish()

        # First, check whether we did upload a magnet link or URL
        if 'uri' in parameters and parameters['uri']:
            deferred = Deferred()
            uri = parameters['uri'][0]
            if uri.startswith("http:") or uri.startswith("https:"):
                deferred = http_get(uri)
                deferred.addCallback(_on_url_fetched)
            elif uri.startswith("magnet:"):
                try:
                    self.session.lm.ltmgr.get_metainfo(
                        uri,
                        callback=deferred.callback,
                        timeout=30,
                        timeout_callback=_on_timeout,
                        notify=True)
                except Exception as ex:
                    deferred.errback(ex)

                deferred.addCallback(_on_magnet_fetched)
            else:
                request.setResponseCode(http.BAD_REQUEST)
                return json.dumps({"error": "unknown uri type"})

            deferred.addCallback(_on_torrent_def_loaded)
            deferred.addCallback(_on_added)
            deferred.addErrback(_on_add_failed)
            return NOT_DONE_YET

        torrents_dir = None
        if 'torrents_dir' in parameters and parameters['torrents_dir'] > 0:
            torrents_dir = parameters['torrents_dir'][0]
            if not os.path.isabs(torrents_dir):
                request.setResponseCode(http.BAD_REQUEST)
                return json.dumps(
                    {"error": "the torrents_dir should point to a directory"})

        recursive = False
        if 'recursive' in parameters and parameters['recursive'] > 0:
            recursive = parameters['recursive'][0]
            if not torrents_dir:
                request.setResponseCode(http.BAD_REQUEST)
                return json.dumps({
                    "error":
                    "the torrents_dir parameter should be provided when the recursive "
                    "parameter is set"
                })

        if torrents_dir:
            torrents_list, errors_list = my_channel.add_torrents_from_dir(
                torrents_dir, recursive)
            return json.dumps({
                "added": len(torrents_list),
                "errors": errors_list
            })

        if 'torrent' not in parameters or not parameters['torrent']:
            request.setResponseCode(http.BAD_REQUEST)
            return json.dumps({"error": "torrent parameter missing"})

        # Try to parse the torrent data
        try:
            torrent = base64.b64decode(parameters['torrent'][0])
            torrent_def = TorrentDef.load_from_memory(torrent)
        except (TypeError, ValueError):
            request.setResponseCode(http.INTERNAL_SERVER_ERROR)
            return json.dumps({"error": "invalid torrent file"})

        try:
            my_channel.add_torrent_to_channel(torrent_def, extra_info)
        except DuplicateTorrentFileError:
            request.setResponseCode(http.INTERNAL_SERVER_ERROR)
            return json.dumps(
                {"error": "this torrent already exists in your channel"})

        return json.dumps({"added": 1})
Exemplo n.º 18
0
    def render_PUT(self, request):
        """
        .. http:put:: /channels/discovered/(string: channelid)/torrents/http%3A%2F%2Ftest.com%2Ftest.torrent

        Add a torrent by magnet or url to your channel. Returns error 500 if something is wrong with the torrent file
        and DuplicateTorrentFileError if already added to your channel (except with magnet links).

            **Example request**:

            .. sourcecode:: none

                curl -X PUT http://localhost:8085/channels/discovered/abcdefg/torrents/
                http%3A%2F%2Ftest.com%2Ftest.torrent --data "description=nice video"

            **Example response**:

            .. sourcecode:: javascript

                {
                    "added": "http://test.com/test.torrent"
                }

            :statuscode 404: if your channel does not exist.
            :statuscode 500: if the specified torrent is already in your channel.
        """
        my_key = self.session.trustchain_keypair
        my_channel_id = my_key.pub().key_to_bin()

        if self.is_chant_channel:
            if my_channel_id != self.cid:
                request.setResponseCode(http.NOT_ALLOWED)
                return json.dumps({"error": "you can only add torrents to your own chant channel"})
            channel = self.session.lm.mds.ChannelMetadata.get_channel_with_id(my_channel_id)
        else:
            channel = self.get_channel_from_db(self.cid)

        if channel is None:
            return BaseChannelsEndpoint.return_404(request)

        parameters = http.parse_qs(request.content.read(), 1)

        if 'description' not in parameters or len(parameters['description']) == 0:
            extra_info = {}
        else:
            extra_info = {'description': parameters['description'][0]}

        def _on_url_fetched(data):
            return TorrentDef.load_from_memory(data)

        def _on_magnet_fetched(meta_info):
            return TorrentDef.load_from_dict(meta_info)

        def _on_torrent_def_loaded(torrent_def):
            if self.is_chant_channel:
                # We have to get my channel again since we are in a different database session now
                with db_session:
                    channel = self.session.lm.mds.get_my_channel()
                    channel.add_torrent_to_channel(torrent_def, extra_info)
            else:
                channel = self.get_channel_from_db(self.cid)
                self.session.add_torrent_def_to_channel(channel[0], torrent_def, extra_info, forward=True)
            return self.path

        def _on_added(added):
            request.write(json.dumps({"added": added}))
            request.finish()

        def _on_add_failed(failure):
            failure.trap(ValueError, DuplicateTorrentFileError, SchemeNotSupported)
            self._logger.exception(failure.value)
            request.write(BaseChannelsEndpoint.return_500(self, request, failure.value))
            request.finish()

        def _on_timeout(_):
            request.write(BaseChannelsEndpoint.return_500(self, request, RuntimeError("Metainfo timeout")))
            request.finish()

        if self.path.startswith("http:") or self.path.startswith("https:"):
            self.deferred = http_get(self.path)
            self.deferred.addCallback(_on_url_fetched)

        if self.path.startswith("magnet:"):
            try:
                self.session.lm.ltmgr.get_metainfo(self.path, callback=self.deferred.callback,
                                                   timeout=30, timeout_callback=_on_timeout, notify=True)
            except Exception as ex:
                self.deferred.errback(ex)

            self.deferred.addCallback(_on_magnet_fetched)

        self.deferred.addCallback(_on_torrent_def_loaded)
        self.deferred.addCallback(_on_added)
        self.deferred.addErrback(_on_add_failed)
        return NOT_DONE_YET