Compare commits

...

14 Commits

Author SHA1 Message Date
Frédéric Guillot 73a0a25b6c Update ChangeLog 2018-02-19 20:15:57 -08:00
Sebastian Korotkiewicz 2786b8f163 Update Polish translation 2018-02-19 15:15:03 -08:00
Frédéric Guillot 9292d5d604 Handle Atom feeds with HTML title 2018-02-17 12:21:58 -08:00
Frédéric Guillot 3ba280e10c There are more than 2 plurals in Polish 2018-02-17 11:43:37 -08:00
Sebastian Korotkiewicz 4eceeaaca4 Add Polish translation 2018-02-17 11:29:14 -08:00
Frédéric Guillot 205aef595b Change color of <q> tags for black theme 2018-02-10 22:35:02 -08:00
Frédéric Guillot a006a93a04 Add indexes 2018-02-10 22:30:43 -08:00
stratmaster 30e80c675e Update German translation 2018-02-09 18:20:06 -08:00
Frédéric Guillot dda9114692 Improve error handling for HTTP client 2018-02-08 18:16:54 -08:00
stratmaster 16c2dc4a8c Add missing German string 2018-02-08 12:41:14 -08:00
Frédéric Guillot 7b0bfd9308 Strip invalid XML characters to avoid parsing errors 2018-02-07 20:57:56 -08:00
Frédéric Guillot c6fd9eb9b1 Remove period for feed errors 2018-02-07 19:10:36 -08:00
Frédéric Guillot 0fb87eba3f Improve error handling when the response is empty 2018-02-07 18:47:47 -08:00
stratmaster 1e70ca1a19 Add missing French translation 2018-02-07 18:05:58 -08:00
18 changed files with 713 additions and 46 deletions
+10
View File
@@ -1,3 +1,13 @@
Version 2.0.3 (Feb 19, 2018)
----------------------------
* Add Polish translation
* Change color of <q> tags for black theme
* Add database indexes (don't forget to run database migrations)
* Handle Atom feeds with HTML title
* Strip invalid XML characters to avoid parsing errors
* Improve error handling for HTTP client
Version 2.0.2 (Feb 5, 2018)
---------------------------
+37 -2
View File
@@ -7,21 +7,36 @@ package http
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"
"time"
"github.com/miniflux/miniflux/errors"
"github.com/miniflux/miniflux/logger"
"github.com/miniflux/miniflux/timer"
"github.com/miniflux/miniflux/version"
)
const requestTimeout = 300
const maxBodySize = 1024 * 1024 * 15
const (
// 20 seconds max.
requestTimeout = 20
// 15MB max.
maxBodySize = 1024 * 1024 * 15
)
var (
errInvalidCertificate = "Invalid SSL certificate (original error: %q)"
errTemporaryNetworkOperation = "This website is temporarily unreachable (original error: %q)"
errPermanentNetworkOperation = "This website is permanently unreachable (original error: %q)"
errRequestTimeout = "Website unreachable, the request timed out after %d seconds"
)
// Client is a HTTP Client :)
type Client struct {
@@ -77,6 +92,26 @@ func (c *Client) executeRequest(request *http.Request) (*Response, error) {
client := c.buildClient()
resp, err := client.Do(request)
if err != nil {
if uerr, ok := err.(*url.Error); ok {
switch uerr.Err.(type) {
case x509.CertificateInvalidError, x509.HostnameError:
err = errors.NewLocalizedError(errInvalidCertificate, uerr.Err)
case *net.OpError:
if uerr.Err.(*net.OpError).Temporary() {
err = errors.NewLocalizedError(errTemporaryNetworkOperation, uerr.Err)
} else {
err = errors.NewLocalizedError(errPermanentNetworkOperation, uerr.Err)
}
case net.Error:
nerr := uerr.Err.(net.Error)
if nerr.Timeout() {
err = errors.NewLocalizedError(errRequestTimeout, requestTimeout)
} else if nerr.Temporary() {
err = errors.NewLocalizedError(errTemporaryNetworkOperation, nerr)
}
}
}
return nil, err
}
Regular → Executable
+1
View File
@@ -30,5 +30,6 @@ func AvailableLanguages() map[string]string {
"en_US": "English",
"fr_FR": "Français",
"de_DE": "Deutsch",
"pl_PL": "Polski",
}
}
Regular → Executable
+249 -16
View File
@@ -1,13 +1,13 @@
// Code generated by go generate; DO NOT EDIT.
// 2018-02-01 14:52:01.163022 +0100 CET m=+0.026333224
// 2018-02-19 22:49:00.3105786 +0100 STD m=+0.012978001
package locale
var translations = map[string]string{
"de_DE": `{
"plural.feed.error_count": [
"%d Error",
"%d Errors"
"%d Fehler",
"%d Fehler"
],
"plural.categories.feed_count": [
"Es gibt %d Abonnement.",
@@ -90,8 +90,8 @@ var translations = map[string]string{
"Last Login": "Letzte Anmeldung",
"Yes": "Ja",
"No": "Nein",
"This feed already exists (%s).": "Diese Abonnement existiert bereits (%s).",
"Unable to fetch feed (statusCode=%d).": "Abonnement konnte nicht abgerufen werden (code=%d).",
"This feed already exists (%s)": "Diese Abonnement existiert bereits (%s)",
"Unable to fetch feed (statusCode=%d)": "Abonnement konnte nicht abgerufen werden (code=%d)",
"Unable to open this link: %v": "Dieser Link konnte nicht geöffnet werden: %v",
"Unable to analyze this page: %v": "Diese Seite konnte nicht analysiert werden: %v",
"Unable to find any subscription.": "Es wurden keine Abonnements gefunden.",
@@ -114,14 +114,14 @@ var translations = map[string]string{
"Invalid username or password.": "Benutzername oder Passwort ungültig.",
"Never": "Niemals",
"Unable to execute request: %v": "Diese Anfrage konnte nicht ausgeführt werden: %v",
"Last Parsing Error": "Letzter Analyse Error",
"Last Parsing Error": "Letzter Analysefehler",
"There is a problem with this feed": "Es gibt ein Problem mit diesem Abonnement",
"Unable to parse OPML file: %v.": "OPML Datei konnte nicht gelesen werden: %v.",
"Unable to parse RSS feed: %v.": "RSS Feed konnte nicht gelesen werden: %v.",
"Unable to parse Atom feed: %v.": "Atom Feed konnte nicht gelesen werden: %v.",
"Unable to parse JSON feed: %v.": "JSON Feed konnte nicht gelesen werden: %v.",
"Unable to parse RDF feed: %v.": "RDF Feed konnte nicht gelesen werden: %v.",
"Unable to normalize encoding: %v.": "Zeichenkodierung konnte nicht normalisiert werden: %v.",
"Unable to normalize encoding: %v": "Zeichenkodierung konnte nicht normalisiert werden: %v",
"Unable to create this category.": "Diese Kategorie konnte nicht angelegt werden.",
"yes": "ja",
"no": "nein",
@@ -139,7 +139,7 @@ var translations = map[string]string{
"Sign in with Google": "Anmeldung mit Google",
"Unlink my Google account": "Google Konto abmelden",
"Link my Google account": "Google Konto verknüpfen",
"Category not found for this user.": "Diese Kategorie existiert nicht für diesen Benutzer.",
"Category not found for this user": "Diese Kategorie existiert nicht für diesen Benutzer",
"Invalid theme.": "Dieses Thema ist fehlerhaft.",
"Entry Sorting": "Sortierung der Artikel",
"Older entries first": "Älteste Artikel zuerst",
@@ -215,7 +215,12 @@ var translations = map[string]string{
"Fever API endpoint:": "Fever API Endpunkt:",
"Miniflux API": "Miniflux API",
"API Endpoint": "API Endpunkt",
"Your account password": "Ihr Konto Passwort"
"Your account password": "Ihr Konto Passwort",
"This web page is empty": "Diese Webseite ist leer",
"Invalid SSL certificate (original error: %q)": "Ungültiges SSL-Zertifikat (ursprünglicher Fehler: %q)",
"This website is temporarily unreachable (original error: %q)": "Diese Webseite ist vorübergehend nicht erreichbar (ursprünglicher Fehler: %q)",
"This website is permanently unreachable (original error: %q)": "Diese Webseite ist dauerhaft nicht erreichbar (ursprünglicher Fehler: %q)",
"Website unreachable, the request timed out after %d seconds": "Webseite nicht erreichbar, die Anfrage endete nach %d Sekunden"
}
`,
"en_US": `{
@@ -314,8 +319,8 @@ var translations = map[string]string{
"Last Login": "Dernière connexion",
"Yes": "Oui",
"No": "Non",
"This feed already exists (%s).": "Cet abonnement existe déjà (%s).",
"Unable to fetch feed (statusCode=%d).": "Impossible de récupérer cet abonnement (code=%d).",
"This feed already exists (%s)": "Cet abonnement existe déjà (%s)",
"Unable to fetch feed (statusCode=%d)": "Impossible de récupérer cet abonnement (code=%d)",
"Unable to open this link: %v": "Impossible d'ouvrir ce lien : %v",
"Unable to analyze this page: %v": "Impossible d'analyzer cette page : %v",
"Unable to find any subscription.": "Impossible de trouver un abonnement.",
@@ -345,7 +350,7 @@ var translations = map[string]string{
"Unable to parse Atom feed: %v.": "Impossible de lire ce flux Atom: %v.",
"Unable to parse JSON feed: %v.": "Impossible de lire ce flux JSON: %v.",
"Unable to parse RDF feed: %v.": "Impossible de lire ce flux RDF: %v.",
"Unable to normalize encoding: %v.": "Impossible de normaliser l'encodage : %v.",
"Unable to normalize encoding: %v": "Impossible de normaliser l'encodage : %v",
"Unable to create this category.": "Impossible de créer cette catégorie.",
"yes": "oui",
"no": "non",
@@ -363,7 +368,7 @@ var translations = map[string]string{
"Sign in with Google": "Se connecter avec Google",
"Unlink my Google account": "Dissocier mon compte Google",
"Link my Google account": "Associer mon compte Google",
"Category not found for this user.": "Cette catégorie n'existe pas pour cet utilisateur.",
"Category not found for this user": "Cette catégorie n'existe pas pour cet utilisateur",
"Invalid theme.": "Le thème est invalide.",
"Entry Sorting": "Ordre des éléments",
"Older entries first": "Ancien éléments en premier",
@@ -414,6 +419,7 @@ var translations = map[string]string{
"Items Navigation": "Naviguation entre les éléments",
"Go to previous item": "Élément précédent",
"Go to next item": "Élément suivant",
"Pages Navigation": "Naviguation entre les pages",
"Go to previous page": "Page précédente",
"Go to next page": "Page suivante",
"Open selected item": "Ouvrir élément sélectionné",
@@ -438,13 +444,240 @@ var translations = map[string]string{
"Fever API endpoint:": "Point de terminaison de l'API Fever :",
"Miniflux API": "API de Miniflux",
"API Endpoint": "Point de terminaison de l'API",
"Your account password": "Le mot de passe de votre compte"
"Your account password": "Le mot de passe de votre compte",
"This web page is empty": "Cette page web est vide",
"Invalid SSL certificate (original error: %q)": "Certificat SSL invalide (erreur originale : %q)",
"This website is temporarily unreachable (original error: %q)": "Ce site web est temporairement injoignable (erreur originale : %q)",
"This website is permanently unreachable (original error: %q)": "Ce site web n'est pas joignable de façon permanente (erreur originale : %q)",
"Website unreachable, the request timed out after %d seconds": "Site web injoignable, la requête à échouée après %d secondes"
}
`,
"pl_PL": `{
"plural.feed.error_count": [
"%d błąd",
"%d błąd",
"%d błędów"
],
"plural.categories.feed_count": [
"Jest %d kanał.",
"Są %d kanały.",
"Jest %d kanałów."
],
"Username": "Nazwa użytkownika",
"Password": "Hasło",
"Unread": "Nieprzeczytane",
"History": "Historia",
"Feeds": "Kanały",
"Categories": "Kategorie",
"Settings": "Ustawienia",
"Logout": "Wyloguj się",
"Next": "Następny",
"Previous": "Poprzedni",
"New Subscription": "Nowa subskrypcja",
"Import": "Importuj",
"Export": "Eksportuj",
"There is no category. You must have at least one category.": "Nie ma żadnej kategorii. Musisz mieć co najmniej jedną kategorię",
"URL": "URL",
"Category": "Kategoria",
"Find a subscription": "Znajdź subskrypcję",
"Loading...": "Ładowanie...",
"Create a category": "Utwórz kategorię",
"There is no category.": "Nie masz żadnej kategorii",
"Edit": "Edytuj",
"Remove": "Usuń",
"No feed.": "Brak kanałów.",
"There is no article in this category.": "W tej kategorii nie ma żadnych artykułów",
"Original": "Oryginalny artykuł",
"Mark this page as read": "Oznacz jako przeczytane",
"not yet": "jeszcze nie",
"just now": "przed chwilą",
"1 minute ago": "minutę temu",
"%d minutes ago": "%d minut temu",
"1 hour ago": "godzinę temu",
"%d hours ago": "%d godzin temu",
"yesterday": "wczoraj",
"%d days ago": "%d dni temu",
"%d weeks ago": "%d tygodni temu",
"%d months ago": "%d miesięcy temu",
"%d years ago": "%d lat temu",
"Date": "Data",
"IP Address": "Adres IP",
"User Agent": "User Agent",
"Actions": "Działania",
"Current session": "Bieżąca sesja",
"Sessions": "Sesje",
"Users": "Użytkownicy",
"Add user": "Dodaj użytkownika",
"Choose a Subscription": "Wybierz subskrypcję",
"Subscribe": "Subskrypcja",
"New Category": "Nowa kategoria",
"Title": "Tytuł",
"Save": "Zapisz",
"or": "lub",
"cancel": "anuluj",
"New User": "Nowy użytkownik",
"Confirmation": "Potwierdź",
"Administrator": "Administrator",
"Edit Category: %s": "Edycja Kategorii: %s",
"Update": "Zaktualizuj",
"Edit Feed: %s": "Edytuj kanał: %s",
"There is no category!": "Nie ma żadnej kategorii!",
"Edit user: %s": "Edytuj użytkownika: %s",
"There is no article for this feed.": "Nie ma artykułu dla tego kanału.",
"Add subscription": "Dodaj subskrypcję",
"You don't have any subscription.": "Nie masz żadnej subskrypcji",
"Last check:": "Ostatnia aktualizacja:",
"Refresh": "Odśwież",
"There is no history at the moment.": "Obecnie nie ma żadnej historii.",
"OPML file": "Plik OPML",
"Sign In": "Zaloguj się",
"Sign in": "Zaloguj się",
"Theme": "Wygląd",
"Timezone": "Strefa czasowa",
"Language": "Język",
"There is no unread article.": "Nie ma żadnych nieprzeczytanych artykułów.",
"You are the only user.": "Jesteś jedynym użytkownikiem.",
"Last Login": "Ostatnie logowanie",
"Yes": "Tak",
"No": "Nie",
"This feed already exists (%s)": "Ten kanał już istnieje (%s)",
"Unable to fetch feed (statusCode=%d)": "Kanał nie mógł zostać pobrany (kod=%d)",
"Unable to open this link: %v": "Nie można było otworzyć tego linku: %v",
"Unable to analyze this page: %v": "Nie można przeanalizować tej strony: %v",
"Unable to find any subscription.": "Nie znaleziono żadnych subskrypcji.",
"The URL and the category are mandatory.": "URL i kategoria są obowiązkowe.",
"All fields are mandatory.": "Wszystkie pola są obowiązkowe.",
"Passwords are not the same.": "Hasła nie są identyczne.",
"You must use at least 6 characters.": "Musisz użyć co najmniej 6 znaków.",
"The username is mandatory.": "Nazwa użytkownika jest obowiązkowa.",
"The username, theme, language and timezone fields are mandatory.": "Pola nazwy użytkownika, tematu, języka i strefy czasowej są obowiązkowe.",
"The title is mandatory.": "Tytuł jest obowiązkowy.",
"About": "O stronie",
"version": "Wersja",
"Version:": "Wersja :",
"Build Date:": "Data opracowania:",
"Author:": "Autor:",
"Authors": "Autorzy",
"License:": "Licencja:",
"Attachments": "Załączniki",
"Download": "Pobierz",
"Invalid username or password.": "Nieprawidłowa nazwa użytkownika lub hasło.",
"Never": "Nigdy",
"Unable to execute request: %v": "To polecenie nie mogło zostać wykonane: %v",
"Last Parsing Error": "Ostatni błąd analizy",
"There is a problem with this feed": "Z tym kanałem jest problem",
"Unable to parse OPML file: %v.": "Plik OPML nie mógł zostać odczytany: %v.",
"Unable to parse RSS feed: %v.": "Nie można było odczytać kanału RSS: %v.",
"Unable to parse Atom feed: %v.": "Nie można było odczytać kanału Atom: %v.",
"Unable to parse JSON feed: %v.": "Nie można było odczytać kanału JSON: %v.",
"Unable to parse RDF feed: %v.": "Nie można było odczytać kanału RDF: %v.",
"Unable to normalize encoding: %v": "Kodowanie znaków nie mogło zostać znormalizowane: %v",
"Unable to create this category.": "Ta kategoria nie mogła zostać utworzona.",
"yes": "tak",
"no": "nie",
"Are you sure?": "Czy jesteś pewny?",
"Work in progress...": "W toku...",
"This user already exists.": "Ten użytkownik już istnieje.",
"This category already exists.": "Ta kategoria już istnieje.",
"Unable to update this category.": "Ta kategoria nie mogła zostać zaktualizowana.",
"Integrations": "Usługi",
"Bookmarklet": "Bookmarklet",
"Drag and drop this link to your bookmarks.": "Przeciągnij i upuść to łącze do zakładek.",
"This special link allows you to subscribe to a website directly by using a bookmark in your web browser.": "Ten link umożliwia subskrypcję strony internetowej bezpośrednio za pomocą zakładki w przeglądarce internetowej",
"Add to Miniflux": "Dodaj do Miniflux",
"Refresh all feeds in background": "Odśwież wszystkie subskrypcje w tle",
"Sign in with Google": "Zaloguj przez Google",
"Unlink my Google account": "Odłącz moje konto Google",
"Link my Google account": "Połącz z moim kontem Google",
"Category not found for this user": "Kategoria nie znaleziona dla tego użytkownika",
"Invalid theme.": "Ten temat jest nieprawidłowy.",
"Entry Sorting": "Sortowanie artykułów",
"Older entries first": "Najstarsze wpisy jako pierwsze",
"Recent entries first": "Najnowsze wpisy jako pierwsze",
"Saving...": "Zapisywanie...",
"Done!": "Gotowe!",
"Save this article": "Zapisz ten artykuł",
"Mark bookmark as unread": "Zaznacz zakładkę jako nieprzeczytaną",
"Pinboard Tags": "Pinboard Tags",
"Pinboard API Token": "Token Pinboard API",
"Save articles to Pinboard": "Zapisz artykuł w Pinboard",
"Save articles to Instapaper": "Zapisz artykuł w Instapaper",
"Instapaper Username": "Login do Instapaper",
"Instapaper Password": "Hasło do Instapaper",
"Activate Fever API": "Aktywuj Fever API",
"Fever Username": "Login do Fever",
"Fever Password": "Hasło do Fever",
"Fetch original content": "Pobierz oryginalną treść",
"Scraper Rules": "Zasady ekstrakcji",
"Rewrite Rules": "Reguły zapisu",
"Preferences saved!": "Ustawienia zapisane!",
"Your external account is now linked !": "Twoje zewnętrzne konto jest teraz połączone!",
"Save articles to Wallabag": "Zapisz artykuły do Wallabag",
"Wallabag API Endpoint": "Wallabag URL",
"Wallabag Client ID": "Wallabag Client-ID",
"Wallabag Client Secret": "Wallabag Client Secret",
"Wallabag Username": "Login do Wallabag",
"Wallabag Password": "Hasło do Wallabag",
"Keyboard Shortcut: %s": "Skróty klawiszowe: %s",
"Favorites": "Ulubione",
"Star": "Oznacz gwiazdką",
"Unstar": "Usuń gwiazdkę",
"Starred": "Oznaczone gwiazdką",
"There is no bookmark at the moment.": "Obecnie nie ma żadnych zakładek.",
"Last checked:": "Ostatnio sprawdzone:",
"ETag header:": "Nagłówek ETag:",
"LastModified header:": "Ostatnio zmienione:",
"None": "Brak",
"Keyboard Shortcuts": "Skróty klawiszowe",
"Sections Navigation": "Nawigacja między punktami menu",
"Go to unread": "Przejdź do nieprzeczytanych artykułów",
"Go to bookmarks": "Przejdź do zakładek",
"Go to history": "Przejdź do historii",
"Go to feeds": "Przejdź do kanałów",
"Go to categories": "Przejdź do kategorii",
"Go to settings": "Przejdź do ustawień",
"Show keyboard shortcuts": "Pokaż listę skrótów klawiszowych",
"Items Navigation": "Nawigacja między artykułami",
"Go to previous item": "Przejdź do poprzedniego artykułu",
"Go to next item": "Przejdź do następnego punktu artykułu",
"Pages Navigation": "Nawigacja między stronami",
"Go to previous page": "Przejdź do poprzedniej strony",
"Go to next page": "Przejdź do następnej strony",
"Open selected item": "Otwórz zaznaczony artykuł",
"Open original link": "Otwórz oryginalny artykuł",
"Toggle read/unread": "Oznacz jako przeczytane/nieprzeczytane",
"Mark current page as read": "Zaznacz aktualną stronę jako przeczytaną",
"Download original content": "Pobierz oryginalną zawartość",
"Toggle bookmark": "Dodaj/usuń zakładki",
"Close modal dialog": "Zamknij listę skrótów klawiszowych",
"Save article": "Zapisz artykuł",
"There is already someone associated with this provider!": "Już ktoś jest powiązany z tym dostawcą!",
"There is already someone else with the same Fever username!": "Już ktoś inny używa tej nazwy użytkownika Fever!",
"Mark all as read": "Oznacz wszystko jako przeczytane",
"This feed is empty": "Ten kanał jest pusty",
"Flush history": "Usuń historię",
"Site URL": "URL strony",
"Feed URL": "URL kanału",
"Logged as %s": "Zalogowany jako %s",
"Unread Items": "Nieprzeczytane",
"Change entry status": "Zmień status artykułu",
"Read": "Przeczytane",
"Fever API endpoint:": "Fever API endpoint:",
"Miniflux API": "Miniflux API",
"API Endpoint": "API endpoint",
"Your account password": "Hasło konta",
"This web page is empty": "Ta strona jest pusta",
"Invalid SSL certificate (original error: %q)": "Certyfikat SSL jest nieprawidłowy (błąd: %q)",
"This website is temporarily unreachable (original error: %q)": "Ta strona jest tymczasowo niedostępna (błąd: %q)",
"This website is permanently unreachable (original error: %q)": "Ta strona jest niedostępna (błąd: %q)",
"Website unreachable, the request timed out after %d seconds": "Strona internetowa nieosiągalna, żądanie wygasło po %d sekundach"
}
`,
}
var translationsChecksums = map[string]string{
"de_DE": "f4a66bffedb7bf99294281da5a1fa78901509db67a9fba15a160b656feb1425a",
"de_DE": "53f7637318ac418ce0e3dd483923dab39ff4f4062b4909e5e03efcc3b693e5d6",
"en_US": "6fe95384260941e8a5a3c695a655a932e0a8a6a572c1e45cb2b1ae8baa01b897",
"fr_FR": "e6305fd54508a4f54d630e3ef231a8eadc2335ed0ffac7b5266b2ec751824e28",
"fr_FR": "ae61f82ac14bc2c6c6a3c2d38cf1ad8309ac2eef19b0726b2969ac155ccddc14",
"pl_PL": "9c10899ec62f97ebb6d5d4d88cde8c68ac584e514ce840b51ba3aeff9ea3efe3",
}
+13 -8
View File
@@ -1,7 +1,7 @@
{
"plural.feed.error_count": [
"%d Error",
"%d Errors"
"%d Fehler",
"%d Fehler"
],
"plural.categories.feed_count": [
"Es gibt %d Abonnement.",
@@ -84,8 +84,8 @@
"Last Login": "Letzte Anmeldung",
"Yes": "Ja",
"No": "Nein",
"This feed already exists (%s).": "Diese Abonnement existiert bereits (%s).",
"Unable to fetch feed (statusCode=%d).": "Abonnement konnte nicht abgerufen werden (code=%d).",
"This feed already exists (%s)": "Diese Abonnement existiert bereits (%s)",
"Unable to fetch feed (statusCode=%d)": "Abonnement konnte nicht abgerufen werden (code=%d)",
"Unable to open this link: %v": "Dieser Link konnte nicht geöffnet werden: %v",
"Unable to analyze this page: %v": "Diese Seite konnte nicht analysiert werden: %v",
"Unable to find any subscription.": "Es wurden keine Abonnements gefunden.",
@@ -108,14 +108,14 @@
"Invalid username or password.": "Benutzername oder Passwort ungültig.",
"Never": "Niemals",
"Unable to execute request: %v": "Diese Anfrage konnte nicht ausgeführt werden: %v",
"Last Parsing Error": "Letzter Analyse Error",
"Last Parsing Error": "Letzter Analysefehler",
"There is a problem with this feed": "Es gibt ein Problem mit diesem Abonnement",
"Unable to parse OPML file: %v.": "OPML Datei konnte nicht gelesen werden: %v.",
"Unable to parse RSS feed: %v.": "RSS Feed konnte nicht gelesen werden: %v.",
"Unable to parse Atom feed: %v.": "Atom Feed konnte nicht gelesen werden: %v.",
"Unable to parse JSON feed: %v.": "JSON Feed konnte nicht gelesen werden: %v.",
"Unable to parse RDF feed: %v.": "RDF Feed konnte nicht gelesen werden: %v.",
"Unable to normalize encoding: %v.": "Zeichenkodierung konnte nicht normalisiert werden: %v.",
"Unable to normalize encoding: %v": "Zeichenkodierung konnte nicht normalisiert werden: %v",
"Unable to create this category.": "Diese Kategorie konnte nicht angelegt werden.",
"yes": "ja",
"no": "nein",
@@ -133,7 +133,7 @@
"Sign in with Google": "Anmeldung mit Google",
"Unlink my Google account": "Google Konto abmelden",
"Link my Google account": "Google Konto verknüpfen",
"Category not found for this user.": "Diese Kategorie existiert nicht für diesen Benutzer.",
"Category not found for this user": "Diese Kategorie existiert nicht für diesen Benutzer",
"Invalid theme.": "Dieses Thema ist fehlerhaft.",
"Entry Sorting": "Sortierung der Artikel",
"Older entries first": "Älteste Artikel zuerst",
@@ -209,5 +209,10 @@
"Fever API endpoint:": "Fever API Endpunkt:",
"Miniflux API": "Miniflux API",
"API Endpoint": "API Endpunkt",
"Your account password": "Ihr Konto Passwort"
"Your account password": "Ihr Konto Passwort",
"This web page is empty": "Diese Webseite ist leer",
"Invalid SSL certificate (original error: %q)": "Ungültiges SSL-Zertifikat (ursprünglicher Fehler: %q)",
"This website is temporarily unreachable (original error: %q)": "Diese Webseite ist vorübergehend nicht erreichbar (ursprünglicher Fehler: %q)",
"This website is permanently unreachable (original error: %q)": "Diese Webseite ist dauerhaft nicht erreichbar (ursprünglicher Fehler: %q)",
"Website unreachable, the request timed out after %d seconds": "Webseite nicht erreichbar, die Anfrage endete nach %d Sekunden"
}
+11 -5
View File
@@ -84,8 +84,8 @@
"Last Login": "Dernière connexion",
"Yes": "Oui",
"No": "Non",
"This feed already exists (%s).": "Cet abonnement existe déjà (%s).",
"Unable to fetch feed (statusCode=%d).": "Impossible de récupérer cet abonnement (code=%d).",
"This feed already exists (%s)": "Cet abonnement existe déjà (%s)",
"Unable to fetch feed (statusCode=%d)": "Impossible de récupérer cet abonnement (code=%d)",
"Unable to open this link: %v": "Impossible d'ouvrir ce lien : %v",
"Unable to analyze this page: %v": "Impossible d'analyzer cette page : %v",
"Unable to find any subscription.": "Impossible de trouver un abonnement.",
@@ -115,7 +115,7 @@
"Unable to parse Atom feed: %v.": "Impossible de lire ce flux Atom: %v.",
"Unable to parse JSON feed: %v.": "Impossible de lire ce flux JSON: %v.",
"Unable to parse RDF feed: %v.": "Impossible de lire ce flux RDF: %v.",
"Unable to normalize encoding: %v.": "Impossible de normaliser l'encodage : %v.",
"Unable to normalize encoding: %v": "Impossible de normaliser l'encodage : %v",
"Unable to create this category.": "Impossible de créer cette catégorie.",
"yes": "oui",
"no": "non",
@@ -133,7 +133,7 @@
"Sign in with Google": "Se connecter avec Google",
"Unlink my Google account": "Dissocier mon compte Google",
"Link my Google account": "Associer mon compte Google",
"Category not found for this user.": "Cette catégorie n'existe pas pour cet utilisateur.",
"Category not found for this user": "Cette catégorie n'existe pas pour cet utilisateur",
"Invalid theme.": "Le thème est invalide.",
"Entry Sorting": "Ordre des éléments",
"Older entries first": "Ancien éléments en premier",
@@ -184,6 +184,7 @@
"Items Navigation": "Naviguation entre les éléments",
"Go to previous item": "Élément précédent",
"Go to next item": "Élément suivant",
"Pages Navigation": "Naviguation entre les pages",
"Go to previous page": "Page précédente",
"Go to next page": "Page suivante",
"Open selected item": "Ouvrir élément sélectionné",
@@ -208,5 +209,10 @@
"Fever API endpoint:": "Point de terminaison de l'API Fever :",
"Miniflux API": "API de Miniflux",
"API Endpoint": "Point de terminaison de l'API",
"Your account password": "Le mot de passe de votre compte"
"Your account password": "Le mot de passe de votre compte",
"This web page is empty": "Cette page web est vide",
"Invalid SSL certificate (original error: %q)": "Certificat SSL invalide (erreur originale : %q)",
"This website is temporarily unreachable (original error: %q)": "Ce site web est temporairement injoignable (erreur originale : %q)",
"This website is permanently unreachable (original error: %q)": "Ce site web n'est pas joignable de façon permanente (erreur originale : %q)",
"Website unreachable, the request timed out after %d seconds": "Site web injoignable, la requête à échouée après %d secondes"
}
+220
View File
@@ -0,0 +1,220 @@
{
"plural.feed.error_count": [
"%d błąd",
"%d błąd",
"%d błędów"
],
"plural.categories.feed_count": [
"Jest %d kanał.",
"Są %d kanały.",
"Jest %d kanałów."
],
"Username": "Nazwa użytkownika",
"Password": "Hasło",
"Unread": "Nieprzeczytane",
"History": "Historia",
"Feeds": "Kanały",
"Categories": "Kategorie",
"Settings": "Ustawienia",
"Logout": "Wyloguj się",
"Next": "Następny",
"Previous": "Poprzedni",
"New Subscription": "Nowa subskrypcja",
"Import": "Importuj",
"Export": "Eksportuj",
"There is no category. You must have at least one category.": "Nie ma żadnej kategorii. Musisz mieć co najmniej jedną kategorię",
"URL": "URL",
"Category": "Kategoria",
"Find a subscription": "Znajdź subskrypcję",
"Loading...": "Ładowanie...",
"Create a category": "Utwórz kategorię",
"There is no category.": "Nie masz żadnej kategorii",
"Edit": "Edytuj",
"Remove": "Usuń",
"No feed.": "Brak kanałów.",
"There is no article in this category.": "W tej kategorii nie ma żadnych artykułów",
"Original": "Oryginalny artykuł",
"Mark this page as read": "Oznacz jako przeczytane",
"not yet": "jeszcze nie",
"just now": "przed chwilą",
"1 minute ago": "minutę temu",
"%d minutes ago": "%d minut temu",
"1 hour ago": "godzinę temu",
"%d hours ago": "%d godzin temu",
"yesterday": "wczoraj",
"%d days ago": "%d dni temu",
"%d weeks ago": "%d tygodni temu",
"%d months ago": "%d miesięcy temu",
"%d years ago": "%d lat temu",
"Date": "Data",
"IP Address": "Adres IP",
"User Agent": "User Agent",
"Actions": "Działania",
"Current session": "Bieżąca sesja",
"Sessions": "Sesje",
"Users": "Użytkownicy",
"Add user": "Dodaj użytkownika",
"Choose a Subscription": "Wybierz subskrypcję",
"Subscribe": "Subskrypcja",
"New Category": "Nowa kategoria",
"Title": "Tytuł",
"Save": "Zapisz",
"or": "lub",
"cancel": "anuluj",
"New User": "Nowy użytkownik",
"Confirmation": "Potwierdź",
"Administrator": "Administrator",
"Edit Category: %s": "Edycja Kategorii: %s",
"Update": "Zaktualizuj",
"Edit Feed: %s": "Edytuj kanał: %s",
"There is no category!": "Nie ma żadnej kategorii!",
"Edit user: %s": "Edytuj użytkownika: %s",
"There is no article for this feed.": "Nie ma artykułu dla tego kanału.",
"Add subscription": "Dodaj subskrypcję",
"You don't have any subscription.": "Nie masz żadnej subskrypcji",
"Last check:": "Ostatnia aktualizacja:",
"Refresh": "Odśwież",
"There is no history at the moment.": "Obecnie nie ma żadnej historii.",
"OPML file": "Plik OPML",
"Sign In": "Zaloguj się",
"Sign in": "Zaloguj się",
"Theme": "Wygląd",
"Timezone": "Strefa czasowa",
"Language": "Język",
"There is no unread article.": "Nie ma żadnych nieprzeczytanych artykułów.",
"You are the only user.": "Jesteś jedynym użytkownikiem.",
"Last Login": "Ostatnie logowanie",
"Yes": "Tak",
"No": "Nie",
"This feed already exists (%s)": "Ten kanał już istnieje (%s)",
"Unable to fetch feed (statusCode=%d)": "Kanał nie mógł zostać pobrany (kod=%d)",
"Unable to open this link: %v": "Nie można było otworzyć tego linku: %v",
"Unable to analyze this page: %v": "Nie można przeanalizować tej strony: %v",
"Unable to find any subscription.": "Nie znaleziono żadnych subskrypcji.",
"The URL and the category are mandatory.": "URL i kategoria są obowiązkowe.",
"All fields are mandatory.": "Wszystkie pola są obowiązkowe.",
"Passwords are not the same.": "Hasła nie są identyczne.",
"You must use at least 6 characters.": "Musisz użyć co najmniej 6 znaków.",
"The username is mandatory.": "Nazwa użytkownika jest obowiązkowa.",
"The username, theme, language and timezone fields are mandatory.": "Pola nazwy użytkownika, tematu, języka i strefy czasowej są obowiązkowe.",
"The title is mandatory.": "Tytuł jest obowiązkowy.",
"About": "O stronie",
"version": "Wersja",
"Version:": "Wersja :",
"Build Date:": "Data opracowania:",
"Author:": "Autor:",
"Authors": "Autorzy",
"License:": "Licencja:",
"Attachments": "Załączniki",
"Download": "Pobierz",
"Invalid username or password.": "Nieprawidłowa nazwa użytkownika lub hasło.",
"Never": "Nigdy",
"Unable to execute request: %v": "To polecenie nie mogło zostać wykonane: %v",
"Last Parsing Error": "Ostatni błąd analizy",
"There is a problem with this feed": "Z tym kanałem jest problem",
"Unable to parse OPML file: %v.": "Plik OPML nie mógł zostać odczytany: %v.",
"Unable to parse RSS feed: %v.": "Nie można było odczytać kanału RSS: %v.",
"Unable to parse Atom feed: %v.": "Nie można było odczytać kanału Atom: %v.",
"Unable to parse JSON feed: %v.": "Nie można było odczytać kanału JSON: %v.",
"Unable to parse RDF feed: %v.": "Nie można było odczytać kanału RDF: %v.",
"Unable to normalize encoding: %v": "Kodowanie znaków nie mogło zostać znormalizowane: %v",
"Unable to create this category.": "Ta kategoria nie mogła zostać utworzona.",
"yes": "tak",
"no": "nie",
"Are you sure?": "Czy jesteś pewny?",
"Work in progress...": "W toku...",
"This user already exists.": "Ten użytkownik już istnieje.",
"This category already exists.": "Ta kategoria już istnieje.",
"Unable to update this category.": "Ta kategoria nie mogła zostać zaktualizowana.",
"Integrations": "Usługi",
"Bookmarklet": "Bookmarklet",
"Drag and drop this link to your bookmarks.": "Przeciągnij i upuść to łącze do zakładek.",
"This special link allows you to subscribe to a website directly by using a bookmark in your web browser.": "Ten link umożliwia subskrypcję strony internetowej bezpośrednio za pomocą zakładki w przeglądarce internetowej",
"Add to Miniflux": "Dodaj do Miniflux",
"Refresh all feeds in background": "Odśwież wszystkie subskrypcje w tle",
"Sign in with Google": "Zaloguj przez Google",
"Unlink my Google account": "Odłącz moje konto Google",
"Link my Google account": "Połącz z moim kontem Google",
"Category not found for this user": "Kategoria nie znaleziona dla tego użytkownika",
"Invalid theme.": "Ten temat jest nieprawidłowy.",
"Entry Sorting": "Sortowanie artykułów",
"Older entries first": "Najstarsze wpisy jako pierwsze",
"Recent entries first": "Najnowsze wpisy jako pierwsze",
"Saving...": "Zapisywanie...",
"Done!": "Gotowe!",
"Save this article": "Zapisz ten artykuł",
"Mark bookmark as unread": "Zaznacz zakładkę jako nieprzeczytaną",
"Pinboard Tags": "Pinboard Tags",
"Pinboard API Token": "Token Pinboard API",
"Save articles to Pinboard": "Zapisz artykuł w Pinboard",
"Save articles to Instapaper": "Zapisz artykuł w Instapaper",
"Instapaper Username": "Login do Instapaper",
"Instapaper Password": "Hasło do Instapaper",
"Activate Fever API": "Aktywuj Fever API",
"Fever Username": "Login do Fever",
"Fever Password": "Hasło do Fever",
"Fetch original content": "Pobierz oryginalną treść",
"Scraper Rules": "Zasady ekstrakcji",
"Rewrite Rules": "Reguły zapisu",
"Preferences saved!": "Ustawienia zapisane!",
"Your external account is now linked !": "Twoje zewnętrzne konto jest teraz połączone!",
"Save articles to Wallabag": "Zapisz artykuły do Wallabag",
"Wallabag API Endpoint": "Wallabag URL",
"Wallabag Client ID": "Wallabag Client-ID",
"Wallabag Client Secret": "Wallabag Client Secret",
"Wallabag Username": "Login do Wallabag",
"Wallabag Password": "Hasło do Wallabag",
"Keyboard Shortcut: %s": "Skróty klawiszowe: %s",
"Favorites": "Ulubione",
"Star": "Oznacz gwiazdką",
"Unstar": "Usuń gwiazdkę",
"Starred": "Oznaczone gwiazdką",
"There is no bookmark at the moment.": "Obecnie nie ma żadnych zakładek.",
"Last checked:": "Ostatnio sprawdzone:",
"ETag header:": "Nagłówek ETag:",
"LastModified header:": "Ostatnio zmienione:",
"None": "Brak",
"Keyboard Shortcuts": "Skróty klawiszowe",
"Sections Navigation": "Nawigacja między punktami menu",
"Go to unread": "Przejdź do nieprzeczytanych artykułów",
"Go to bookmarks": "Przejdź do zakładek",
"Go to history": "Przejdź do historii",
"Go to feeds": "Przejdź do kanałów",
"Go to categories": "Przejdź do kategorii",
"Go to settings": "Przejdź do ustawień",
"Show keyboard shortcuts": "Pokaż listę skrótów klawiszowych",
"Items Navigation": "Nawigacja między artykułami",
"Go to previous item": "Przejdź do poprzedniego artykułu",
"Go to next item": "Przejdź do następnego punktu artykułu",
"Pages Navigation": "Nawigacja między stronami",
"Go to previous page": "Przejdź do poprzedniej strony",
"Go to next page": "Przejdź do następnej strony",
"Open selected item": "Otwórz zaznaczony artykuł",
"Open original link": "Otwórz oryginalny artykuł",
"Toggle read/unread": "Oznacz jako przeczytane/nieprzeczytane",
"Mark current page as read": "Zaznacz aktualną stronę jako przeczytaną",
"Download original content": "Pobierz oryginalną zawartość",
"Toggle bookmark": "Dodaj/usuń zakładki",
"Close modal dialog": "Zamknij listę skrótów klawiszowych",
"Save article": "Zapisz artykuł",
"There is already someone associated with this provider!": "Już ktoś jest powiązany z tym dostawcą!",
"There is already someone else with the same Fever username!": "Już ktoś inny używa tej nazwy użytkownika Fever!",
"Mark all as read": "Oznacz wszystko jako przeczytane",
"This feed is empty": "Ten kanał jest pusty",
"Flush history": "Usuń historię",
"Site URL": "URL strony",
"Feed URL": "URL kanału",
"Logged as %s": "Zalogowany jako %s",
"Unread Items": "Nieprzeczytane",
"Change entry status": "Zmień status artykułu",
"Read": "Przeczytane",
"Fever API endpoint:": "Fever API endpoint:",
"Miniflux API": "Miniflux API",
"API Endpoint": "API endpoint",
"Your account password": "Hasło konta",
"This web page is empty": "Ta strona jest pusta",
"Invalid SSL certificate (original error: %q)": "Certyfikat SSL jest nieprawidłowy (błąd: %q)",
"This website is temporarily unreachable (original error: %q)": "Ta strona jest tymczasowo niedostępna (błąd: %q)",
"This website is permanently unreachable (original error: %q)": "Ta strona jest niedostępna (błąd: %q)",
"Website unreachable, the request timed out after %d seconds": "Strona internetowa nieosiągalna, żądanie wygasło po %d sekundach"
}
+14 -2
View File
@@ -14,6 +14,7 @@ import (
"github.com/miniflux/miniflux/logger"
"github.com/miniflux/miniflux/model"
"github.com/miniflux/miniflux/reader/date"
"github.com/miniflux/miniflux/reader/sanitizer"
"github.com/miniflux/miniflux/url"
)
@@ -28,7 +29,7 @@ type atomFeed struct {
type atomEntry struct {
ID string `xml:"id"`
Title string `xml:"title"`
Title atomContent `xml:"title"`
Updated string `xml:"updated"`
Links []atomLink `xml:"link"`
Summary string `xml:"summary"`
@@ -97,7 +98,7 @@ func (a *atomEntry) Transform() *model.Entry {
entry.Author = getAuthor(a.Author)
entry.Hash = getHash(a)
entry.Content = getContent(a)
entry.Title = strings.TrimSpace(a.Title)
entry.Title = getTitle(a)
entry.Enclosures = getEnclosures(a)
return entry
}
@@ -160,6 +161,17 @@ func getContent(a *atomEntry) string {
return ""
}
func getTitle(a *atomEntry) string {
title := ""
if a.Title.Type == "xhtml" {
title = a.Title.XML
} else {
title = a.Title.Data
}
return strings.TrimSpace(sanitizer.StripTags(title))
}
func getHash(a *atomEntry) string {
for _, value := range []string{a.ID, getURL(a.Links)} {
if value != "" {
+78
View File
@@ -206,6 +206,84 @@ func TestParseEntryTitleWithWhitespaces(t *testing.T) {
}
}
func TestParseEntryTitleWithHTMLAndCDATA(t *testing.T) {
data := `<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Example Feed</title>
<link href="http://example.org/"/>
<entry>
<title type="html"><![CDATA[Test &#8220;Test&#8221;]]></title>
<link href="http://example.org/2003/12/13/atom03"/>
<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
<updated>2003-12-13T18:30:02Z</updated>
<summary>Some text.</summary>
</entry>
</feed>`
feed, err := Parse(bytes.NewBufferString(data))
if err != nil {
t.Error(err)
}
if feed.Entries[0].Title != "Test “Test”" {
t.Errorf("Incorrect entry title, got: %q", feed.Entries[0].Title)
}
}
func TestParseEntryTitleWithHTML(t *testing.T) {
data := `<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Example Feed</title>
<link href="http://example.org/"/>
<entry>
<title type="html">&lt;code&gt;Test&lt;/code&gt; Test</title>
<link href="http://example.org/2003/12/13/atom03"/>
<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
<updated>2003-12-13T18:30:02Z</updated>
<summary>Some text.</summary>
</entry>
</feed>`
feed, err := Parse(bytes.NewBufferString(data))
if err != nil {
t.Error(err)
}
if feed.Entries[0].Title != "Test Test" {
t.Errorf("Incorrect entry title, got: %q", feed.Entries[0].Title)
}
}
func TestParseEntryTitleWithXHTML(t *testing.T) {
data := `<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Example Feed</title>
<link href="http://example.org/"/>
<entry>
<title type="xhtml"><code>Test</code> Test</title>
<link href="http://example.org/2003/12/13/atom03"/>
<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
<updated>2003-12-13T18:30:02Z</updated>
<summary>Some text.</summary>
</entry>
</feed>`
feed, err := Parse(bytes.NewBufferString(data))
if err != nil {
t.Error(err)
}
if feed.Entries[0].Title != "Test Test" {
t.Errorf("Incorrect entry title, got: %q", feed.Entries[0].Title)
}
}
func TestParseEntryWithAuthorName(t *testing.T) {
data := `<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
+14 -5
View File
@@ -20,11 +20,11 @@ import (
var (
errRequestFailed = "Unable to execute request: %v"
errServerFailure = "Unable to fetch feed (statusCode=%d)."
errDuplicate = "This feed already exists (%s)."
errServerFailure = "Unable to fetch feed (statusCode=%d)"
errDuplicate = "This feed already exists (%s)"
errNotFound = "Feed %d not found"
errEncoding = "Unable to normalize encoding: %v."
errCategoryNotFound = "Category not found for this user."
errEncoding = "Unable to normalize encoding: %v"
errCategoryNotFound = "Category not found for this user"
errEmptyFeed = "This feed is empty"
)
@@ -44,6 +44,9 @@ func (h *Handler) CreateFeed(userID, categoryID int64, url string, crawler bool)
client := http.NewClient(url)
response, err := client.Get()
if err != nil {
if _, ok := err.(errors.LocalizedError); ok {
return nil, err
}
return nil, errors.NewLocalizedError(errRequestFailed, err)
}
@@ -120,7 +123,13 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error {
client := http.NewClientWithCacheHeaders(originalFeed.FeedURL, originalFeed.EtagHeader, originalFeed.LastModifiedHeader)
response, err := client.Get()
if err != nil {
customErr := errors.NewLocalizedError(errRequestFailed, err)
var customErr errors.LocalizedError
if lerr, ok := err.(errors.LocalizedError); ok {
customErr = lerr
} else {
customErr = errors.NewLocalizedError(errRequestFailed, err)
}
originalFeed.ParsingErrorCount++
originalFeed.ParsingErrorMsg = customErr.Error()
h.store.UpdateFeed(originalFeed)
+30 -2
View File
@@ -12,6 +12,7 @@ import (
"strings"
"time"
"github.com/miniflux/miniflux/logger"
"github.com/miniflux/miniflux/model"
"github.com/miniflux/miniflux/reader/atom"
"github.com/miniflux/miniflux/reader/encoding"
@@ -69,9 +70,13 @@ func parseFeed(r io.Reader) (*model.Feed, error) {
defer timer.ExecutionTime(time.Now(), "[Feed:ParseFeed]")
var buffer bytes.Buffer
io.Copy(&buffer, r)
size, _ := io.Copy(&buffer, r)
if size == 0 {
return nil, errors.New("This feed is empty")
}
reader := bytes.NewReader(buffer.Bytes())
str := stripInvalidXMLCharacters(buffer.String())
reader := strings.NewReader(str)
format := DetectFeedFormat(reader)
reader.Seek(0, io.SeekStart)
@@ -88,3 +93,26 @@ func parseFeed(r io.Reader) (*model.Feed, error) {
return nil, errors.New("Unsupported feed format")
}
}
func stripInvalidXMLCharacters(input string) string {
return strings.Map(func(r rune) rune {
if isInCharacterRange(r) {
return r
}
logger.Debug("Strip invalid XML characters: %U", r)
return -1
}, input)
}
// Decide whether the given rune is in the XML Character Range, per
// the Char production of http://www.xml.com/axml/testaxml.htm,
// Section 2.2 Characters.
func isInCharacterRange(r rune) (inrange bool) {
return r == 0x09 ||
r == 0x0A ||
r == 0x0D ||
r >= 0x20 && r <= 0xDF77 ||
r >= 0xE000 && r <= 0xFFFD ||
r >= 0x10000 && r <= 0x10FFFF
}
+7
View File
@@ -205,3 +205,10 @@ func TestParseUnknownFeed(t *testing.T) {
t.Error("ParseFeed must returns an error")
}
}
func TestParseEmptyFeed(t *testing.T) {
_, err := parseFeed(bytes.NewBufferString(""))
if err == nil {
t.Error("ParseFeed must returns an error")
}
}
+14 -1
View File
@@ -23,6 +23,7 @@ import (
var (
errConnectionFailure = "Unable to open this link: %v"
errUnreadableDoc = "Unable to analyze this page: %v"
errEmptyBody = "This web page is empty"
)
// FindSubscriptions downloads and try to find one or more subscriptions from an URL.
@@ -32,16 +33,28 @@ func FindSubscriptions(websiteURL string) (Subscriptions, error) {
client := http.NewClient(websiteURL)
response, err := client.Get()
if err != nil {
if _, ok := err.(errors.LocalizedError); ok {
return nil, err
}
return nil, errors.NewLocalizedError(errConnectionFailure, err)
}
// Content-Length = -1 when no Content-Length header is sent
if response.ContentLength == 0 {
return nil, errors.NewLocalizedError(errEmptyBody)
}
body, err := response.NormalizeBodyEncoding()
if err != nil {
return nil, err
}
var buffer bytes.Buffer
io.Copy(&buffer, body)
size, _ := io.Copy(&buffer, body)
if size == 0 {
return nil, errors.NewLocalizedError(errEmptyBody)
}
reader := bytes.NewReader(buffer.Bytes())
if format := feed.DetectFeedFormat(reader); format != feed.FormatUnknown {
+2
View File
@@ -0,0 +1,2 @@
create index entries_user_status_idx on entries(user_id, status);
create index feeds_user_category_idx on feeds(user_id, category_id);
+5 -1
View File
@@ -1,5 +1,5 @@
// Code generated by go generate; DO NOT EDIT.
// 2018-01-31 21:53:31.179267411 -0800 PST m=+0.003439489
// 2018-02-10 22:26:57.088648586 -0800 PST m=+0.004853992
package sql
@@ -123,6 +123,9 @@ alter table integrations add column wallabag_client_secret text default '';
alter table integrations add column wallabag_username text default '';
alter table integrations add column wallabag_password text default '';`,
"schema_version_12": `alter table entries add column starred bool default 'f';`,
"schema_version_13": `create index entries_user_status_idx on entries(user_id, status);
create index feeds_user_category_idx on feeds(user_id, category_id);
`,
"schema_version_2": `create extension if not exists hstore;
alter table users add column extra hstore;
create index users_extra_idx on users using gin(extra);
@@ -166,6 +169,7 @@ var SqlMapChecksums = map[string]string{
"schema_version_10": "8faf15ddeff7c8cc305e66218face11ed92b97df2bdc2d0d7944d61441656795",
"schema_version_11": "dc5bbc302e01e425b49c48ddcd8e29e3ab2bb8e73a6cd1858a6ba9fbec0b5243",
"schema_version_12": "a95abab6cdf64811fc744abd37457e2928939d999c5ef00d2bdd9398e16f32fb",
"schema_version_13": "9073fae1e796936f4a43a8120ebdb4218442fe7d346ace6387556a357c2d7edf",
"schema_version_2": "e8e9ff32478df04fcddad10a34cba2e8bb1e67e7977b5bd6cdc4c31ec94282b4",
"schema_version_3": "a54745dbc1c51c000f74d4e5068f1e2f43e83309f023415b1749a47d5c1e0f12",
"schema_version_4": "216ea3a7d3e1704e40c797b5dc47456517c27dbb6ca98bf88812f4f63d74b5d9",
+1 -1
View File
@@ -12,7 +12,7 @@ import (
"github.com/miniflux/miniflux/sql"
)
const schemaVersion = 12
const schemaVersion = 13
// Migrate run database migrations.
func (s *Storage) Migrate() {
+3 -3
View File
File diff suppressed because one or more lines are too long
+4
View File
@@ -214,6 +214,10 @@ article.feed-parsing-error {
border-color: #888;
}
.entry-content q {
color: #777;
}
.entry-enclosure {
border-color: #333;
}