Hello! I just signed up for the distorted view show member's feed (you get extra show content, etc). The problem is that the download server is password protected which means the podcast client must understand how to do basic http authentication.
Attached is a patch against svn revision 351 to add the ability to download podcasts from password protected server directories. It stores the username and password in the index.xml files in each local podcast directory. The passwords are obfuscated using a simple mapping table and the string.translate function. I know this isn't secure but I don't think it's advisable to store the passwords in plaintext either. The username and password are set via the "Advanced" tab in the "Edit channel information" dialogue. If you would like to test it, I set up a feed and password protected directory on my server (with a 5 second "podcast" I made). Here's the info for that: feed url: http://nikosapi.org/test_protected_feed/feed.xml username: user01 password: testfeed Also, I fixed a little bug that would cause gPodder to freeze when loading a cover file with 0 length. nick
--- gpodder-r351/data/gpodder.glade 2007-07-14 14:47:23.000000000 -0400 +++ gpodder-r351-dev/data/gpodder.glade 2007-07-14 19:25:08.000000000 -0400 @@ -1516,6 +1516,131 @@ </child> <child> + <widget class="GtkTable" id="table10"> + <property name="visible">True</property> + <property name="n_rows">2</property> + <property name="n_columns">2</property> + <property name="homogeneous">False</property> + <property name="row_spacing">0</property> + <property name="column_spacing">0</property> + + <child> + <widget class="GtkLabel" id="label93"> + <property name="visible">True</property> + <property name="label" translatable="yes">Feed Username: </property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="x_options">fill</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label94"> + <property name="visible">True</property> + <property name="label" translatable="yes">Feed Password: </property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">fill</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkEntry" id="FeedUsername"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"></property> + <property name="has_frame">True</property> + <property name="invisible_char">â</property> + <property name="activates_default">False</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="y_options"></property> + </packing> + </child> + + <child> + <widget class="GtkEntry" id="FeedPassword"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">False</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"></property> + <property name="has_frame">True</property> + <property name="invisible_char">â</property> + <property name="activates_default">False</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options"></property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + </packing> + </child> + + <child> + <widget class="GtkHSeparator" id="hseparator13"> + <property name="visible">True</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + </packing> + </child> + + <child> <widget class="GtkTable" id="table3"> <property name="border_width">5</property> <property name="visible">True</property> @@ -1639,7 +1764,7 @@ </widget> <packing> <property name="padding">0</property> - <property name="expand">True</property> + <property name="expand">False</property> <property name="fill">True</property> </packing> </child>
diff -u gpodder-r351/src/gpodder/gpodder.py gpodder-r351-dev/src/gpodder/gpodder.py --- gpodder-r351/src/gpodder/gpodder.py 2007-07-14 14:47:17.000000000 -0400 +++ gpodder-r351-dev/src/gpodder/gpodder.py 2007-07-14 17:46:07.000000000 -0400 @@ -1142,6 +1142,8 @@ self.cbNoSync.set_active( not channel.sync_to_devices) self.musicPlaylist.set_text( channel.device_playlist_name) self.cbMusicChannel.set_active( channel.is_music_channel) + self.FeedUsername.set_text( channel.username) + self.FeedPassword.set_text( channel.password) gPodderLib().get_image_from_url( channel.image, self.imgCover.set_from_pixbuf, self.labelCoverStatus.set_text, self.labelCoverStatus.hide, channel.cover_file) b = gtk.TextBuffer() @@ -1174,6 +1176,8 @@ self.channel.is_music_channel = self.cbMusicChannel.get_active() self.channel.device_playlist_name = self.musicPlaylist.get_text() self.channel.set_custom_title( self.entryTitle.get_text()) + self.channel.username = self.FeedUsername.get_text().strip() + self.channel.password = self.FeedPassword.get_text().strip() self.channel.save_metadata_to_localdb() self.gPodderChannel.destroy() diff -u gpodder-r351/src/gpodder/liblocdbreader.py gpodder-r351-dev/src/gpodder/liblocdbreader.py --- gpodder-r351/src/gpodder/liblocdbreader.py 2007-07-14 14:47:17.000000000 -0400 +++ gpodder-r351-dev/src/gpodder/liblocdbreader.py 2007-07-14 19:45:25.000000000 -0400 @@ -85,6 +86,8 @@ if attrs.get('nosync', 'false').lower() == 'true': self.channel.sync_to_devices = False self.channel.override_title = attrs.get('title','') + self.channel.username = attrs.get('username', '') + self.channel.password = self.channel.unobfuscate_password(attrs.get('password', '')) def endElement( self, name): if self.current_item == None: diff -u gpodder-r351/src/gpodder/liblocdbwriter.py gpodder-r351-dev/src/gpodder/liblocdbwriter.py --- gpodder-r351/src/gpodder/liblocdbwriter.py 2007-07-14 14:47:17.000000000 -0400 +++ gpodder-r351-dev/src/gpodder/liblocdbwriter.py 2007-07-14 18:07:10.000000000 -0400 @@ -64,6 +64,9 @@ self.ofile.write( ' playlist="%s"' % (channel.device_playlist_name)) if channel.override_title: self.ofile.write( ' title="%s"' % (channel.override_title)) + if channel.username or channel.password: + self.ofile.write( ' username="%s"' % (channel.username)) + self.ofile.write( ' password="%s"' % (channel.obfuscate_password(channel.password))) self.ofile.write( '/>'+"\n") def writeEpisodeMetadata( self, episode): diff -u gpodder-r351/src/gpodder/libpodcasts.py gpodder-r351-dev/src/gpodder/libpodcasts.py --- gpodder-r351/src/gpodder/libpodcasts.py 2007-07-14 14:47:17.000000000 -0400 +++ gpodder-r351-dev/src/gpodder/libpodcasts.py 2007-07-14 20:40:27.000000000 -0400 @@ -64,6 +64,7 @@ import md5 +import string class podcastChannel(ListType): """holds data for a complete channel""" @@ -87,6 +88,11 @@ self.device_playlist_name = 'gPodder' # if set, this overrides the channel-provided title self.override_title = '' + self.username = '' + self.password = '' + # mapping table for maketrans + self.map_from = 'abcdefghijklmnopqrstuvwxyz0123456789' + self.map_to = 'qazwsxedcrfvtgbyhnujmikolp9514738062' def get_filename( self): """Return the MD5 sum of the channel URL""" @@ -155,6 +161,8 @@ self.is_music_channel = ch.is_music_channel self.device_playlist_name = ch.device_playlist_name self.override_title = ch.override_title + self.username = ch.username + self.password = ch.password def newest_pubdate_downloaded( self): gl = libgpodder.gPodderLib() @@ -493,6 +501,20 @@ libgpodder.releaseLock() + def obfuscate_password(self, password): + translation_table = string.maketrans(self.map_from + self.map_from.upper(), self.map_to + self.map_to.upper()) + return string.translate(password, translation_table) + + def unobfuscate_password(self, password_obfus): + if password_obfus == '': + return + translation_table = string.maketrans(self.map_to + self.map_to.upper(), self.map_from + self.map_from.upper()) + try: + # For now at least, only ascii passwords will work + return string.translate(password_obfus.encode('ascii'), translation_table) + except: + return + class podcastItem(object): """holds data for one object in a channel""" def __init__( self, @@ -686,7 +708,7 @@ new_model.set( new_iter, 7, '%s\n<small>%s</small>' % ( saxutils.escape( channel.title), saxutils.escape( channel.description.split('\n')[0]), )) - if os.path.exists( channel.cover_file): + if os.path.exists( channel.cover_file) and os.path.getsize(channel.cover_file) > 0: new_model.set( new_iter, 8, gtk.gdk.pixbuf_new_from_file_at_size( channel.cover_file, 32, 32)) else: iconsize = gtk.icon_size_from_name('channel-icon') diff -u gpodder-r351/src/gpodder/libwget.py gpodder-r351-dev/src/gpodder/libwget.py --- gpodder-r351/src/gpodder/libwget.py 2007-07-14 14:47:17.000000000 -0400 +++ gpodder-r351-dev/src/gpodder/libwget.py 2007-07-14 18:53:55.000000000 -0400 @@ -85,7 +85,10 @@ limit_str = '--limit-rate=%.1fk' % ( libgpodder.gPodderLib().limit_rate_value, ) libgpodder.gPodderLib().deleteFilename( self.tempname) # TODO use config file for number of retries (wget --tries)? - command = 'wget --timeout=120 --continue %s --output-document="%s" "%s"' % ( limit_str, self.tempname, self.url ) + if self.channelitem and ( self.channelitem.username or self.channelitem.password ): + command = 'wget --user "%s" --password "%s" --timeout=120 --continue %s --output-document="%s" "%s"' % ( self.channelitem.username, self.channelitem.password, limit_str, self.tempname, self.url ) + else: + command = 'wget --timeout=120 --continue %s --output-document="%s" "%s"' % ( limit_str, self.tempname, self.url ) log( 'Command: %s', command) if self.statusmgr: self.statusmgr.updateInfo( self.statusmgr_id, { 'episode':self.cutename, 'speed':_('Queued'), 'progress':0, 'url':self.url})
_______________________________________________ gpodder-devel mailing list gpodder-devel@lists.berlios.de https://lists.berlios.de/mailman/listinfo/gpodder-devel