Merge branch 'master' into dejal

This commit is contained in:
David Sinclair 2019-08-21 14:13:25 -07:00
commit aa6700cedc
24 changed files with 203 additions and 63 deletions

View file

@ -39,8 +39,8 @@
* [jQuery](http://www.jquery.com): Cross-browser compliant JavaScript code. IE works without effort.
* [Underscore.js](http://underscorejs.org/): Functional programming for JavaScript.
Indispensible.
* [Backbone.js](http://backbonejs.org/): Framework for the web app. Also indispensible.
Indispensable.
* [Backbone.js](http://backbonejs.org/): Framework for the web app. Also indispensable.
* Miscellaneous jQuery Plugins: Everything from resizable layouts, to progress
bars, sortables, date handling, colors, corners, JSON, animations.
[See the complete list](https://github.com/samuelclay/NewsBlur/tree/master/media/js).

View file

@ -46,7 +46,7 @@ from utils.feed_functions import relative_timesince
from utils.feed_functions import seconds_timesince
from utils.story_functions import strip_tags, htmldiff, strip_comments, strip_comments__lxml
from utils.story_functions import prep_for_search
from utils.story_functions import create_signed_url
from utils.story_functions import create_camo_signed_url
ENTRY_NEW, ENTRY_UPDATED, ENTRY_SAME, ENTRY_ERR = range(4)
@ -1908,9 +1908,9 @@ class Feed(models.Model):
@classmethod
def secure_image_urls(cls, urls):
signed_urls = [create_signed_url(settings.IMAGES_URL,
settings.IMAGES_SECRET_KEY,
url) for url in urls]
signed_urls = [create_camo_signed_url(settings.IMAGES_URL,
settings.IMAGES_SECRET_KEY,
url) for url in urls]
return dict(zip(urls, signed_urls))
def get_tags(self, entry):

View file

@ -41,6 +41,7 @@
android:layout_marginTop="1dp"
android:layout_marginBottom="1dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:visibility="gone" />
<ImageView

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="thumbnails_small_size">50dp</dimen>
<dimen name="thumbnails_size">90dp</dimen>
</resources>

View file

@ -324,9 +324,26 @@
<string name="settings_immersive_enter_single_tap">Immersive Mode Via Single Tap</string>
<string name="settings_show_content_preview">Show Content Preview Text</string>
<string name="settings_show_thumbnails">Show Image Preview Thumbnails</string>
<string name="settings_thumbnails_style">Image Preview Thumbnails</string>
<string name="settings_notifications">Notifications</string>
<string name="settings_enable_notifications">Enable Notifications</string>
<string name="large">Large</string>
<string name="small">Small</string>
<string-array name="thumbnails_style_entries">
<item>@string/large</item>
<item>@string/small</item>
<item>@string/off</item>
</string-array>
<string-array name="thumbnails_style_values">
<item>LARGE</item>
<item>SMALL</item>
<item>OFF</item>
</string-array>
<string name="thumbnails_style_value">LARGE</string>
<string name="infrequent_choice_title">Stories from sites with</string>
<string name="infrequent_5">&lt; 5 STORIES/MONTH</string>
<string name="infrequent_15">&lt; 15 STORIES/MONTH</string>

View file

@ -88,10 +88,13 @@
android:defaultValue="true"
android:key="pref_show_content_preview"
android:title="@string/settings_show_content_preview" />
<CheckBoxPreference
android:defaultValue="true"
android:key="pref_show_thumbnails"
android:title="@string/settings_show_thumbnails" />
<ListPreference
android:key="pref_thumbnails_style"
android:title="@string/settings_thumbnails_style"
android:dialogTitle="@string/settings_thumbnails_style"
android:entries="@array/thumbnails_style_entries"
android:entryValues="@array/thumbnails_style_values"
android:defaultValue="@string/thumbnails_style_value" />
<CheckBoxPreference
android:defaultValue="false"
android:key="pref_mark_read_on_scroll"

View file

@ -121,6 +121,8 @@ public class Main extends NbActivity implements StateChangedListener, SwipeRefre
public void afterTextChanged(Editable s) {}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
});
FeedUtils.currentFolderName = null;
}
@Override

View file

@ -767,6 +767,7 @@ public class BlurDatabaseHelper {
Cursor c = dbRO.query(DatabaseConstants.FEED_TABLE, null, selection, selArgs, null, null, null);
while (c.moveToNext()) {
Feed f = Feed.fromCursor(c);
if(!f.active) continue;
result += f.positiveCount;
if ((stateFilter == StateFilter.SOME) || (stateFilter == StateFilter.ALL)) result += f.neutralCount;
if (stateFilter == StateFilter.ALL) result += f.negativeCount;

View file

@ -18,6 +18,7 @@ import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import butterknife.Bind;
@ -42,6 +43,7 @@ import com.newsblur.util.ImageLoader;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.StoryListStyle;
import com.newsblur.util.StoryUtils;
import com.newsblur.util.ThumbnailStyle;
import com.newsblur.util.UIUtils;
/**
@ -391,11 +393,12 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
return true;
case R.id.menu_save_story:
FeedUtils.setStorySaved(story, true, context);
//TODO get folder name
FeedUtils.setStorySaved(story, true, context, null);
return true;
case R.id.menu_unsave_story:
FeedUtils.setStorySaved(story, false, context);
FeedUtils.setStorySaved(story, false, context, null);
return true;
case R.id.menu_intel:
@ -443,10 +446,10 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
FeedUtils.markStoryUnread(story, context);
break;
case GEST_ACTION_SAVE:
FeedUtils.setStorySaved(story, true, context);
FeedUtils.setStorySaved(story, true, context, null);
break;
case GEST_ACTION_UNSAVE:
FeedUtils.setStorySaved(story, false, context);
FeedUtils.setStorySaved(story, false, context, null);
break;
case GEST_ACTION_NONE:
default:
@ -510,11 +513,12 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
*/
private void bindCommon(StoryViewHolder vh, int position, Story story) {
if ((vh instanceof StoryTileViewHolder) ||
((PrefsUtils.isShowThumbnails(context)) && (story.thumbnailUrl != null))) {
((PrefsUtils.getThumbnailStyle(context) != ThumbnailStyle.OFF) && (story.thumbnailUrl != null))) {
// when first created, tiles' views tend to not yet have their dimensions calculated, but
// upon being recycled they will often have a known size, which lets us give a max size to
// the image loader, which in turn can massively optimise loading. the image loader will
// reject nonsene values
int thumbSizeGuess = vh.thumbView.getMeasuredHeight();
// there is a not-unlikely chance that the recycler will re-use a tile for a story with the
// same thumbnail. only load it if it is different.
@ -627,6 +631,16 @@ public class StoryViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
vh.storyAuthor.setTextSize(textSize * defaultTextSize_story_item_author);
vh.storySnippet.setTextSize(textSize * defaultTextSize_story_item_snip);
ThumbnailStyle thumbnailStyle = PrefsUtils.getThumbnailStyle(context);
int sizeRes = thumbnailStyle == ThumbnailStyle.SMALL ? R.dimen.thumbnails_small_size : R.dimen.thumbnails_size;
int sizeDp = context.getResources().getDimensionPixelSize(sizeRes);
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) vh.thumbView.getLayoutParams();
if (params.height != sizeDp) {
params.height = sizeDp;
params.width = sizeDp;
}
if (this.ignoreReadStatus || (! story.read)) {
vh.storyAuthor.setAlpha(1.0f);
vh.storySnippet.setAlpha(1.0f);

View file

@ -80,6 +80,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
super.onCreate(savedInstanceState);
currentState = PrefsUtils.getStateFilter(getActivity());
adapter = new FolderListAdapter(getActivity(), currentState);
FeedUtils.currentFolderName = null;
// NB: it is by design that loaders are not started until we get a
// ping from the sync service indicating that it has initialised
}
@ -502,6 +503,7 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
@Override
public boolean onChildClick(ExpandableListView list, View childView, int groupPosition, int childPosition, long id) {
FeedUtils.currentFolderName = null;
FeedSet fs = adapter.getChild(groupPosition, childPosition);
if (adapter.isRowAllSharedStories(groupPosition)) {
SocialFeed socialFeed = adapter.getSocialFeed(groupPosition, childPosition);
@ -519,6 +521,12 @@ public class FolderListFragment extends NbFragment implements OnCreateContextMen
// a folder name on the FeedSet and making it into a folder-type set. it is just a single feed,
// and the folder name is a bit of metadata needed by the UI/API
String folderName = adapter.getGroupFolderName(groupPosition);
if(folderName == null || folderName.equals(AppConstants.ROOT_FOLDER)){
FeedUtils.currentFolderName = null;
}else{
FeedUtils.currentFolderName = folderName;
}
Intent intent = new Intent(getActivity(), FeedItemsList.class);
intent.putExtra(ItemsList.EXTRA_FEED_SET, fs);
intent.putExtra(FeedItemsList.EXTRA_FEED, feed);

View file

@ -40,6 +40,7 @@ import com.newsblur.R;
import com.newsblur.activity.NbActivity;
import com.newsblur.activity.Reading;
import com.newsblur.domain.Classifier;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Story;
import com.newsblur.domain.UserDetails;
import com.newsblur.service.OriginalTextService;
@ -56,8 +57,10 @@ import com.newsblur.view.FlowLayout;
import com.newsblur.view.NewsblurWebview;
import com.newsblur.view.ReadingScrollView;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -244,7 +247,6 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
HitTestResult result = web.getHitTestResult();
if (result.getType() == HitTestResult.IMAGE_TYPE ||
result.getType() == HitTestResult.SRC_ANCHOR_TYPE ||
result.getType() == HitTestResult.SRC_IMAGE_ANCHOR_TYPE ) {
// if the long-pressed item was an image, see if we can pop up a little dialogue
// that presents the alt text. Note that images wrapped in links tend to get detected
@ -282,6 +284,13 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
}
});
builder.show();
} else if (result.getType() == HitTestResult.SRC_ANCHOR_TYPE) {
String url = result.getExtra();
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_SUBJECT, UIUtils.fromHtml(story.title).toString());
intent.putExtra(Intent.EXTRA_TEXT, url);
startActivity(Intent.createChooser(intent, "Share using"));
} else {
super.onCreateContextMenu(menu, v, menuInfo);
}
@ -341,9 +350,9 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
return true;
} else if (item.getItemId() == R.id.menu_reading_save) {
if (story.starred) {
FeedUtils.setStorySaved(story, false, getActivity());
FeedUtils.setStorySaved(story, false, getActivity(), null);
} else {
FeedUtils.setStorySaved(story, true, getActivity());
FeedUtils.setStorySaved(story.storyHash, true, getActivity());
}
return true;
} else if (item.getItemId() == R.id.menu_reading_markunread) {
@ -373,9 +382,9 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
@OnClick(R.id.save_story_button) void clickSave() {
if (story.starred) {
FeedUtils.setStorySaved(story, false, getActivity());
FeedUtils.setStorySaved(story.storyHash, false, getActivity());
} else {
FeedUtils.setStorySaved(story,true, getActivity());
FeedUtils.setStorySaved(story.storyHash,true, getActivity());
}
}
@ -760,6 +769,8 @@ public class ReadingItemFragment extends NbFragment implements PopupMenu.OnMenuI
builder.append(font.forWebView(currentSize));
builder.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"reading.css\" />");
if (themeValue == ThemeValue.LIGHT) {
// builder.append("<meta name=\"color-scheme\" content=\"light\"/>");
// builder.append("<meta name=\"supported-color-schemes\" content=\"light\"/>");
builder.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"light_reading.css\" />");
} else if (themeValue == ThemeValue.DARK) {
builder.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"dark_reading.css\" />");

View file

@ -116,7 +116,8 @@ public class APIConstants {
public static final String PARAMETER_TAG = "tag";
public static final String PARAMETER_APPROVED_FEEDS = "approved_feeds";
public static final String PARAMETER_NOTIFICATION_TYPES = "notification_types";
public static final String PARAMETER_NOTIFICATION_FILTER = "notification_filter";
public static final String PAREMETER_USER_TAGS = "user_tags";
public static final String PARAMETER_NOTIFICATION_FILTER = "notification_filter";
public static final String PARAMETER_RESET_FETCH = "reset_fetch";
public static final String PARAMETER_INFREQUENT = "infrequent";
public static final String PARAMETER_FEEDTITLE = "feed_title";

View file

@ -198,9 +198,12 @@ public class APIManager {
return response.getResponse(gson, NewsBlurResponse.class);
}
public NewsBlurResponse markStoryAsStarred(String storyHash) {
public NewsBlurResponse markStoryAsStarred(String storyHash, List<String> userTags) {
ValueMultimap values = new ValueMultimap();
values.put(APIConstants.PARAMETER_STORY_HASH, storyHash);
for (String tag : userTags) {
values.put(APIConstants.PAREMETER_USER_TAGS, tag);
}
APIResponse response = post(buildUrl(APIConstants.PATH_MARK_STORY_AS_STARRED), values);
return response.getResponse(gson, NewsBlurResponse.class);
}

View file

@ -1,7 +1,9 @@
package com.newsblur.util;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import android.content.Context;
@ -34,6 +36,12 @@ public class FeedUtils {
public static ImageLoader thumbnailLoader;
public static FileCache storyImageCache;
// this is gross, but the feedset can't hold a folder title
// without being mistaken for a folder feed.
// The alternative is to pass it through alongside all instances
// of the feedset
public static String currentFolderName;
public static void offerInitContext(Context context) {
if (dbHelper == null) {
dbHelper = new BlurDatabaseHelper(context.getApplicationContext());
@ -76,15 +84,22 @@ public class FeedUtils {
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public static void setStorySaved(final Story story, final boolean saved, final Context context) {
setStorySaved(story.storyHash, saved, context);
public static void setStorySaved(final String storyHash, final boolean saved, final Context context) {
List<String> userTags = new ArrayList<>();
if(FeedUtils.currentFolderName != null){
userTags.add(FeedUtils.currentFolderName);
}
setStorySaved(storyHash, saved, context, userTags);
}
public static void setStorySaved(final Story story, final boolean saved, final Context context, final List<String> userTags) {
setStorySaved(story.storyHash, saved, context, userTags);
}
public static void setStorySaved(final String storyHash, final boolean saved, final Context context) {
public static void setStorySaved(final String storyHash, final boolean saved, final Context context, final List<String> userTags) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... arg) {
ReadingAction ra = (saved ? ReadingAction.saveStory(storyHash) : ReadingAction.unsaveStory(storyHash));
ReadingAction ra = (saved ? ReadingAction.saveStory(storyHash, userTags) : ReadingAction.unsaveStory(storyHash));
ra.doLocal(dbHelper);
NbActivity.updateAllActivities(NbActivity.UPDATE_STORY);
dbHelper.enqueueAction(ra);
@ -307,6 +322,7 @@ public class FeedUtils {
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setType("text/plain");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Intent.EXTRA_SUBJECT, UIUtils.fromHtml(story.title).toString());
intent.putExtra(Intent.EXTRA_TEXT, String.format(context.getResources().getString(R.string.send_brief), new Object[]{UIUtils.fromHtml(story.title), story.permalink}));
context.startActivity(Intent.createChooser(intent, "Send using"));
}

View file

@ -59,6 +59,7 @@ public class PrefConstants {
public static final String STORIES_MARK_READ_ON_SCROLL = "pref_mark_read_on_scroll";
public static final String STORIES_SHOW_PREVIEWS = "pref_show_content_preview";
public static final String STORIES_SHOW_THUMBNAILS = "pref_show_thumbnails";
public static final String STORIES_THUMBNAILS_STYLE = "pref_thumbnails_style";
public static final String ENABLE_OFFLINE = "enable_offline";
public static final String ENABLE_IMAGE_PREFETCH = "enable_image_prefetch";

View file

@ -685,11 +685,18 @@ public class PrefsUtils {
return prefs.getBoolean(PrefConstants.STORIES_SHOW_PREVIEWS, true);
}
public static boolean isShowThumbnails(Context context) {
private static boolean isShowThumbnails(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
return prefs.getBoolean(PrefConstants.STORIES_SHOW_THUMBNAILS, true);
}
public static ThumbnailStyle getThumbnailStyle(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
boolean isShowThumbnails = isShowThumbnails(context);
ThumbnailStyle defValue = isShowThumbnails ? ThumbnailStyle.LARGE : ThumbnailStyle.OFF;
return ThumbnailStyle.valueOf(prefs.getString(PrefConstants.STORIES_THUMBNAILS_STYLE, defValue.toString()));
}
public static boolean isAutoOpenFirstUnread(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PrefConstants.PREFERENCES, 0);
return prefs.getBoolean(PrefConstants.STORIES_AUTO_OPEN_FIRST, false);

View file

@ -59,6 +59,7 @@ public class ReadingAction implements Serializable {
private String replyId;
private String notifyFilter;
private List<String> notifyTypes;
private List<String> userTags;
private Classifier classifier;
private String newFeedName;
@ -96,10 +97,15 @@ public class ReadingAction implements Serializable {
return ra;
}
public static ReadingAction saveStory(String hash) {
public static ReadingAction saveStory(String hash, List<String> userTags) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.SAVE;
ra.storyHash = hash;
if (userTags == null) {
ra.userTags = new ArrayList<String>();
} else {
ra.userTags = userTags;
}
return ra;
}
@ -289,7 +295,7 @@ public class ReadingAction implements Serializable {
break;
case SAVE:
result = apiManager.markStoryAsStarred(storyHash);
result = apiManager.markStoryAsStarred(storyHash, userTags);
break;
case UNSAVE:

View file

@ -6,6 +6,7 @@ import android.text.format.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
/**
* Created by mark on 04/02/2014.
@ -67,6 +68,20 @@ public class StoryUtils {
int month = storyCalendar.get(Calendar.DAY_OF_MONTH);
SimpleDateFormat timeFormat = getTimeFormat(context);
Locale locale = context.getResources().getConfiguration().locale;
if(!locale.getLanguage().equals("en")){
String pattern = null;
if (storyDate.getTime() > beginningOfMonth.getTime()) {
// localized pattern, without year
pattern = android.text.format.DateFormat.getBestDateTimePattern(locale, "EEEE MMMM d");
}else {
// localized pattern, with year
pattern = android.text.format.DateFormat.getBestDateTimePattern(locale, "EEEE MMMM d yyyy");
}
return DateFormat.format(pattern, storyDate).toString() + " " + timeFormat.format(storyDate);
}
if (storyDate.getTime() > midnightToday.getTime()) {
// Today, January 1st 00:00
@ -105,7 +120,7 @@ public class StoryUtils {
}
private static SimpleDateFormat getTimeFormat(Context context) {
if (DateFormat.is24HourFormat(context)) {
if (android.text.format.DateFormat.is24HourFormat(context)) {
return twentyFourHourFormat.get();
} else {
return twelveHourFormat.get();
@ -134,6 +149,17 @@ public class StoryUtils {
SimpleDateFormat timeFormat = getTimeFormat(context);
Locale locale = context.getResources().getConfiguration().locale;
if(!locale.getLanguage().equals("en")){
if (storyDate.getTime() > midnightToday.getTime()) {
return timeFormat.format(storyDate);
}else {
String pattern = android.text.format.DateFormat.getBestDateTimePattern(locale, "d MMM yyyy " + timeFormat.toPattern());
return DateFormat.format(pattern, storyDate).toString();
}
}
if (storyDate.getTime() > midnightToday.getTime()) {
// 00:00
return timeFormat.format(storyDate);

View file

@ -0,0 +1,8 @@
package com.newsblur.util;
public enum ThumbnailStyle {
LARGE,
SMALL,
OFF
}

View file

@ -1,9 +1,16 @@
upstream camo_server {
server 0.0.0.0:8081 fail_timeout=10 max_fails=3;
server 127.0.0.1:8081 fail_timeout=10 max_fails=3;
}
server {
listen 80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 80;
listen 443 ssl;
ssl_certificate /srv/newsblur/config/certificates/newsblur.com.pem;

View file

@ -1,4 +1,6 @@
bleach==3.1.0
BeautifulSoup==3.2.1
beautifulsoup4==4.8.0
boto==2.43.0
celery==3.1.25
chardet==3.0.4

View file

@ -74,8 +74,7 @@ NEWSBLUR.Models.Story = Backbone.Model.extend({
secure_content: function(content_attr) {
var content = this.get(content_attr);
if (window.location.protocol == 'https:' &&
NEWSBLUR.Globals.is_staff) {
if (window.location.protocol == 'https:') {
_.each(this.get('secure_image_urls'), function(secure_url, url) {
if (_.str.startsWith(url, "http://")) {
console.log(['Securing image url', url, secure_url]);

View file

@ -14,7 +14,7 @@ __all__ = ['Scrubber', 'SelectiveScriptScrubber', 'ScrubberWarning', 'Unapproved
import re, string
from urlparse import urljoin
from itertools import chain
from BeautifulSoup import BeautifulSoup, Comment
from bs4 import BeautifulSoup, Comment
def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
"""Converts any URLs in text into clickable links.
@ -157,21 +157,23 @@ class Scrubber(object):
continue
# Remove disallowed attributes
attrs = []
for k, v in node.attrs:
if not v:
continue
attrs = {}
if hasattr(node, 'attrs') and isinstance(node.attrs, dict):
for k, v in node.attrs.items():
if not v:
continue
if k.lower() not in self.allowed_attributes:
continue
if k.lower() not in self.allowed_attributes:
continue
# TODO: This probably needs to be more robust
v2 = v.lower()
if any(x in v2 for x in ('javascript:', 'vbscript:', 'expression(')):
continue
# TODO: This probably needs to be more robust
if isinstance(v, str):
v2 = v.lower()
if any(x in v2 for x in ('javascript:', 'vbscript:', 'expression(')):
continue
attrs.append((k,v))
node.attrs = attrs
attrs[k] = v
node.attrs = attrs
self._remove_nodes(toremove)
@ -203,10 +205,10 @@ class Scrubber(object):
def _scrub_tag_a(self, a):
if self.nofollow:
a['rel'] = "nofollow"
a['rel'] = ["nofollow"]
if not a.get('class', None):
a['class'] = "external"
a['class'] = ["external"]
self._clean_path(a, 'href')
@ -223,17 +225,18 @@ class Scrubber(object):
self._clean_path(img, 'src')
def _scrub_tag_font(self, node):
attrs = []
for k, v in node.attrs:
if k.lower() == 'size' and v.startswith('+'):
# Remove "size=+0"
continue
attrs.append((k, v))
node.attrs = attrs
attrs = {}
if hasattr(node, 'attrs') and isinstance(node.attrs, dict):
for k, v in node.attrs.items():
if k.lower() == 'size' and v.startswith('+'):
# Remove "size=+0"
continue
attrs[k] = v
node.attrs = attrs
if len(node.attrs) == 0:
# IE renders font tags with no attributes differently then other browsers so remove them
return "keep_contents"
if len(node.attrs) == 0:
# IE renders font tags with no attributes differently then other browsers so remove them
return "keep_contents"
def _scrub_html_pre(self, html):
"""Process the html before sanitization"""
@ -251,7 +254,7 @@ class Scrubber(object):
toremove = []
for tag_name, scrubbers in self.tag_scrubbers.items():
for node in soup(tag_name):
for node in soup.find_all(tag_name):
for scrub in scrubbers:
remove = scrub(node)
if remove:
@ -267,9 +270,8 @@ class Scrubber(object):
"""Return a sanitized version of the given html."""
self.warnings = []
html = self._scrub_html_pre(html)
soup = BeautifulSoup(html)
soup = BeautifulSoup(html, features="lxml")
self._scrub_soup(soup)
html = unicode(soup)
return self._scrub_html_post(html)

View file

@ -366,7 +366,7 @@ def htmldiff(old_html, new_html):
return fixup_ins_del_tags(result)
def create_signed_url(base_url, hmac_key, url):
def create_camo_signed_url(base_url, hmac_key, url):
"""Create a camo signed URL for the specified image URL
Args:
base_url: Base URL of the camo installation