Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
c2b9664b4b | |||
e760d2b022 | |||
1a836d914b | |||
1b0e6eb6c4 | |||
39827ad485 | |||
79c4d4e98f | |||
427bd2f348 | |||
4fefd0e128 | |||
d79060237d | |||
2df7a1c592 | |||
8c4ceb3e4b | |||
bf725ece72 | |||
76992bd4f4 | |||
dccd530475 | |||
f1fa01a0eb | |||
7949142ef6 | |||
af1da8fc0a | |||
39f4237cde | |||
b676ed74cd | |||
bf474877ca | |||
7ee13af238 | |||
d488e5874a | |||
b1a7c22452 | |||
e928d202ee | |||
c39961d253 | |||
c6d11fcd7f | |||
eb96103478 | |||
583ccfc7b1 | |||
e0437718a0 | |||
2126ae022b | |||
2ef39dcb19 | |||
047c865e76 | |||
c586cd00cc | |||
ee7f470269 | |||
f04dda308b | |||
071458e262 | |||
9ffc96de4d | |||
26e9a5404a | |||
f44fe59054 |
@ -81,3 +81,9 @@ The javascript library accessing it is licensed under the MIT license.
|
|||||||
## whatwg-mimetype
|
## whatwg-mimetype
|
||||||
|
|
||||||
Licensed under MIT
|
Licensed under MIT
|
||||||
|
|
||||||
|
|
||||||
|
## CDHeaderParser
|
||||||
|
|
||||||
|
Licensed under MPL-2
|
||||||
|
(c) 2017 Rob Wu <rob@robwu.nl> (https://robwu.nl)
|
||||||
|
1
TODO.md
1
TODO.md
@ -13,7 +13,6 @@ Planned for later.
|
|||||||
* Delete files (well, as far as the browser allows)
|
* Delete files (well, as far as the browser allows)
|
||||||
* Inter-addon API (basic)
|
* Inter-addon API (basic)
|
||||||
* Add downloads
|
* Add downloads
|
||||||
* Chrome support
|
|
||||||
* vtable perf: cache column widths
|
* vtable perf: cache column widths
|
||||||
* Download options
|
* Download options
|
||||||
* This is a bit more limited, as we cannot modify options of downloads that have been started (and paused) or that are done.
|
* This is a bit more limited, as we cannot modify options of downloads that have been started (and paused) or that are done.
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
{
|
{
|
||||||
|
"ar": "العربية [ar]",
|
||||||
"cs": "Čeština (CZ) [cs]",
|
"cs": "Čeština (CZ) [cs]",
|
||||||
"de": "Deutsch [de]",
|
"de": "Deutsch [de]",
|
||||||
"el": "Ελληνικά [el]",
|
"el": "Ελληνικά [el]",
|
||||||
"en": "English (US) [en]",
|
"en": "English (US) [en]",
|
||||||
"es": "Español (España) [es]",
|
"es": "Español (España) [es]",
|
||||||
"et": "Eesti Keel [et]",
|
"et": "Eesti Keel [et]",
|
||||||
"fr": "Français (FR) [fr]",
|
"fr": "Français [fr]",
|
||||||
|
"hu": "Magyar (HU) [hu]",
|
||||||
"id": "Bahasa Indonesia [id]",
|
"id": "Bahasa Indonesia [id]",
|
||||||
|
"it": "Italiano [it]",
|
||||||
|
"ja": "日本語 (JP) [ja]",
|
||||||
"ko": "한국어 [ko]",
|
"ko": "한국어 [ko]",
|
||||||
"lt": "Lietuvių [lt]",
|
"lt": "Lietuvių [lt]",
|
||||||
"nl": "Nederlands [nl]",
|
"nl": "Nederlands [nl]",
|
||||||
"pl": "Polski (PL) [pl]",
|
"pl": "Polski [pl]",
|
||||||
"pt": "Português (Brasil) [pt]",
|
"pt": "Português (Brasil) [pt]",
|
||||||
"ru": "Русский [ru]",
|
"ru": "Русский [ru]",
|
||||||
"zh_CN": "简体中文 [zh_CN]",
|
"zh_CN": "简体中文 [zh_CN]",
|
||||||
|
1170
_locales/ar/messages.json
Normal file
1170
_locales/ar/messages.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -11,272 +11,6 @@
|
|||||||
"message": "Pausiert hinzufügen",
|
"message": "Pausiert hinzufügen",
|
||||||
"description": "Action: Add paused"
|
"description": "Action: Add paused"
|
||||||
},
|
},
|
||||||
"cancel": {
|
|
||||||
"message": "Abbrechen",
|
|
||||||
"description": "Button text: Cancel"
|
|
||||||
},
|
|
||||||
"canceled": {
|
|
||||||
"message": "Abgebrochen",
|
|
||||||
"description": "Download statu text"
|
|
||||||
},
|
|
||||||
"colConnections": {
|
|
||||||
"message": "Gleichzeitige Verbindungen",
|
|
||||||
"description": "Table column in prefs/network"
|
|
||||||
},
|
|
||||||
"colDomain": {
|
|
||||||
"message": "Domain",
|
|
||||||
"description": "Table column in manager"
|
|
||||||
},
|
|
||||||
"colETA": {
|
|
||||||
"message": "Verbl. Zeit",
|
|
||||||
"description": "Table column in manager"
|
|
||||||
},
|
|
||||||
"colNameURL": {
|
|
||||||
"message": "Name/URL",
|
|
||||||
"description": "Table column in manager"
|
|
||||||
},
|
|
||||||
"colPercent": {
|
|
||||||
"message": "%",
|
|
||||||
"description": "Table column in manager"
|
|
||||||
},
|
|
||||||
"colProgress": {
|
|
||||||
"message": "Fortschritt",
|
|
||||||
"description": "Table column in manager"
|
|
||||||
},
|
|
||||||
"colSegments": {
|
|
||||||
"message": "Segmente",
|
|
||||||
"description": "Table column in manager"
|
|
||||||
},
|
|
||||||
"colSize": {
|
|
||||||
"message": "Größe",
|
|
||||||
"description": "Table column in manager"
|
|
||||||
},
|
|
||||||
"colSpeed": {
|
|
||||||
"message": "Geschwindigkeit",
|
|
||||||
"description": "Table column in manager"
|
|
||||||
},
|
|
||||||
"CRASH": {
|
|
||||||
"message": "Interner Browser Fehler",
|
|
||||||
"description": "Error Message"
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"message": "Entfernen",
|
|
||||||
"description": "button text"
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"message": "Beschreibung",
|
|
||||||
"description": "Description (keep it short); e.g. the description column in select"
|
|
||||||
},
|
|
||||||
"donate": {
|
|
||||||
"message": "Spenden!",
|
|
||||||
"description": "Donate button"
|
|
||||||
},
|
|
||||||
"done": {
|
|
||||||
"message": "Fertig",
|
|
||||||
"description": "Status text"
|
|
||||||
},
|
|
||||||
"download": {
|
|
||||||
"message": "Download",
|
|
||||||
"description": "Download (noun); e.g. Download column in select"
|
|
||||||
},
|
|
||||||
"extensionDescription": {
|
|
||||||
"message": "Der Massen-Downloader für Deinen Browser",
|
|
||||||
"description": "DownThemAll! tagline, displayed in about:addons; Please do NOT refer to a specific browser such as firefox, as we will probably support more than one"
|
|
||||||
},
|
|
||||||
"fastfiltering": {
|
|
||||||
"message": "Schnelles Filtern",
|
|
||||||
"description": "Label for Fast Filtering input"
|
|
||||||
},
|
|
||||||
"FILE_FAILED": {
|
|
||||||
"message": "Dateizugriffsfehler",
|
|
||||||
"description": "Error Message"
|
|
||||||
},
|
|
||||||
"finishing": {
|
|
||||||
"message": "Beenden",
|
|
||||||
"description": "Status text"
|
|
||||||
},
|
|
||||||
"links": {
|
|
||||||
"message": "Links",
|
|
||||||
"description": "Links tab label (short); select window"
|
|
||||||
},
|
|
||||||
"mask": {
|
|
||||||
"message": "Maske",
|
|
||||||
"description": "Renaming mask (short); used in e.g. select"
|
|
||||||
},
|
|
||||||
"media": {
|
|
||||||
"message": "Medien",
|
|
||||||
"description": "Media label (short)"
|
|
||||||
},
|
|
||||||
"missing": {
|
|
||||||
"message": "Fehlt",
|
|
||||||
"description": "Status text in manager"
|
|
||||||
},
|
|
||||||
"NETWORK_FAILED": {
|
|
||||||
"message": "Netzwerkfehler",
|
|
||||||
"description": "Error Message"
|
|
||||||
},
|
|
||||||
"ok": {
|
|
||||||
"message": "OK",
|
|
||||||
"description": "Button text; Used in message boxes"
|
|
||||||
},
|
|
||||||
"paused": {
|
|
||||||
"message": "Pausiert",
|
|
||||||
"description": "Status text; manager"
|
|
||||||
},
|
|
||||||
"queued": {
|
|
||||||
"message": "Wartend",
|
|
||||||
"description": "Status text"
|
|
||||||
},
|
|
||||||
"referrer": {
|
|
||||||
"message": "Referrer",
|
|
||||||
"description": "Label for \"Referrer\""
|
|
||||||
},
|
|
||||||
"remember": {
|
|
||||||
"message": "Diese Entscheidung merken",
|
|
||||||
"description": "Checkbox text for confirmation, e.g. when removing a download in manager"
|
|
||||||
},
|
|
||||||
"rename": {
|
|
||||||
"message": "Umbenennen",
|
|
||||||
"description": "UI for renaming; currently unused"
|
|
||||||
},
|
|
||||||
"renmask": {
|
|
||||||
"message": "Umbennenungsmaske",
|
|
||||||
"description": "Renaming mask (long)"
|
|
||||||
},
|
|
||||||
"reset": {
|
|
||||||
"message": "Zurücksetzen",
|
|
||||||
"description": "Button text; pref window"
|
|
||||||
},
|
|
||||||
"running": {
|
|
||||||
"message": "Laufend",
|
|
||||||
"description": "Status text"
|
|
||||||
},
|
|
||||||
"save": {
|
|
||||||
"message": "Speichern",
|
|
||||||
"description": "Button text; e.g. prefs/Network"
|
|
||||||
},
|
|
||||||
"search": {
|
|
||||||
"message": "Suchen…",
|
|
||||||
"description": "Placeholder text; manager status search field"
|
|
||||||
},
|
|
||||||
"SERVER_BAD_CONTENT": {
|
|
||||||
"message": "Nicht gefunden",
|
|
||||||
"description": "Error message"
|
|
||||||
},
|
|
||||||
"SERVER_FAILED": {
|
|
||||||
"message": "Server-Fehler",
|
|
||||||
"description": "Error message"
|
|
||||||
},
|
|
||||||
"SERVER_FORBIDDEN": {
|
|
||||||
"message": "Nicht erlaubt",
|
|
||||||
"description": "Error message"
|
|
||||||
},
|
|
||||||
"SERVER_UNAUTHORIZED": {
|
|
||||||
"message": "Keine Berechtigung",
|
|
||||||
"description": "Error message"
|
|
||||||
},
|
|
||||||
"sizeB": {
|
|
||||||
"message": "$S$B",
|
|
||||||
"description": "Size formatting; bytes",
|
|
||||||
"placeholders": {
|
|
||||||
"s": {
|
|
||||||
"content": "$1",
|
|
||||||
"example": "100b"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sizeGB": {
|
|
||||||
"message": "$S$GB",
|
|
||||||
"description": "Size formatting; giga bytes",
|
|
||||||
"placeholders": {
|
|
||||||
"s": {
|
|
||||||
"content": "$1",
|
|
||||||
"example": "100.200GB"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sizeKB": {
|
|
||||||
"message": "$S$KB",
|
|
||||||
"description": "Size formatting; kilo bytes",
|
|
||||||
"placeholders": {
|
|
||||||
"s": {
|
|
||||||
"content": "$1",
|
|
||||||
"example": "100.2KB"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sizeMB": {
|
|
||||||
"message": "$S$MB",
|
|
||||||
"description": "Size formatting; mega bytes",
|
|
||||||
"placeholders": {
|
|
||||||
"s": {
|
|
||||||
"content": "$1",
|
|
||||||
"example": "100.22MB"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sizePB": {
|
|
||||||
"message": "$S$PB",
|
|
||||||
"description": "Size formatting; peta bytes (you never know)",
|
|
||||||
"placeholders": {
|
|
||||||
"s": {
|
|
||||||
"content": "$1",
|
|
||||||
"example": "100.212PB"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sizeTB": {
|
|
||||||
"message": "$S$TB",
|
|
||||||
"description": "Size formatting; tera bytes (you never know)",
|
|
||||||
"placeholders": {
|
|
||||||
"s": {
|
|
||||||
"content": "$1",
|
|
||||||
"example": "100.002TB"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"speedB": {
|
|
||||||
"message": "$SPEED$b/s",
|
|
||||||
"description": "Speed formatting; bytes",
|
|
||||||
"placeholders": {
|
|
||||||
"speed": {
|
|
||||||
"content": "$1",
|
|
||||||
"example": "100b/s"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"speedKB": {
|
|
||||||
"message": "$SPEED$KB/s",
|
|
||||||
"description": "Speed formatting; kilo bytes",
|
|
||||||
"placeholders": {
|
|
||||||
"speed": {
|
|
||||||
"content": "$1",
|
|
||||||
"example": "100.1KB/s"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"speedMB": {
|
|
||||||
"message": "$SPEED$MB/s",
|
|
||||||
"description": "Speed formatting; mega bytes",
|
|
||||||
"placeholders": {
|
|
||||||
"speed": {
|
|
||||||
"content": "$1",
|
|
||||||
"example": "100.20MB/s"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"message": "Titel",
|
|
||||||
"description": "Column text; Title label (short)"
|
|
||||||
},
|
|
||||||
"unlimited": {
|
|
||||||
"message": "Unbegrenzt",
|
|
||||||
"description": "Option text; Prefs/Network"
|
|
||||||
},
|
|
||||||
"useonlyonce": {
|
|
||||||
"message": "Einmalig",
|
|
||||||
"description": "Label for Use-Once checkboxes"
|
|
||||||
},
|
|
||||||
"add_download": {
|
"add_download": {
|
||||||
"message": "Download hinzufügen",
|
"message": "Download hinzufügen",
|
||||||
"description": "Action for adding a download"
|
"description": "Action for adding a download"
|
||||||
@ -329,6 +63,14 @@
|
|||||||
"message": "Batch Download",
|
"message": "Batch Download",
|
||||||
"description": "Messagebox title for batch confirmations"
|
"description": "Messagebox title for batch confirmations"
|
||||||
},
|
},
|
||||||
|
"cancel": {
|
||||||
|
"message": "Abbrechen",
|
||||||
|
"description": "Button text: Cancel"
|
||||||
|
},
|
||||||
|
"canceled": {
|
||||||
|
"message": "Abgebrochen",
|
||||||
|
"description": "Download status text"
|
||||||
|
},
|
||||||
"cancel_download": {
|
"cancel_download": {
|
||||||
"message": "Abbrechen",
|
"message": "Abbrechen",
|
||||||
"description": "Action to cancel downloads, e.g. from the context menu"
|
"description": "Action to cancel downloads, e.g. from the context menu"
|
||||||
@ -342,9 +84,45 @@
|
|||||||
"description": "Checkbox label text for decision confirmations"
|
"description": "Checkbox label text for decision confirmations"
|
||||||
},
|
},
|
||||||
"check_selected_items": {
|
"check_selected_items": {
|
||||||
"message": "Ausgewählte Einträge makieren",
|
"message": "Ausgewählte Einträge markieren",
|
||||||
"description": "Menu text"
|
"description": "Menu text"
|
||||||
},
|
},
|
||||||
|
"colConnections": {
|
||||||
|
"message": "Gleichzeitige Verbindungen",
|
||||||
|
"description": "Table column in prefs/network"
|
||||||
|
},
|
||||||
|
"colDomain": {
|
||||||
|
"message": "Domain",
|
||||||
|
"description": "Table column in manager"
|
||||||
|
},
|
||||||
|
"colETA": {
|
||||||
|
"message": "Verbl. Zeit",
|
||||||
|
"description": "Table column in manager"
|
||||||
|
},
|
||||||
|
"colNameURL": {
|
||||||
|
"message": "Name/URL",
|
||||||
|
"description": "Table column in manager"
|
||||||
|
},
|
||||||
|
"colPercent": {
|
||||||
|
"message": "%",
|
||||||
|
"description": "Table column in manager"
|
||||||
|
},
|
||||||
|
"colProgress": {
|
||||||
|
"message": "Fortschritt",
|
||||||
|
"description": "Table column in manager"
|
||||||
|
},
|
||||||
|
"colSegments": {
|
||||||
|
"message": "Segmente",
|
||||||
|
"description": "Table column in manager"
|
||||||
|
},
|
||||||
|
"colSize": {
|
||||||
|
"message": "Größe",
|
||||||
|
"description": "Table column in manager"
|
||||||
|
},
|
||||||
|
"colSpeed": {
|
||||||
|
"message": "Geschwindigkeit",
|
||||||
|
"description": "Table column in manager"
|
||||||
|
},
|
||||||
"conflict_overwrite": {
|
"conflict_overwrite": {
|
||||||
"message": "Überschreiben",
|
"message": "Überschreiben",
|
||||||
"description": "Option text; prefs/general"
|
"description": "Option text; prefs/general"
|
||||||
@ -357,6 +135,10 @@
|
|||||||
"message": "Umbenennen",
|
"message": "Umbenennen",
|
||||||
"description": "Option text; prefs/general"
|
"description": "Option text; prefs/general"
|
||||||
},
|
},
|
||||||
|
"CRASH": {
|
||||||
|
"message": "Interner Browser Fehler",
|
||||||
|
"description": "Error Message"
|
||||||
|
},
|
||||||
"create_filter": {
|
"create_filter": {
|
||||||
"message": "Filter erstellen",
|
"message": "Filter erstellen",
|
||||||
"description": "Button text; Create filter dialog; prefs/filters"
|
"description": "Button text; Create filter dialog; prefs/filters"
|
||||||
@ -405,26 +187,42 @@
|
|||||||
"message": "Videos (mp4, webm, mkv, …)",
|
"message": "Videos (mp4, webm, mkv, …)",
|
||||||
"description": "Filter label for the Videos filter"
|
"description": "Filter label for the Videos filter"
|
||||||
},
|
},
|
||||||
|
"delete": {
|
||||||
|
"message": "Entfernen",
|
||||||
|
"description": "button text"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"message": "Beschreibung",
|
||||||
|
"description": "Description (keep it short); e.g. the description column in select"
|
||||||
|
},
|
||||||
"disable_other_filters": {
|
"disable_other_filters": {
|
||||||
"message": "Andere deaktivieren",
|
"message": "Andere deaktivieren",
|
||||||
"description": "Checkbox label. Keep it short"
|
"description": "Checkbox label. Keep it short"
|
||||||
},
|
},
|
||||||
|
"donate": {
|
||||||
|
"message": "Spenden!",
|
||||||
|
"description": "Donate button"
|
||||||
|
},
|
||||||
|
"done": {
|
||||||
|
"message": "Fertig",
|
||||||
|
"description": "Status text"
|
||||||
|
},
|
||||||
|
"download": {
|
||||||
|
"message": "Download",
|
||||||
|
"description": "Download (noun); e.g. Download column in select"
|
||||||
|
},
|
||||||
"download_verb": {
|
"download_verb": {
|
||||||
"message": "Download",
|
"message": "Download",
|
||||||
"description": "Download (verb/action); e.g. in single and select buttons"
|
"description": "Download (verb/action); e.g. in single and select buttons"
|
||||||
},
|
},
|
||||||
"dta_regular_all": {
|
|
||||||
"message": "DownThemAll! - Alle Tabs",
|
|
||||||
"description": "Menu text"
|
|
||||||
},
|
|
||||||
"dta_turbo_all": {
|
|
||||||
"message": "OneClick! - Alle Tabs",
|
|
||||||
"description": "Menu text"
|
|
||||||
},
|
|
||||||
"dta_regular": {
|
"dta_regular": {
|
||||||
"message": "DownThemAll!",
|
"message": "DownThemAll!",
|
||||||
"description": "Regular dta action; Menu text"
|
"description": "Regular dta action; Menu text"
|
||||||
},
|
},
|
||||||
|
"dta_regular_all": {
|
||||||
|
"message": "DownThemAll! - Alle Tabs",
|
||||||
|
"description": "Menu text"
|
||||||
|
},
|
||||||
"dta_regular_image": {
|
"dta_regular_image": {
|
||||||
"message": "Bild mit DownThemAll! speichern",
|
"message": "Bild mit DownThemAll! speichern",
|
||||||
"description": "Menu text"
|
"description": "Menu text"
|
||||||
@ -445,6 +243,10 @@
|
|||||||
"message": "OneClick!",
|
"message": "OneClick!",
|
||||||
"description": "OneClick! action; Menu text"
|
"description": "OneClick! action; Menu text"
|
||||||
},
|
},
|
||||||
|
"dta_turbo_all": {
|
||||||
|
"message": "OneClick! - Alle Tabs",
|
||||||
|
"description": "Menu text"
|
||||||
|
},
|
||||||
"dta_turbo_image": {
|
"dta_turbo_image": {
|
||||||
"message": "Bild mit OneClick! speichern",
|
"message": "Bild mit OneClick! speichern",
|
||||||
"description": "Menu text"
|
"description": "Menu text"
|
||||||
@ -477,16 +279,28 @@
|
|||||||
"message": "Nichts ausgewählt",
|
"message": "Nichts ausgewählt",
|
||||||
"description": "Error Message; select window"
|
"description": "Error Message; select window"
|
||||||
},
|
},
|
||||||
|
"extensionDescription": {
|
||||||
|
"message": "Der Massen-Downloader für Deinen Browser",
|
||||||
|
"description": "DownThemAll! tagline, displayed in about:addons; Please do NOT refer to a specific browser such as firefox, as we will probably support more than one"
|
||||||
|
},
|
||||||
|
"fastfiltering": {
|
||||||
|
"message": "Schnelles Filtern",
|
||||||
|
"description": "Label for Fast Filtering input"
|
||||||
|
},
|
||||||
"fastfilter_placeholder": {
|
"fastfilter_placeholder": {
|
||||||
"message": "Platzhalter-Ausdruck oder Regular Expression",
|
"message": "Platzhalter-Ausdruck oder Regular Expression",
|
||||||
"description": "Placeholder for fastfilter inputs"
|
"description": "Placeholder for fastfilter inputs"
|
||||||
},
|
},
|
||||||
|
"FILE_FAILED": {
|
||||||
|
"message": "Dateizugriffsfehler",
|
||||||
|
"description": "Error Message"
|
||||||
|
},
|
||||||
"filter_at_least_one": {
|
"filter_at_least_one": {
|
||||||
"message": "Mindestens einen Filter-Typ auswählen!",
|
"message": "Mindestens einen Filter-Typ auswählen!",
|
||||||
"description": "Error message when no filter types are selected for a filter in the preferences UI"
|
"description": "Error message when no filter types are selected for a filter in the preferences UI"
|
||||||
},
|
},
|
||||||
"filter_create_title": {
|
"filter_create_title": {
|
||||||
"message": "Neuen Filter erstelln",
|
"message": "Neuen Filter erstellen",
|
||||||
"description": "Message box title"
|
"description": "Message box title"
|
||||||
},
|
},
|
||||||
"filter_expression": {
|
"filter_expression": {
|
||||||
@ -497,6 +311,10 @@
|
|||||||
"message": "Filter-Titel",
|
"message": "Filter-Titel",
|
||||||
"description": "Message box label"
|
"description": "Message box label"
|
||||||
},
|
},
|
||||||
|
"filter_types": {
|
||||||
|
"message": "Filter Typen",
|
||||||
|
"description": "Message box label"
|
||||||
|
},
|
||||||
"filter_type_link": {
|
"filter_type_link": {
|
||||||
"message": "Link Filter",
|
"message": "Link Filter",
|
||||||
"description": "Message box checkbox label"
|
"description": "Message box checkbox label"
|
||||||
@ -505,9 +323,9 @@
|
|||||||
"message": "Medien Filter",
|
"message": "Medien Filter",
|
||||||
"description": "Message box checkbox label"
|
"description": "Message box checkbox label"
|
||||||
},
|
},
|
||||||
"filter_types": {
|
"finishing": {
|
||||||
"message": "Filter Typen",
|
"message": "Beenden",
|
||||||
"description": "Message box label"
|
"description": "Status text"
|
||||||
},
|
},
|
||||||
"force_start": {
|
"force_start": {
|
||||||
"message": "Start erzwingen",
|
"message": "Start erzwingen",
|
||||||
@ -557,6 +375,14 @@
|
|||||||
"message": "Begrenzt auf",
|
"message": "Begrenzt auf",
|
||||||
"description": "Label text; used in prefs/network"
|
"description": "Label text; used in prefs/network"
|
||||||
},
|
},
|
||||||
|
"links": {
|
||||||
|
"message": "Links",
|
||||||
|
"description": "Links tab label (short); select window"
|
||||||
|
},
|
||||||
|
"manager_short": {
|
||||||
|
"message": "Manager",
|
||||||
|
"description": "Menu text"
|
||||||
|
},
|
||||||
"manager_status_items": {
|
"manager_status_items": {
|
||||||
"message": "$COMPLETE$ von $TOTAL$ Downloads beendet ($SHOWING$ angezeigt), $RUNNING$ laufend",
|
"message": "$COMPLETE$ von $TOTAL$ Downloads beendet ($SHOWING$ angezeigt), $RUNNING$ laufend",
|
||||||
"description": "Status bar text; manager",
|
"description": "Status bar text; manager",
|
||||||
@ -579,18 +405,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"manager_short": {
|
|
||||||
"message": "Manager",
|
|
||||||
"description": "Menu text"
|
|
||||||
},
|
|
||||||
"manager_title": {
|
"manager_title": {
|
||||||
"message": "DownThemAll! Manager",
|
"message": "DownThemAll! Manager",
|
||||||
"description": "Window/tab title"
|
"description": "Window/tab title"
|
||||||
},
|
},
|
||||||
|
"mask": {
|
||||||
|
"message": "Maske",
|
||||||
|
"description": "Renaming mask (short); used in e.g. select"
|
||||||
|
},
|
||||||
"mask_default": {
|
"mask_default": {
|
||||||
"message": "Standard-Maske",
|
"message": "Standard-Maske",
|
||||||
"description": "Status text; Used in the mask column, select window"
|
"description": "Status text; Used in the mask column, select window"
|
||||||
},
|
},
|
||||||
|
"media": {
|
||||||
|
"message": "Medien",
|
||||||
|
"description": "Media label (short)"
|
||||||
|
},
|
||||||
|
"missing": {
|
||||||
|
"message": "Fehlt",
|
||||||
|
"description": "Status text in manager"
|
||||||
|
},
|
||||||
"move_bottom": {
|
"move_bottom": {
|
||||||
"message": "Ende",
|
"message": "Ende",
|
||||||
"description": "Action for moving a download to the bottom"
|
"description": "Action for moving a download to the bottom"
|
||||||
@ -617,18 +451,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"NETWORK_FAILED": {
|
||||||
|
"message": "Netzwerkfehler",
|
||||||
|
"description": "Error Message"
|
||||||
|
},
|
||||||
"never_ask_again": {
|
"never_ask_again": {
|
||||||
"message": "Nicht wieder fragen",
|
"message": "Nicht wieder fragen",
|
||||||
"description": "Donation button"
|
"description": "Donation button"
|
||||||
},
|
},
|
||||||
"no_links": {
|
|
||||||
"message": "Keine Links gefunden!",
|
|
||||||
"description": "Notification text"
|
|
||||||
},
|
|
||||||
"noitems_label": {
|
"noitems_label": {
|
||||||
"message": "Nichts ausgewählt",
|
"message": "Nichts ausgewählt",
|
||||||
"description": "Status bar text in select"
|
"description": "Status bar text in select"
|
||||||
},
|
},
|
||||||
|
"no_links": {
|
||||||
|
"message": "Keine Links gefunden!",
|
||||||
|
"description": "Notification text"
|
||||||
|
},
|
||||||
"numitems_label": {
|
"numitems_label": {
|
||||||
"message": "$ITEMS$ Downloads ausgewählt",
|
"message": "$ITEMS$ Downloads ausgewählt",
|
||||||
"description": "Status bar text in select; Number of items selected (label)",
|
"description": "Status bar text in select; Number of items selected (label)",
|
||||||
@ -639,6 +477,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ok": {
|
||||||
|
"message": "OK",
|
||||||
|
"description": "Button text; Used in message boxes"
|
||||||
|
},
|
||||||
"open_directory": {
|
"open_directory": {
|
||||||
"message": "Verzeichnis öffnen",
|
"message": "Verzeichnis öffnen",
|
||||||
"description": "Menu text; manager context"
|
"description": "Menu text; manager context"
|
||||||
@ -663,10 +505,26 @@
|
|||||||
"message": "Netzwerk",
|
"message": "Netzwerk",
|
||||||
"description": "Pref tab text"
|
"description": "Pref tab text"
|
||||||
},
|
},
|
||||||
|
"paused": {
|
||||||
|
"message": "Pausiert",
|
||||||
|
"description": "Status text; manager"
|
||||||
|
},
|
||||||
"pause_download": {
|
"pause_download": {
|
||||||
"message": "Pausieren",
|
"message": "Pausieren",
|
||||||
"description": "Action for pausing a download"
|
"description": "Action for pausing a download"
|
||||||
},
|
},
|
||||||
|
"prefs_conflicts": {
|
||||||
|
"message": "Wenn eine Datei bereits existiert",
|
||||||
|
"description": "Preferences/General; group text"
|
||||||
|
},
|
||||||
|
"prefs_short": {
|
||||||
|
"message": "Einstellungen",
|
||||||
|
"description": "Menu text; Preferences"
|
||||||
|
},
|
||||||
|
"prefs_title": {
|
||||||
|
"message": "DownThemAll! Einstellungen",
|
||||||
|
"description": "Window/tab title; Preferences"
|
||||||
|
},
|
||||||
"pref_add_paused": {
|
"pref_add_paused": {
|
||||||
"message": "Neue Downloads immer pausiert hinzufügen, anstatt sie direkt zu starten",
|
"message": "Neue Downloads immer pausiert hinzufügen, anstatt sie direkt zu starten",
|
||||||
"description": "Preferences/General"
|
"description": "Preferences/General"
|
||||||
@ -687,14 +545,26 @@
|
|||||||
"message": "Keine allgemeinen Menü-Eintrage anzeigen",
|
"message": "Keine allgemeinen Menü-Eintrage anzeigen",
|
||||||
"description": "Preferences/General"
|
"description": "Preferences/General"
|
||||||
},
|
},
|
||||||
|
"pref_manager": {
|
||||||
|
"message": "Manager",
|
||||||
|
"description": "Preferences/General; group text"
|
||||||
|
},
|
||||||
"pref_manager_tooltip": {
|
"pref_manager_tooltip": {
|
||||||
"message": "Keine Tooltips im Manager-Tab anzeigen",
|
"message": "Keine Tooltips im Manager-Tab anzeigen",
|
||||||
"description": "Preferences/General"
|
"description": "Preferences/General"
|
||||||
},
|
},
|
||||||
|
"pref_netglobal": {
|
||||||
|
"message": "Allgemeine Netzwerk-Beschränkungen",
|
||||||
|
"description": "Preferences/General; group text"
|
||||||
|
},
|
||||||
"pref_open_manager_on_queue": {
|
"pref_open_manager_on_queue": {
|
||||||
"message": "Den Manager öffnen nachdem neue Downloads zur Warteschlange hinzugefügt wurden",
|
"message": "Den Manager öffnen nachdem neue Downloads zur Warteschlange hinzugefügt wurden",
|
||||||
"description": "Preferences/General"
|
"description": "Preferences/General"
|
||||||
},
|
},
|
||||||
|
"pref_queueing": {
|
||||||
|
"message": "Download-Warteschlange",
|
||||||
|
"description": "Preferences/General; group text"
|
||||||
|
},
|
||||||
"pref_queue_notification": {
|
"pref_queue_notification": {
|
||||||
"message": "Benachrichtigung anzeigen, wenn neue Downloads hinzugefügt wurden",
|
"message": "Benachrichtigung anzeigen, wenn neue Downloads hinzugefügt wurden",
|
||||||
"description": "Preferences/General"
|
"description": "Preferences/General"
|
||||||
@ -711,37 +581,13 @@
|
|||||||
"message": "Versuche Text-Links in Webseiten zu finden (langsamer)",
|
"message": "Versuche Text-Links in Webseiten zu finden (langsamer)",
|
||||||
"description": "Preferences/General"
|
"description": "Preferences/General"
|
||||||
},
|
},
|
||||||
"pref_manager": {
|
|
||||||
"message": "Manager",
|
|
||||||
"description": "Preferences/General; group text"
|
|
||||||
},
|
|
||||||
"pref_netglobal": {
|
|
||||||
"message": "Allgemeine Netzwerk-Beschränkungen",
|
|
||||||
"description": "Preferences/General; group text"
|
|
||||||
},
|
|
||||||
"pref_queueing": {
|
|
||||||
"message": "Download-Warteschlange",
|
|
||||||
"description": "Preferences/General; group text"
|
|
||||||
},
|
|
||||||
"pref_ui": {
|
"pref_ui": {
|
||||||
"message": "Benutzeroberfläche",
|
"message": "Benutzeroberfläche",
|
||||||
"description": "Preferences/General; group text"
|
"description": "Preferences/General; group text"
|
||||||
},
|
},
|
||||||
"prefs_conflicts": {
|
"queued": {
|
||||||
"message": "Wenn eine Datei bereits existiert",
|
"message": "Wartend",
|
||||||
"description": "Preferences/General; group text"
|
"description": "Status text"
|
||||||
},
|
|
||||||
"prefs_short": {
|
|
||||||
"message": "Einstellungen",
|
|
||||||
"description": "Menu text; Preferences"
|
|
||||||
},
|
|
||||||
"prefs_title": {
|
|
||||||
"message": "DownThemAll! Einstellungen",
|
|
||||||
"description": "Window/tab title; Preferences"
|
|
||||||
},
|
|
||||||
"queue_finished": {
|
|
||||||
"message": "Die Download-Warteschlange ist fertig",
|
|
||||||
"description": "Notification text"
|
|
||||||
},
|
},
|
||||||
"queued_download": {
|
"queued_download": {
|
||||||
"message": "Ein Download hinzugefügt!",
|
"message": "Ein Download hinzugefügt!",
|
||||||
@ -757,6 +603,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"queue_finished": {
|
||||||
|
"message": "Die Download-Warteschlange ist fertig",
|
||||||
|
"description": "Notification text"
|
||||||
|
},
|
||||||
|
"referrer": {
|
||||||
|
"message": "Referrer",
|
||||||
|
"description": "Label for \"Referrer\""
|
||||||
|
},
|
||||||
|
"remember": {
|
||||||
|
"message": "Diese Entscheidung merken",
|
||||||
|
"description": "Checkbox text for confirmation, e.g. when removing a download in manager"
|
||||||
|
},
|
||||||
"remove_all_complete_downloads": {
|
"remove_all_complete_downloads": {
|
||||||
"message": "Alle fertigen entfernen",
|
"message": "Alle fertigen entfernen",
|
||||||
"description": "Menu text"
|
"description": "Menu text"
|
||||||
@ -831,18 +689,18 @@
|
|||||||
"message": "Download entfernen",
|
"message": "Download entfernen",
|
||||||
"description": "Action for removing a download, no matter what state"
|
"description": "Action for removing a download, no matter what state"
|
||||||
},
|
},
|
||||||
"remove_download_question": {
|
|
||||||
"message": "Wirklich alle ausgewählten Downloads entfernen?",
|
|
||||||
"description": "Messagebox text"
|
|
||||||
},
|
|
||||||
"remove_downloads": {
|
"remove_downloads": {
|
||||||
"message": "Downloas entfernen",
|
"message": "Downloads entfernen",
|
||||||
"description": "Menu text"
|
"description": "Menu text"
|
||||||
},
|
},
|
||||||
"remove_downloads_title": {
|
"remove_downloads_title": {
|
||||||
"message": "Wirklich Downloads entfernen?",
|
"message": "Wirklich Downloads entfernen?",
|
||||||
"description": "Messagebox title; manager"
|
"description": "Messagebox title; manager"
|
||||||
},
|
},
|
||||||
|
"remove_download_question": {
|
||||||
|
"message": "Wirklich alle ausgewählten Downloads entfernen?",
|
||||||
|
"description": "Messagebox text"
|
||||||
|
},
|
||||||
"remove_failed_downloads": {
|
"remove_failed_downloads": {
|
||||||
"message": "Fehlgeschlagene entfernen",
|
"message": "Fehlgeschlagene entfernen",
|
||||||
"description": "Menu text"
|
"description": "Menu text"
|
||||||
@ -889,6 +747,10 @@
|
|||||||
"message": "Ausgewählte entfernen",
|
"message": "Ausgewählte entfernen",
|
||||||
"description": "Menu text"
|
"description": "Menu text"
|
||||||
},
|
},
|
||||||
|
"rename": {
|
||||||
|
"message": "Umbenennen",
|
||||||
|
"description": "UI for renaming; currently unused"
|
||||||
|
},
|
||||||
"renamer_batch": {
|
"renamer_batch": {
|
||||||
"message": "Batch Nummer",
|
"message": "Batch Nummer",
|
||||||
"description": "Mask text; see mask button"
|
"description": "Mask text; see mask button"
|
||||||
@ -1005,6 +867,14 @@
|
|||||||
"message": "Datum hinzugefügt - Jahr",
|
"message": "Datum hinzugefügt - Jahr",
|
||||||
"description": "Mask text; see mask button"
|
"description": "Mask text; see mask button"
|
||||||
},
|
},
|
||||||
|
"renmask": {
|
||||||
|
"message": "Umbenennungsmaske",
|
||||||
|
"description": "Renaming mask (long)"
|
||||||
|
},
|
||||||
|
"reset": {
|
||||||
|
"message": "Zurücksetzen",
|
||||||
|
"description": "Button text; pref window"
|
||||||
|
},
|
||||||
"reset_confirmations": {
|
"reset_confirmations": {
|
||||||
"message": "Gemerkte Entscheidungen zurücksetzen",
|
"message": "Gemerkte Entscheidungen zurücksetzen",
|
||||||
"description": "Button text; pref/General"
|
"description": "Button text; pref/General"
|
||||||
@ -1025,6 +895,18 @@
|
|||||||
"message": "Fortsetzen",
|
"message": "Fortsetzen",
|
||||||
"description": "Action for resuming a download"
|
"description": "Action for resuming a download"
|
||||||
},
|
},
|
||||||
|
"running": {
|
||||||
|
"message": "Laufend",
|
||||||
|
"description": "Status text"
|
||||||
|
},
|
||||||
|
"save": {
|
||||||
|
"message": "Speichern",
|
||||||
|
"description": "Button text; e.g. prefs/Network"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"message": "Suchen…",
|
||||||
|
"description": "Placeholder text; manager status search field"
|
||||||
|
},
|
||||||
"select_all": {
|
"select_all": {
|
||||||
"message": "Alles auswählen",
|
"message": "Alles auswählen",
|
||||||
"description": "Menu text; e.g. select context"
|
"description": "Menu text; e.g. select context"
|
||||||
@ -1041,6 +923,22 @@
|
|||||||
"message": "DownThemAll! - Downloads auswählen",
|
"message": "DownThemAll! - Downloads auswählen",
|
||||||
"description": "Title of the select window"
|
"description": "Title of the select window"
|
||||||
},
|
},
|
||||||
|
"SERVER_BAD_CONTENT": {
|
||||||
|
"message": "Nicht gefunden",
|
||||||
|
"description": "Error message"
|
||||||
|
},
|
||||||
|
"SERVER_FAILED": {
|
||||||
|
"message": "Server-Fehler",
|
||||||
|
"description": "Error message"
|
||||||
|
},
|
||||||
|
"SERVER_FORBIDDEN": {
|
||||||
|
"message": "Nicht erlaubt",
|
||||||
|
"description": "Error message"
|
||||||
|
},
|
||||||
|
"SERVER_UNAUTHORIZED": {
|
||||||
|
"message": "Keine Berechtigung",
|
||||||
|
"description": "Error message"
|
||||||
|
},
|
||||||
"set_mask": {
|
"set_mask": {
|
||||||
"message": "Umbenennungsmaske setzen",
|
"message": "Umbenennungsmaske setzen",
|
||||||
"description": "Menu text; select window"
|
"description": "Menu text; select window"
|
||||||
@ -1050,30 +948,62 @@
|
|||||||
"description": "Header text; single window"
|
"description": "Header text; single window"
|
||||||
},
|
},
|
||||||
"single_header": {
|
"single_header": {
|
||||||
"message": "Download URL (link) und andere Optionen eingeben",
|
"message": "Download URL (Link) und andere Optionen eingeben",
|
||||||
"description": "Header text; single window"
|
"description": "Header text; single window"
|
||||||
},
|
},
|
||||||
"single_title": {
|
"single_title": {
|
||||||
"message": "DownThemAll! - Link hinzufügen",
|
"message": "DownThemAll! - Link hinzufügen",
|
||||||
"description": "Title of single window"
|
"description": "Title of single window"
|
||||||
},
|
},
|
||||||
"size_progress": {
|
"sizeB": {
|
||||||
"message": "$WRITTEN$ von $TOTAL$",
|
"message": "$S$B",
|
||||||
"description": "Status text; manager size column",
|
"description": "Size formatting; bytes",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"total": {
|
"s": {
|
||||||
"content": "$2",
|
|
||||||
"example": ""
|
|
||||||
},
|
|
||||||
"written": {
|
|
||||||
"content": "$1",
|
"content": "$1",
|
||||||
"example": ""
|
"example": "100b"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"size_unknown": {
|
"sizeGB": {
|
||||||
"message": "Unbekannt",
|
"message": "$S$GB",
|
||||||
"description": "Status text; manager size column"
|
"description": "Size formatting; giga bytes",
|
||||||
|
"placeholders": {
|
||||||
|
"s": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "100.200GB"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sizeKB": {
|
||||||
|
"message": "$S$KB",
|
||||||
|
"description": "Size formatting; kilo bytes",
|
||||||
|
"placeholders": {
|
||||||
|
"s": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "100.2KB"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sizeMB": {
|
||||||
|
"message": "$S$MB",
|
||||||
|
"description": "Size formatting; mega bytes",
|
||||||
|
"placeholders": {
|
||||||
|
"s": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "100.22MB"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sizePB": {
|
||||||
|
"message": "$S$PB",
|
||||||
|
"description": "Size formatting; peta bytes (you never know)",
|
||||||
|
"placeholders": {
|
||||||
|
"s": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "100.212PB"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"sizes_huge": {
|
"sizes_huge": {
|
||||||
"message": "Riesig (> $HIGH$)",
|
"message": "Riesig (> $HIGH$)",
|
||||||
@ -1127,6 +1057,64 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"sizeTB": {
|
||||||
|
"message": "$S$TB",
|
||||||
|
"description": "Size formatting; tera bytes (you never know)",
|
||||||
|
"placeholders": {
|
||||||
|
"s": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "100.002TB"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size_progress": {
|
||||||
|
"message": "$WRITTEN$ von $TOTAL$",
|
||||||
|
"description": "Status text; manager size column",
|
||||||
|
"placeholders": {
|
||||||
|
"total": {
|
||||||
|
"content": "$2",
|
||||||
|
"example": ""
|
||||||
|
},
|
||||||
|
"written": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size_unknown": {
|
||||||
|
"message": "Unbekannt",
|
||||||
|
"description": "Status text; manager size column"
|
||||||
|
},
|
||||||
|
"speedB": {
|
||||||
|
"message": "$SPEED$b/s",
|
||||||
|
"description": "Speed formatting; bytes",
|
||||||
|
"placeholders": {
|
||||||
|
"speed": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "100b/s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"speedKB": {
|
||||||
|
"message": "$SPEED$KB/s",
|
||||||
|
"description": "Speed formatting; kilo bytes",
|
||||||
|
"placeholders": {
|
||||||
|
"speed": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "100.1KB/s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"speedMB": {
|
||||||
|
"message": "$SPEED$MB/s",
|
||||||
|
"description": "Speed formatting; mega bytes",
|
||||||
|
"placeholders": {
|
||||||
|
"speed": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "100.20MB/s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"statusNetwork_active_title": {
|
"statusNetwork_active_title": {
|
||||||
"message": "Neue Downloads werden gestartet",
|
"message": "Neue Downloads werden gestartet",
|
||||||
"description": "Status bar tooltip; manager network icon"
|
"description": "Status bar tooltip; manager network icon"
|
||||||
@ -1135,8 +1123,12 @@
|
|||||||
"message": "Neue Downloads werden nicht gestartet",
|
"message": "Neue Downloads werden nicht gestartet",
|
||||||
"description": "Status bar tooltip; manager network icon"
|
"description": "Status bar tooltip; manager network icon"
|
||||||
},
|
},
|
||||||
|
"title": {
|
||||||
|
"message": "Titel",
|
||||||
|
"description": "Column text; Title label (short)"
|
||||||
|
},
|
||||||
"toggle_selected_items": {
|
"toggle_selected_items": {
|
||||||
"message": "Markierungen für Auswahl umgekehren",
|
"message": "Markierungen für Auswahl umkehren",
|
||||||
"description": "Menu text; select"
|
"description": "Menu text; select"
|
||||||
},
|
},
|
||||||
"tooltip_date": {
|
"tooltip_date": {
|
||||||
@ -1166,5 +1158,13 @@
|
|||||||
"uncheck_selected_items": {
|
"uncheck_selected_items": {
|
||||||
"message": "Markierung von Auswahl entfernen",
|
"message": "Markierung von Auswahl entfernen",
|
||||||
"description": "Menu text; select"
|
"description": "Menu text; select"
|
||||||
|
},
|
||||||
|
"unlimited": {
|
||||||
|
"message": "Unbegrenzt",
|
||||||
|
"description": "Option text; Prefs/Network"
|
||||||
|
},
|
||||||
|
"useonlyonce": {
|
||||||
|
"message": "Einmalig",
|
||||||
|
"description": "Label for Use-Once checkboxes"
|
||||||
}
|
}
|
||||||
}
|
}
|
1170
_locales/hu/messages.json
Normal file
1170
_locales/hu/messages.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -7,6 +7,18 @@
|
|||||||
"message": "id",
|
"message": "id",
|
||||||
"description": "Language code the locale will use, e.g. de or en-GB or pt-BR"
|
"description": "Language code the locale will use, e.g. de or en-GB or pt-BR"
|
||||||
},
|
},
|
||||||
|
"renamer_tags": {
|
||||||
|
"message": "Penanda Mask Penamaan",
|
||||||
|
"description": "Mask text; see mask button"
|
||||||
|
},
|
||||||
|
"renmask": {
|
||||||
|
"message": "Mask penamaan",
|
||||||
|
"description": "Renaming mask (long)"
|
||||||
|
},
|
||||||
|
"set_mask": {
|
||||||
|
"message": "Set Mask Penamaan",
|
||||||
|
"description": "Menu text; select window"
|
||||||
|
},
|
||||||
"addpaused": {
|
"addpaused": {
|
||||||
"message": "Tambahkan dalam kondisi terpause",
|
"message": "Tambahkan dalam kondisi terpause",
|
||||||
"description": "Action: Add paused"
|
"description": "Action: Add paused"
|
||||||
@ -280,15 +292,15 @@
|
|||||||
"description": "Message box title"
|
"description": "Message box title"
|
||||||
},
|
},
|
||||||
"filter_expression": {
|
"filter_expression": {
|
||||||
"message": "Ekspres-Filter",
|
"message": "Ekspresi",
|
||||||
"description": "Message box label"
|
"description": "Message box label"
|
||||||
},
|
},
|
||||||
"filter_label": {
|
"filter_label": {
|
||||||
"message": "Label-Filter",
|
"message": "Label",
|
||||||
"description": "Message box label"
|
"description": "Message box label"
|
||||||
},
|
},
|
||||||
"filter_types": {
|
"filter_types": {
|
||||||
"message": "Tipe-Filter",
|
"message": "Tipe",
|
||||||
"description": "Message box label"
|
"description": "Message box label"
|
||||||
},
|
},
|
||||||
"filter_type_link": {
|
"filter_type_link": {
|
||||||
@ -320,7 +332,7 @@
|
|||||||
"description": "Menu text"
|
"description": "Menu text"
|
||||||
},
|
},
|
||||||
"limited_to": {
|
"limited_to": {
|
||||||
"message": "Terbatas ke",
|
"message": "Batasi ke",
|
||||||
"description": "Label text; used in prefs/network"
|
"description": "Label text; used in prefs/network"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
@ -362,11 +374,11 @@
|
|||||||
"description": "Status text; Used in the mask column, select window"
|
"description": "Status text; Used in the mask column, select window"
|
||||||
},
|
},
|
||||||
"missing": {
|
"missing": {
|
||||||
"message": "Tidak Ada",
|
"message": "Hilang",
|
||||||
"description": "Status text in manager"
|
"description": "Status text in manager"
|
||||||
},
|
},
|
||||||
"move_bottom": {
|
"move_bottom": {
|
||||||
"message": "Bawah",
|
"message": "Ke Bawah",
|
||||||
"description": "Action for moving a download to the bottom"
|
"description": "Action for moving a download to the bottom"
|
||||||
},
|
},
|
||||||
"move_down": {
|
"move_down": {
|
||||||
@ -374,7 +386,7 @@
|
|||||||
"description": "Action for moving a download down"
|
"description": "Action for moving a download down"
|
||||||
},
|
},
|
||||||
"move_top": {
|
"move_top": {
|
||||||
"message": "Atas",
|
"message": "Ke Atas",
|
||||||
"description": "Action for moving a download to the top"
|
"description": "Action for moving a download to the top"
|
||||||
},
|
},
|
||||||
"move_up": {
|
"move_up": {
|
||||||
@ -560,7 +572,7 @@
|
|||||||
"description": "Menu text"
|
"description": "Menu text"
|
||||||
},
|
},
|
||||||
"remove_batch_downloads_question": {
|
"remove_batch_downloads_question": {
|
||||||
"message": "Hapus semua unduhan dari kumpulan yang sama dengan unduhan terpilih?",
|
"message": "Hapus semua unduhan dari batch yang sama dengan unduhan terpilih?",
|
||||||
"description": "Messagebox text"
|
"description": "Messagebox text"
|
||||||
},
|
},
|
||||||
"remove_complete_downloads": {
|
"remove_complete_downloads": {
|
||||||
@ -600,7 +612,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"remove_domain_downloads": {
|
"remove_domain_downloads": {
|
||||||
"message": "Hapus Domain Ini",
|
"message": "Hapus Unduhan Dari Domain Ini",
|
||||||
"description": "Menu text"
|
"description": "Menu text"
|
||||||
},
|
},
|
||||||
"remove_domain_downloads_question": {
|
"remove_domain_downloads_question": {
|
||||||
@ -630,7 +642,7 @@
|
|||||||
"description": "Messagebox text"
|
"description": "Messagebox text"
|
||||||
},
|
},
|
||||||
"remove_failed_downloads": {
|
"remove_failed_downloads": {
|
||||||
"message": "Gagal Menghapus",
|
"message": "Hapus Unduhan Gagal",
|
||||||
"description": "Menu text"
|
"description": "Menu text"
|
||||||
},
|
},
|
||||||
"remove_failed_downloads_question": {
|
"remove_failed_downloads_question": {
|
||||||
@ -648,11 +660,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"remove_missing": {
|
"remove_missing": {
|
||||||
"message": "Hapus Unduhan Yang Tidak Ada",
|
"message": "Hapus Unduhan Yang Hilang",
|
||||||
"description": "Menu text"
|
"description": "Menu text"
|
||||||
},
|
},
|
||||||
"remove_missing_downloads_question": {
|
"remove_missing_downloads_question": {
|
||||||
"message": "Hapus semua unduhan yang tidak ada?",
|
"message": "Hapus semua unduhan yang hilang?",
|
||||||
"description": "Messagebox text"
|
"description": "Messagebox text"
|
||||||
},
|
},
|
||||||
"remove_paused_downloads": {
|
"remove_paused_downloads": {
|
||||||
@ -664,7 +676,7 @@
|
|||||||
"description": "Messagebox text"
|
"description": "Messagebox text"
|
||||||
},
|
},
|
||||||
"remove_selected_complete_downloads": {
|
"remove_selected_complete_downloads": {
|
||||||
"message": "Hapus Yang Selesai Di Pilihan",
|
"message": "Hapus Yang Selesai Dari Unduhan Terpilih",
|
||||||
"description": "Menu text"
|
"description": "Menu text"
|
||||||
},
|
},
|
||||||
"remove_selected_complete_downloads_question": {
|
"remove_selected_complete_downloads_question": {
|
||||||
|
1170
_locales/it/messages.json
Normal file
1170
_locales/it/messages.json
Normal file
File diff suppressed because it is too large
Load Diff
1170
_locales/ja/messages.json
Normal file
1170
_locales/ja/messages.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -108,7 +108,7 @@
|
|||||||
"description": "Media label (short)"
|
"description": "Media label (short)"
|
||||||
},
|
},
|
||||||
"missing": {
|
"missing": {
|
||||||
"message": "Trūksta",
|
"message": "Nėra",
|
||||||
"description": "Status text in manager"
|
"description": "Status text in manager"
|
||||||
},
|
},
|
||||||
"NETWORK_FAILED": {
|
"NETWORK_FAILED": {
|
||||||
@ -692,15 +692,15 @@
|
|||||||
"description": "Preferences/General"
|
"description": "Preferences/General"
|
||||||
},
|
},
|
||||||
"pref_open_manager_on_queue": {
|
"pref_open_manager_on_queue": {
|
||||||
"message": "Atidaryti Menedžerio kortelę, po kai kurių parsisiuntimų atsiradimo eilėje",
|
"message": "Atidaryti Menedžerio kortelę po parsisiuntimo pridėjimo",
|
||||||
"description": "Preferences/General"
|
"description": "Preferences/General"
|
||||||
},
|
},
|
||||||
"pref_queue_notification": {
|
"pref_queue_notification": {
|
||||||
"message": "Parodyti pranešimą, kai eilėje atsiranda nauji parsisiuntimai",
|
"message": "Rodyti pranešimą po naujų parsisiuntimų pridėjimo",
|
||||||
"description": "Preferences/General"
|
"description": "Preferences/General"
|
||||||
},
|
},
|
||||||
"pref_remove_missing_on_init": {
|
"pref_remove_missing_on_init": {
|
||||||
"message": "Pašalinti trūkstamus parsisiuntimus po restarto",
|
"message": "Šalinti nepavykusius parsisiuntimus po restarto",
|
||||||
"description": "Preferences/General"
|
"description": "Preferences/General"
|
||||||
},
|
},
|
||||||
"pref_show_urls": {
|
"pref_show_urls": {
|
||||||
@ -740,7 +740,7 @@
|
|||||||
"description": "Window/tab title; Preferences"
|
"description": "Window/tab title; Preferences"
|
||||||
},
|
},
|
||||||
"queue_finished": {
|
"queue_finished": {
|
||||||
"message": "Parsisiuntimų eilė baigta",
|
"message": "Visi parsisiuntimai baigti",
|
||||||
"description": "Notification text"
|
"description": "Notification text"
|
||||||
},
|
},
|
||||||
"queued_download": {
|
"queued_download": {
|
||||||
@ -862,11 +862,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"remove_missing": {
|
"remove_missing": {
|
||||||
"message": "Išvalyti trūkstamus parsisiuntimus",
|
"message": "Išvalyti nepavykusius parsisiuntimus",
|
||||||
"description": "Menu text"
|
"description": "Menu text"
|
||||||
},
|
},
|
||||||
"remove_missing_downloads_question": {
|
"remove_missing_downloads_question": {
|
||||||
"message": "Norite išvalyti visus trūkstamus parsisiuntimus?",
|
"message": "Norite išvalyti visus nepavykusius parsisiuntimus?",
|
||||||
"description": "Messagebox text"
|
"description": "Messagebox text"
|
||||||
},
|
},
|
||||||
"remove_paused_downloads": {
|
"remove_paused_downloads": {
|
||||||
|
@ -19,6 +19,9 @@ import {
|
|||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
MenuClickInfo,
|
MenuClickInfo,
|
||||||
CHROME,
|
CHROME,
|
||||||
|
runtime,
|
||||||
|
history,
|
||||||
|
sessions,
|
||||||
} from "./browser";
|
} from "./browser";
|
||||||
import { Bus } from "./bus";
|
import { Bus } from "./bus";
|
||||||
import { filterInSitu } from "./util";
|
import { filterInSitu } from "./util";
|
||||||
@ -45,6 +48,9 @@ const CHROME_CONTEXTS = Object.freeze(new Set([
|
|||||||
|
|
||||||
async function runContentJob(tab: Tab, file: string, msg: any) {
|
async function runContentJob(tab: Tab, file: string, msg: any) {
|
||||||
try {
|
try {
|
||||||
|
if (tab && tab.incognito && msg) {
|
||||||
|
msg.private = tab.incognito;
|
||||||
|
}
|
||||||
const res = await tabs.executeScript(tab.id, {
|
const res = await tabs.executeScript(tab.id, {
|
||||||
file,
|
file,
|
||||||
allFrames: true,
|
allFrames: true,
|
||||||
@ -566,6 +572,43 @@ locale.then(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
(async function init() {
|
(async function init() {
|
||||||
|
const urlBase = runtime.getURL("");
|
||||||
|
history.onVisited.addListener(({url}: {url: string}) => {
|
||||||
|
if (!url || !url.startsWith(urlBase)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
history.deleteUrl({url});
|
||||||
|
});
|
||||||
|
const results: {url?: string}[] = await history.search({text: urlBase});
|
||||||
|
for (const {url} of results) {
|
||||||
|
if (!url) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
history.deleteUrl({url});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CHROME) {
|
||||||
|
const sessionRemover = async () => {
|
||||||
|
for (const s of await sessions.getRecentlyClosed()) {
|
||||||
|
if (s.tab) {
|
||||||
|
if (s.tab.url.startsWith(urlBase)) {
|
||||||
|
await sessions.forgetClosedTab(s.tab.windowId, s.tab.sessionId);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!s.window || !s.window.tabs || s.window.tabs.length > 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const [tab] = s.window.tabs;
|
||||||
|
if (tab.url.startsWith(urlBase)) {
|
||||||
|
await sessions.forgetClosedWindow(s.window.sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sessions.onChanged.addListener(sessionRemover);
|
||||||
|
await sessionRemover();
|
||||||
|
}
|
||||||
|
|
||||||
await Prefs.set("last-run", new Date());
|
await Prefs.set("last-run", new Date());
|
||||||
Prefs.get("global-turbo", false).then(v => adjustAction(v));
|
Prefs.get("global-turbo", false).then(v => adjustAction(v));
|
||||||
Prefs.on("global-turbo", (prefs, key, value) => {
|
Prefs.on("global-turbo", (prefs, key, value) => {
|
||||||
|
@ -19,6 +19,7 @@ export interface MessageSender {
|
|||||||
|
|
||||||
export interface Tab {
|
export interface Tab {
|
||||||
id?: number;
|
id?: number;
|
||||||
|
incognito?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MenuClickInfo {
|
export interface MenuClickInfo {
|
||||||
@ -39,17 +40,71 @@ export interface RawPort {
|
|||||||
postMessage: (message: any) => void;
|
postMessage: (message: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const {extension} = polyfill;
|
interface WebRequestFilter {
|
||||||
export const {notifications} = polyfill;
|
urls?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WebRequestListener {
|
||||||
|
addListener(
|
||||||
|
callback: Function,
|
||||||
|
filter: WebRequestFilter,
|
||||||
|
extraInfoSpec: string[]
|
||||||
|
): void;
|
||||||
|
removeListener(callback: Function): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Header = {name: string; value: string};
|
||||||
|
|
||||||
|
export interface DownloadOptions {
|
||||||
|
conflictAction: string;
|
||||||
|
filename: string;
|
||||||
|
saveAs: boolean;
|
||||||
|
url: string;
|
||||||
|
method?: string;
|
||||||
|
body?: string;
|
||||||
|
incognito?: boolean;
|
||||||
|
headers: Header[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DownloadsQuery {
|
||||||
|
id?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Downloads {
|
||||||
|
download(download: DownloadOptions): Promise<number>;
|
||||||
|
open(manId: number): Promise<void>;
|
||||||
|
show(manId: number): Promise<void>;
|
||||||
|
pause(manId: number): Promise<void>;
|
||||||
|
resume(manId: number): Promise<void>;
|
||||||
|
cancel(manId: number): Promise<void>;
|
||||||
|
erase(query: DownloadsQuery): Promise<void>;
|
||||||
|
search(query: DownloadsQuery): Promise<any[]>;
|
||||||
|
getFileIcon(id: number, options?: any): Promise<string>;
|
||||||
|
setShelfEnabled(state: boolean): void;
|
||||||
|
onCreated: ExtensionListener;
|
||||||
|
onChanged: ExtensionListener;
|
||||||
|
onErased: ExtensionListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WebRequest {
|
||||||
|
onBeforeSendHeaders: WebRequestListener;
|
||||||
|
onSendHeaders: WebRequestListener;
|
||||||
|
onHeadersReceived: WebRequestListener;
|
||||||
|
}
|
||||||
|
|
||||||
export const {browserAction} = polyfill;
|
export const {browserAction} = polyfill;
|
||||||
export const {contextMenus} = polyfill;
|
export const {contextMenus} = polyfill;
|
||||||
export const {downloads} = polyfill;
|
export const {downloads}: {downloads: Downloads} = polyfill;
|
||||||
|
export const {extension} = polyfill;
|
||||||
|
export const {history} = polyfill;
|
||||||
export const {menus} = polyfill;
|
export const {menus} = polyfill;
|
||||||
|
export const {notifications} = polyfill;
|
||||||
export const {runtime} = polyfill;
|
export const {runtime} = polyfill;
|
||||||
|
export const {sessions} = polyfill;
|
||||||
export const {storage} = polyfill;
|
export const {storage} = polyfill;
|
||||||
export const {tabs} = polyfill;
|
export const {tabs} = polyfill;
|
||||||
export const {webNavigation} = polyfill;
|
export const {webNavigation} = polyfill;
|
||||||
export const {webRequest} = polyfill;
|
export const {webRequest}: {webRequest: WebRequest} = polyfill;
|
||||||
export const {windows} = polyfill;
|
export const {windows} = polyfill;
|
||||||
|
|
||||||
export const CHROME = navigator.appVersion.includes("Chrome/");
|
export const CHROME = navigator.appVersion.includes("Chrome/");
|
||||||
|
230
lib/cdheaderparser.ts
Normal file
230
lib/cdheaderparser.ts
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
/**
|
||||||
|
* (c) 2017 Rob Wu <rob@robwu.nl> (https://robwu.nl)
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
/* eslint-disable max-len,no-magic-numbers */
|
||||||
|
// License: MPL-2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This typescript port was done by Nils Maier based on
|
||||||
|
* https://github.com/Rob--W/open-in-browser/blob/83248155b633ed41bc9cdb1205042653e644abd2/extension/content-disposition.js
|
||||||
|
* Special thanks goes to Rob doing all the heavy lifting and putting
|
||||||
|
* it together in a reuseable, open source'd library.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const R_RFC6266 = /(?:^|;)\s*filename\*\s*=\s*([^";\s][^;\s]*|"(?:[^"\\]|\\"?)+"?)/i;
|
||||||
|
const R_RFC5987 = /(?:^|;)\s*filename\s*=\s*([^";\s][^;\s]*|"(?:[^"\\]|\\"?)+"?)/i;
|
||||||
|
|
||||||
|
function unquoteRFC2616(value: string) {
|
||||||
|
if (!value.startsWith("\"")) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = value.slice(1).split("\\\"");
|
||||||
|
// Find the first unescaped " and terminate there.
|
||||||
|
for (let i = 0; i < parts.length; ++i) {
|
||||||
|
const quotindex = parts[i].indexOf("\"");
|
||||||
|
if (quotindex !== -1) {
|
||||||
|
parts[i] = parts[i].slice(0, quotindex);
|
||||||
|
// Truncate and stop the iteration.
|
||||||
|
parts.length = i + 1;
|
||||||
|
}
|
||||||
|
parts[i] = parts[i].replace(/\\(.)/g, "$1");
|
||||||
|
}
|
||||||
|
value = parts.join("\"");
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CDHeaderParser {
|
||||||
|
private needsFixup: boolean;
|
||||||
|
|
||||||
|
// We need to keep this per instance, because of the global flag.
|
||||||
|
// Hence we need to reset it after a use.
|
||||||
|
private R_MULTI = /(?:^|;)\s*filename\*((?!0\d)\d+)(\*?)\s*=\s*([^";\s][^;\s]*|"(?:[^"\\]|\\"?)+"?)/gi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a content-disposition header, with relaxed spec tolerance
|
||||||
|
*
|
||||||
|
* @param {string} header Header to parse
|
||||||
|
* @returns {string} Parsed header
|
||||||
|
*/
|
||||||
|
parse(header: string) {
|
||||||
|
this.needsFixup = true;
|
||||||
|
|
||||||
|
// filename*=ext-value ("ext-value" from RFC 5987, referenced by RFC 6266).
|
||||||
|
{
|
||||||
|
const match = R_RFC6266.exec(header);
|
||||||
|
if (match) {
|
||||||
|
const [, tmp] = match;
|
||||||
|
let filename = unquoteRFC2616(tmp);
|
||||||
|
filename = unescape(filename);
|
||||||
|
filename = this.decodeRFC5897(filename);
|
||||||
|
filename = this.decodeRFC2047(filename);
|
||||||
|
return this.maybeFixupEncoding(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continuations (RFC 2231 section 3, referenced by RFC 5987 section 3.1).
|
||||||
|
// filename*n*=part
|
||||||
|
// filename*n=part
|
||||||
|
{
|
||||||
|
const tmp = this.getParamRFC2231(header);
|
||||||
|
if (tmp) {
|
||||||
|
// RFC 2047, section
|
||||||
|
const filename = this.decodeRFC2047(tmp);
|
||||||
|
return this.maybeFixupEncoding(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// filename=value (RFC 5987, section 4.1).
|
||||||
|
{
|
||||||
|
const match = R_RFC5987.exec(header);
|
||||||
|
if (match) {
|
||||||
|
const [, tmp] = match;
|
||||||
|
let filename = unquoteRFC2616(tmp);
|
||||||
|
filename = this.decodeRFC2047(filename);
|
||||||
|
return this.maybeFixupEncoding(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private maybeDecode(encoding: string, value: string) {
|
||||||
|
if (!encoding) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
const bytes = Array.from(value, c => c.charCodeAt(0));
|
||||||
|
if (!bytes.every(code => code <= 0xff)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
value = new TextDecoder(encoding, {fatal: true}).
|
||||||
|
decode(new Uint8Array(bytes));
|
||||||
|
this.needsFixup = false;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// TextDecoder constructor threw - unrecognized encoding.
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private maybeFixupEncoding(value: string) {
|
||||||
|
if (!this.needsFixup && /[\x80-\xff]/.test(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe multi-byte UTF-8.
|
||||||
|
value = this.maybeDecode("utf-8", value);
|
||||||
|
if (!this.needsFixup) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try iso-8859-1 encoding.
|
||||||
|
return this.maybeDecode("iso-8859-1", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getParamRFC2231(value: string) {
|
||||||
|
const matches: string[][] = [];
|
||||||
|
|
||||||
|
// Iterate over all filename*n= and filename*n*= with n being an integer
|
||||||
|
// of at least zero. Any non-zero number must not start with '0'.
|
||||||
|
let match;
|
||||||
|
this.R_MULTI.lastIndex = 0;
|
||||||
|
while ((match = this.R_MULTI.exec(value)) !== null) {
|
||||||
|
const [, num, quot, part] = match;
|
||||||
|
const n = parseInt(num, 10);
|
||||||
|
if (n in matches) {
|
||||||
|
// Ignore anything after the invalid second filename*0.
|
||||||
|
if (n === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
matches[n] = [quot, part];
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts: string[] = [];
|
||||||
|
for (let n = 0; n < matches.length; ++n) {
|
||||||
|
if (!(n in matches)) {
|
||||||
|
// Numbers must be consecutive. Truncate when there is a hole.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const [quot, rawPart] = matches[n];
|
||||||
|
let part = unquoteRFC2616(rawPart);
|
||||||
|
if (quot) {
|
||||||
|
part = unescape(part);
|
||||||
|
if (n === 0) {
|
||||||
|
part = this.decodeRFC5897(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parts.push(part);
|
||||||
|
}
|
||||||
|
return parts.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
private decodeRFC2047(value: string) {
|
||||||
|
// RFC 2047-decode the result. Firefox tried to drop support for it, but
|
||||||
|
// backed out because some servers use it - https://bugzil.la/875615
|
||||||
|
// Firefox's condition for decoding is here:
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
// https://searchfox.org/mozilla-central/rev/4a590a5a15e35d88a3b23dd6ac3c471cf85b04a8/netwerk/mime/nsMIMEHeaderParamImpl.cpp#742-748
|
||||||
|
|
||||||
|
// We are more strict and only recognize RFC 2047-encoding if the value
|
||||||
|
// starts with "=?", since then it is likely that the full value is
|
||||||
|
// RFC 2047-encoded.
|
||||||
|
|
||||||
|
// Firefox also decodes words even where RFC 2047 section 5 states:
|
||||||
|
// "An 'encoded-word' MUST NOT appear within a 'quoted-string'."
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
|
if (!value.startsWith("=?") || /[\x00-\x19\x80-\xff]/.test(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
// RFC 2047, section 2.4
|
||||||
|
// encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
|
||||||
|
// charset = token (but let's restrict to characters that denote a
|
||||||
|
// possibly valid encoding).
|
||||||
|
// encoding = q or b
|
||||||
|
// encoded-text = any printable ASCII character other than ? or space.
|
||||||
|
// ... but Firefox permits ? and space.
|
||||||
|
return value.replace(
|
||||||
|
/=\?([\w-]*)\?([QqBb])\?((?:[^?]|\?(?!=))*)\?=/g,
|
||||||
|
(_, charset, encoding, text) => {
|
||||||
|
if (encoding === "q" || encoding === "Q") {
|
||||||
|
// RFC 2047 section 4.2.
|
||||||
|
text = text.replace(/_/g, " ");
|
||||||
|
text = text.replace(/=([0-9a-fA-F]{2})/g,
|
||||||
|
(_: string, hex: string) => String.fromCharCode(parseInt(hex, 16)));
|
||||||
|
return this.maybeDecode(charset, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// else encoding is b or B - base64 (RFC 2047 section 4.1)
|
||||||
|
try {
|
||||||
|
text = atob(text);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
return this.maybeDecode(charset, text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private decodeRFC5897(extValue: string) {
|
||||||
|
// Decodes "ext-value" from RFC 5987.
|
||||||
|
const extEnd = extValue.indexOf("'");
|
||||||
|
if (extEnd < 0) {
|
||||||
|
// Some servers send "filename*=" without encoding'language' prefix,
|
||||||
|
// e.g. in https://github.com/Rob--W/open-in-browser/issues/26
|
||||||
|
// Let's accept the value like Firefox (57) (Chrome 62 rejects it).
|
||||||
|
return extValue;
|
||||||
|
}
|
||||||
|
const encoding = extValue.slice(0, extEnd);
|
||||||
|
const langvalue = extValue.slice(extEnd + 1);
|
||||||
|
// Ignore language (RFC 5987 section 3.2.1, and RFC 6266 section 4.1 ).
|
||||||
|
return this.maybeDecode(encoding, langvalue.replace(/^[^']*'/, ""));
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
// License: MIT
|
// License: MIT
|
||||||
|
|
||||||
import MimeType from "whatwg-mimetype";
|
// eslint-disable-next-line no-unused-vars
|
||||||
import { CHROME, downloads, webRequest } from "../browser";
|
import { CHROME, downloads, DownloadOptions } from "../browser";
|
||||||
import { Prefs } from "../prefs";
|
import { Prefs } from "../prefs";
|
||||||
import { PromiseSerializer } from "../pserializer";
|
import { PromiseSerializer } from "../pserializer";
|
||||||
import { filterInSitu, parsePath, sanitizePath } from "../util";
|
import { filterInSitu, parsePath } from "../util";
|
||||||
import { BaseDownload } from "./basedownload";
|
import { BaseDownload } from "./basedownload";
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
import { Manager } from "./man";
|
import { Manager } from "./man";
|
||||||
@ -21,62 +21,7 @@ import {
|
|||||||
QUEUED,
|
QUEUED,
|
||||||
RUNNING
|
RUNNING
|
||||||
} from "./state";
|
} from "./state";
|
||||||
|
import { Preroller } from "./preroller";
|
||||||
const PREROLL_HEURISTICS = /dl|attach|download|name|file|get|retr|^n$|\.(php|asp|py|pl|action|htm|shtm)/i;
|
|
||||||
const PREROLL_HOSTS = /4cdn|chan/;
|
|
||||||
const PREROLL_TIMEOUT = 10000;
|
|
||||||
const PREROLL_NOPE = new Set<string>();
|
|
||||||
|
|
||||||
function parseDisposition(disp: MimeType) {
|
|
||||||
if (!disp) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
let encoding = (disp.parameters.get("charset") || "utf-8").trim();
|
|
||||||
let file = (disp.parameters.get("filename") || "").trim().replace(/^(["'])(.*)\1$/, "$2");
|
|
||||||
if (!file) {
|
|
||||||
const encoded = disp.parameters.get("filename*");
|
|
||||||
if (!encoded) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
const pieces = encoded.split("'", 3);
|
|
||||||
if (pieces.length !== 3) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
encoding = pieces[0].trim() || encoding;
|
|
||||||
file = (pieces[3] || "").trim().replace(/^(["'])(.*)\1$/, "$2");
|
|
||||||
}
|
|
||||||
file = file.trim();
|
|
||||||
if (!file) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// And now for the tricky part...
|
|
||||||
// First unescape the string, to get the raw bytes
|
|
||||||
// not utf-8-interpreted bytes
|
|
||||||
// Then convert the string into an uint8[]
|
|
||||||
// Then decode
|
|
||||||
return new TextDecoder(encoding).decode(
|
|
||||||
new Uint8Array(unescape(file).split("").map(e => e.charCodeAt(0)))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
catch (ex) {
|
|
||||||
console.error("Cannot decode", encoding, file, ex);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
type Header = {name: string; value: string};
|
|
||||||
interface Options {
|
|
||||||
conflictAction: string;
|
|
||||||
filename: string;
|
|
||||||
saveAs: boolean;
|
|
||||||
url: string;
|
|
||||||
method?: string;
|
|
||||||
body?: string;
|
|
||||||
incognito?: boolean;
|
|
||||||
headers: Header[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Download extends BaseDownload {
|
export class Download extends BaseDownload {
|
||||||
public manager: Manager;
|
public manager: Manager;
|
||||||
@ -120,23 +65,23 @@ export class Download extends BaseDownload {
|
|||||||
if (this.manId) {
|
if (this.manId) {
|
||||||
const {manId: id} = this;
|
const {manId: id} = this;
|
||||||
try {
|
try {
|
||||||
const state = await downloads.search({id});
|
const state = (await downloads.search({id})).pop() || {};
|
||||||
if (state[0].state === "in_progress") {
|
if (state.state === "in_progress" && !state.error && !state.paused) {
|
||||||
this.changeState(RUNNING);
|
this.changeState(RUNNING);
|
||||||
this.updateStateFromBrowser();
|
this.updateStateFromBrowser();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (state[0].state === "complete") {
|
if (state.state === "complete") {
|
||||||
this.changeState(DONE);
|
this.changeState(DONE);
|
||||||
this.updateStateFromBrowser();
|
this.updateStateFromBrowser();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!state[0].canResume) {
|
if (!state.canResume) {
|
||||||
throw new Error("Cannot resume");
|
throw new Error("Cannot resume");
|
||||||
}
|
}
|
||||||
// Cannot await here
|
// Cannot await here
|
||||||
// Firefox bug: will not return until download is finished
|
// Firefox bug: will not return until download is finished
|
||||||
downloads.resume(id).catch(() => {});
|
downloads.resume(id).catch(console.error);
|
||||||
this.changeState(RUNNING);
|
this.changeState(RUNNING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -164,7 +109,7 @@ export class Download extends BaseDownload {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const options: Options = {
|
const options: DownloadOptions = {
|
||||||
conflictAction: await Prefs.get("conflict-action"),
|
conflictAction: await Prefs.get("conflict-action"),
|
||||||
filename: this.dest.full,
|
filename: this.dest.full,
|
||||||
saveAs: false,
|
saveAs: false,
|
||||||
@ -184,6 +129,12 @@ export class Download extends BaseDownload {
|
|||||||
value: this.referrer
|
value: this.referrer
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
else if (CHROME) {
|
||||||
|
options.headers.push({
|
||||||
|
name: "X-DTA-ID",
|
||||||
|
value: this.sessionId.toString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
if (this.manId) {
|
if (this.manId) {
|
||||||
this.manager.removeManId(this.manId);
|
this.manager.removeManId(this.manId);
|
||||||
}
|
}
|
||||||
@ -210,39 +161,30 @@ export class Download extends BaseDownload {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private get shouldPreroll() {
|
|
||||||
const {pathname, search, host} = this.uURL;
|
|
||||||
if (PREROLL_NOPE.has(host)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!this.renamer.p_ext) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (search.length) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (this.uURL.pathname.endsWith("/")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (PREROLL_HEURISTICS.test(pathname)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (PREROLL_HOSTS.test(host)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async maybePreroll() {
|
private async maybePreroll() {
|
||||||
try {
|
try {
|
||||||
if (this.prerolled) {
|
if (this.prerolled) {
|
||||||
// Check again, just in case, async and all
|
// Check again, just in case, async and all
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.shouldPreroll) {
|
const roller = new Preroller(this);
|
||||||
|
if (!roller.shouldPreroll) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await (CHROME ? this.prerollChrome() : this.prerollFirefox());
|
const res = await roller.roll();
|
||||||
|
if (!res) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (res.mime) {
|
||||||
|
this.mime = res.mime;
|
||||||
|
}
|
||||||
|
if (res.name) {
|
||||||
|
this.serverName = res.name;
|
||||||
|
}
|
||||||
|
if (res.error) {
|
||||||
|
this.cancel();
|
||||||
|
this.error = res.error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
console.error("Failed to preroll", this, ex.toString(), ex.stack, ex);
|
console.error("Failed to preroll", this, ex.toString(), ex.stack, ex);
|
||||||
@ -255,101 +197,6 @@ export class Download extends BaseDownload {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async prerollFirefox() {
|
|
||||||
const controller = new AbortController();
|
|
||||||
const {signal} = controller;
|
|
||||||
const res = await fetch(this.uURL.toString(), {
|
|
||||||
method: "HEAD",
|
|
||||||
mode: "same-origin",
|
|
||||||
signal,
|
|
||||||
});
|
|
||||||
controller.abort();
|
|
||||||
const {headers} = res;
|
|
||||||
this.prerollFinialize(headers, res);
|
|
||||||
}
|
|
||||||
|
|
||||||
async prerollChrome() {
|
|
||||||
let rid = "";
|
|
||||||
const rurl = this.uURL.toString();
|
|
||||||
let listener: any;
|
|
||||||
const wr = new Promise<any[]>(resolve => {
|
|
||||||
listener = (details: any) => {
|
|
||||||
const {url, requestId, statusCode} = details;
|
|
||||||
if (rid !== requestId && url !== rurl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line no-magic-numbers
|
|
||||||
if (statusCode >= 300 && statusCode < 400) {
|
|
||||||
// Redirect, continue tracking;
|
|
||||||
rid = requestId;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
resolve(details.responseHeaders);
|
|
||||||
};
|
|
||||||
webRequest.onHeadersReceived.addListener(
|
|
||||||
listener, {urls: ["<all_urls>"]}, ["responseHeaders"]);
|
|
||||||
});
|
|
||||||
const p = Promise.race([
|
|
||||||
wr,
|
|
||||||
new Promise<any[]>((_, reject) =>
|
|
||||||
setTimeout(() => reject(new Error("timeout")), PREROLL_TIMEOUT))
|
|
||||||
]);
|
|
||||||
|
|
||||||
p.finally(() => {
|
|
||||||
webRequest.onHeadersReceived.removeListener(listener);
|
|
||||||
});
|
|
||||||
const controller = new AbortController();
|
|
||||||
const {signal} = controller;
|
|
||||||
const res = await fetch(rurl, {
|
|
||||||
method: "HEAD",
|
|
||||||
signal,
|
|
||||||
});
|
|
||||||
controller.abort();
|
|
||||||
const headers = await p;
|
|
||||||
this.prerollFinialize(
|
|
||||||
new Headers(headers.map(i => [i.name, i.value])), res);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private prerollFinialize(headers: Headers, res: Response) {
|
|
||||||
const type = MimeType.parse(headers.get("content-type") || "");
|
|
||||||
const dispHeader = headers.get("content-disposition");
|
|
||||||
let file = "";
|
|
||||||
if (dispHeader) {
|
|
||||||
const disp = new MimeType(`${type && type.toString() || "application/octet-stream"}; ${dispHeader}`);
|
|
||||||
file = parseDisposition(disp);
|
|
||||||
// Sanitize
|
|
||||||
file = sanitizePath(file.replace(/[/\\]+/g, "-"));
|
|
||||||
}
|
|
||||||
if (type) {
|
|
||||||
this.mime = type.essence;
|
|
||||||
}
|
|
||||||
this.serverName = file;
|
|
||||||
this.markDirty();
|
|
||||||
const {status} = res;
|
|
||||||
/* eslint-disable no-magic-numbers */
|
|
||||||
if (status === 404) {
|
|
||||||
this.cancel();
|
|
||||||
this.error = "SERVER_BAD_CONTENT";
|
|
||||||
}
|
|
||||||
else if (status === 403) {
|
|
||||||
this.cancel();
|
|
||||||
this.error = "SERVER_FORBIDDEN";
|
|
||||||
}
|
|
||||||
else if (status === 402 || status === 407) {
|
|
||||||
this.cancel();
|
|
||||||
this.error = "SERVER_UNAUTHORIZED";
|
|
||||||
}
|
|
||||||
else if (status === 400 || status === 405) {
|
|
||||||
PREROLL_NOPE.add(this.uURL.host);
|
|
||||||
}
|
|
||||||
else if (status > 400 && status < 500) {
|
|
||||||
this.cancel();
|
|
||||||
this.error = "SERVER_FAILED";
|
|
||||||
}
|
|
||||||
/* eslint-enable no-magic-numbers */
|
|
||||||
}
|
|
||||||
|
|
||||||
resume(forced = false) {
|
resume(forced = false) {
|
||||||
if (!(FORCABLE & this.state)) {
|
if (!(FORCABLE & this.state)) {
|
||||||
return;
|
return;
|
||||||
@ -467,7 +314,10 @@ export class Download extends BaseDownload {
|
|||||||
this.markDirty();
|
this.markDirty();
|
||||||
switch (state.state) {
|
switch (state.state) {
|
||||||
case "in_progress":
|
case "in_progress":
|
||||||
if (error) {
|
if (state.paused) {
|
||||||
|
this.changeState(PAUSED);
|
||||||
|
}
|
||||||
|
else if (error) {
|
||||||
this.cancel();
|
this.cancel();
|
||||||
this.error = error;
|
this.error = error;
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,9 @@ import { Download } from "./download";
|
|||||||
import { ManagerPort } from "./port";
|
import { ManagerPort } from "./port";
|
||||||
import { Scheduler } from "./scheduler";
|
import { Scheduler } from "./scheduler";
|
||||||
import { Limits } from "./limits";
|
import { Limits } from "./limits";
|
||||||
import { downloads, runtime } from "../browser";
|
import { downloads, runtime, webRequest, CHROME } from "../browser";
|
||||||
|
|
||||||
|
const US = runtime.getURL("");
|
||||||
|
|
||||||
const AUTOSAVE_TIMEOUT = 2000;
|
const AUTOSAVE_TIMEOUT = 2000;
|
||||||
const DIRTY_TIMEOUT = 100;
|
const DIRTY_TIMEOUT = 100;
|
||||||
@ -83,6 +84,14 @@ export class Manager extends EventEmitter {
|
|||||||
Limits.on("changed", () => {
|
Limits.on("changed", () => {
|
||||||
this.resetScheduler();
|
this.resetScheduler();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (CHROME) {
|
||||||
|
webRequest.onBeforeSendHeaders.addListener(
|
||||||
|
this.stuffReferrer.bind(this),
|
||||||
|
{urls: ["<all_urls>"]},
|
||||||
|
["blocking", "requestHeaders", "extraHeaders"]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
@ -384,6 +393,31 @@ export class Manager extends EventEmitter {
|
|||||||
getMsgItems() {
|
getMsgItems() {
|
||||||
return this.items.map(e => e.toMsg());
|
return this.items.map(e => e.toMsg());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stuffReferrer(details: any): any {
|
||||||
|
if (details.tabId > 0 && !US.startsWith(details.initiator)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const sidx = details.requestHeaders.findIndex(
|
||||||
|
(e: any) => e.name.toLowerCase() === "x-dta-id");
|
||||||
|
if (sidx < 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const sid = parseInt(details.requestHeaders[sidx].value, 10);
|
||||||
|
details.requestHeaders.splice(sidx, 1);
|
||||||
|
const item = this.sids.get(sid);
|
||||||
|
if (!item) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
details.requestHeaders.push({
|
||||||
|
name: "Referer",
|
||||||
|
value: (item.uReferrer || item.uURL).toString()
|
||||||
|
});
|
||||||
|
const rv: any = {
|
||||||
|
requestHeaders: details.requestHeaders
|
||||||
|
};
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let inited: Promise<Manager>;
|
let inited: Promise<Manager>;
|
||||||
|
234
lib/manager/preroller.ts
Normal file
234
lib/manager/preroller.ts
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
"use strict";
|
||||||
|
// License: MIT
|
||||||
|
|
||||||
|
import MimeType from "whatwg-mimetype";
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
import { Download } from "./download";
|
||||||
|
import { CHROME, webRequest } from "../browser";
|
||||||
|
import { CDHeaderParser } from "../cdheaderparser";
|
||||||
|
import { sanitizePath, parsePath } from "../util";
|
||||||
|
import { MimeDB } from "../mime";
|
||||||
|
|
||||||
|
const PREROLL_HEURISTICS = /dl|attach|download|name|file|get|retr|^n$|\.(php|asp|py|pl|action|htm|shtm)/i;
|
||||||
|
const PREROLL_HOSTS = /4cdn|chan/;
|
||||||
|
const PREROLL_TIMEOUT = 10000;
|
||||||
|
const PREROLL_NOPE = new Set<string>();
|
||||||
|
|
||||||
|
/* eslint-disable no-magic-numbers */
|
||||||
|
const NOPE_STATUSES = Object.freeze(new Set([
|
||||||
|
400,
|
||||||
|
401,
|
||||||
|
402,
|
||||||
|
405,
|
||||||
|
416,
|
||||||
|
]));
|
||||||
|
/* eslint-enable no-magic-numbers */
|
||||||
|
|
||||||
|
const PREROLL_SEARCHEXTS = Object.freeze(new Set<string>([
|
||||||
|
"php",
|
||||||
|
"asp",
|
||||||
|
"aspx",
|
||||||
|
"inc",
|
||||||
|
"py",
|
||||||
|
"pl",
|
||||||
|
"action",
|
||||||
|
"htm",
|
||||||
|
"html",
|
||||||
|
"shtml"
|
||||||
|
]));
|
||||||
|
const NAME_TESTER = /\.[a-z0-9]{1,5}$/i;
|
||||||
|
const CDPARSER = new CDHeaderParser();
|
||||||
|
|
||||||
|
export interface PrerollResults {
|
||||||
|
error?: string;
|
||||||
|
name?: string;
|
||||||
|
mime?: string;
|
||||||
|
finalURL?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Preroller {
|
||||||
|
private readonly download: Download
|
||||||
|
|
||||||
|
constructor(download: Download) {
|
||||||
|
this.download = download;
|
||||||
|
}
|
||||||
|
|
||||||
|
get shouldPreroll() {
|
||||||
|
const {uURL, renamer} = this.download;
|
||||||
|
const {pathname, search, host} = uURL;
|
||||||
|
if (PREROLL_NOPE.has(host)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!renamer.p_ext) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (search.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (uURL.pathname.endsWith("/")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (PREROLL_HEURISTICS.test(pathname)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (PREROLL_HOSTS.test(host)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async roll() {
|
||||||
|
try {
|
||||||
|
return await (CHROME ? this.prerollChrome() : this.prerollFirefox());
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
console.error("Failed to preroll", this, ex.toString(), ex.stack, ex);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async prerollFirefox() {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const {signal} = controller;
|
||||||
|
const {uURL, uReferrer} = this.download;
|
||||||
|
const res = await fetch(uURL.toString(), {
|
||||||
|
method: "GET",
|
||||||
|
headers: new Headers({
|
||||||
|
Range: "bytes=0-1",
|
||||||
|
}),
|
||||||
|
mode: "same-origin",
|
||||||
|
signal,
|
||||||
|
referrer: (uReferrer || uURL).toString(),
|
||||||
|
});
|
||||||
|
if (res.body) {
|
||||||
|
res.body.cancel();
|
||||||
|
}
|
||||||
|
controller.abort();
|
||||||
|
const {headers} = res;
|
||||||
|
return this.finalize(headers, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async prerollChrome() {
|
||||||
|
let rid = "";
|
||||||
|
const {uURL, uReferrer} = this.download;
|
||||||
|
const rurl = uURL.toString();
|
||||||
|
let listener: any;
|
||||||
|
const wr = new Promise<any[]>(resolve => {
|
||||||
|
listener = (details: any) => {
|
||||||
|
const {url, requestId, statusCode} = details;
|
||||||
|
if (rid !== requestId && url !== rurl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-magic-numbers
|
||||||
|
if (statusCode >= 300 && statusCode < 400) {
|
||||||
|
// Redirect, continue tracking;
|
||||||
|
rid = requestId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(details.responseHeaders);
|
||||||
|
};
|
||||||
|
webRequest.onHeadersReceived.addListener(
|
||||||
|
listener, {urls: ["<all_urls>"]}, ["responseHeaders"]);
|
||||||
|
});
|
||||||
|
const p = Promise.race([
|
||||||
|
wr,
|
||||||
|
new Promise<any[]>((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error("timeout")), PREROLL_TIMEOUT))
|
||||||
|
]);
|
||||||
|
|
||||||
|
p.finally(() => {
|
||||||
|
webRequest.onHeadersReceived.removeListener(listener);
|
||||||
|
});
|
||||||
|
const controller = new AbortController();
|
||||||
|
const {signal} = controller;
|
||||||
|
const res = await fetch(rurl, {
|
||||||
|
method: "GET",
|
||||||
|
headers: new Headers({
|
||||||
|
"Range": "bytes=0-1",
|
||||||
|
"X-DTA-ID": this.download.sessionId.toString(),
|
||||||
|
}),
|
||||||
|
signal,
|
||||||
|
referrer: (uReferrer || uURL).toString(),
|
||||||
|
});
|
||||||
|
if (res.body) {
|
||||||
|
res.body.cancel();
|
||||||
|
}
|
||||||
|
controller.abort();
|
||||||
|
const headers = await p;
|
||||||
|
return this.finalize(
|
||||||
|
new Headers(headers.map(i => [i.name, i.value])), res);
|
||||||
|
}
|
||||||
|
|
||||||
|
private finalize(headers: Headers, res: Response): PrerollResults {
|
||||||
|
const rv: PrerollResults = {};
|
||||||
|
|
||||||
|
const type = MimeType.parse(headers.get("content-type") || "");
|
||||||
|
if (type) {
|
||||||
|
rv.mime = type.essence;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {p_ext: ext} = this.download.renamer;
|
||||||
|
const dispHeader = headers.get("content-disposition");
|
||||||
|
if (dispHeader) {
|
||||||
|
const file = CDPARSER.parse(dispHeader);
|
||||||
|
// Sanitize
|
||||||
|
rv.name = sanitizePath(file.replace(/[/\\]+/g, "-"));
|
||||||
|
}
|
||||||
|
else if (!ext || PREROLL_SEARCHEXTS.has(ext.toLocaleLowerCase())) {
|
||||||
|
const {searchParams} = this.download.uURL;
|
||||||
|
let detected = "";
|
||||||
|
for (const [, value] of searchParams) {
|
||||||
|
if (!NAME_TESTER.test(value)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const p = parsePath(value);
|
||||||
|
if (!p.base || !p.ext) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!MimeDB.hasExtension(p.ext)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (rv.mime) {
|
||||||
|
const mime = MimeDB.getMime(rv.mime);
|
||||||
|
if (mime && !mime.extensions.has(p.ext.toLowerCase())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const sanitized = sanitizePath(p.name);
|
||||||
|
if (sanitized.length <= detected.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
detected = sanitized;
|
||||||
|
}
|
||||||
|
if (detected) {
|
||||||
|
rv.name = detected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rv.finalURL = res.url;
|
||||||
|
|
||||||
|
/* eslint-disable no-magic-numbers */
|
||||||
|
const {status} = res;
|
||||||
|
if (status === 404) {
|
||||||
|
rv.error = "SERVER_BAD_CONTENT";
|
||||||
|
}
|
||||||
|
else if (status === 403) {
|
||||||
|
rv.error = "SERVER_FORBIDDEN";
|
||||||
|
}
|
||||||
|
else if (status === 402 || status === 407) {
|
||||||
|
rv.error = "SERVER_UNAUTHORIZED";
|
||||||
|
}
|
||||||
|
else if (NOPE_STATUSES.has(status)) {
|
||||||
|
PREROLL_NOPE.add(this.download.uURL.host);
|
||||||
|
if (PREROLL_NOPE.size > 1000) {
|
||||||
|
PREROLL_NOPE.delete(PREROLL_NOPE.keys().next().value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (status > 400 && status < 500) {
|
||||||
|
rv.error = "SERVER_FAILED";
|
||||||
|
}
|
||||||
|
/* eslint-enable no-magic-numbers */
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
12
lib/mime.ts
12
lib/mime.ts
@ -25,9 +25,11 @@ export class MimeInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MimeDB = new class {
|
export const MimeDB = new class MimeDB {
|
||||||
private readonly mimeToExts: Map<string, MimeInfo>;
|
private readonly mimeToExts: Map<string, MimeInfo>;
|
||||||
|
|
||||||
|
private readonly registeredExtensions: Set<string>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const exts = new Map<string, string[]>();
|
const exts = new Map<string, string[]>();
|
||||||
for (const [prim, more] of Object.entries(mime.e)) {
|
for (const [prim, more] of Object.entries(mime.e)) {
|
||||||
@ -42,6 +44,10 @@ export const MimeDB = new class {
|
|||||||
Object.entries(mime.m),
|
Object.entries(mime.m),
|
||||||
([mime, prim]) => [mime, new MimeInfo(mime, exts.get(prim) || [prim])]
|
([mime, prim]) => [mime, new MimeInfo(mime, exts.get(prim) || [prim])]
|
||||||
));
|
));
|
||||||
|
const all = Array.from(
|
||||||
|
this.mimeToExts.values(),
|
||||||
|
m => Array.from(m.extensions, e => e.toLowerCase()));
|
||||||
|
this.registeredExtensions = new Set(all.flat());
|
||||||
}
|
}
|
||||||
|
|
||||||
getPrimary(mime: string) {
|
getPrimary(mime: string) {
|
||||||
@ -52,4 +58,8 @@ export const MimeDB = new class {
|
|||||||
getMime(mime: string) {
|
getMime(mime: string) {
|
||||||
return this.mimeToExts.get(mime.trim().toLocaleLowerCase());
|
return this.mimeToExts.get(mime.trim().toLocaleLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasExtension(ext: string) {
|
||||||
|
return this.registeredExtensions.has(ext.toLowerCase());
|
||||||
|
}
|
||||||
}();
|
}();
|
||||||
|
@ -98,6 +98,7 @@ export async function select(links: BaseItem[], media: BaseItem[]) {
|
|||||||
type: "popup",
|
type: "popup",
|
||||||
});
|
});
|
||||||
const window = await windows.create(windowOptions);
|
const window = await windows.create(windowOptions);
|
||||||
|
tracker.track(window.id, null);
|
||||||
try {
|
try {
|
||||||
const port = await Promise.race<Port>([
|
const port = await Promise.race<Port>([
|
||||||
new Promise<Port>(resolve => Bus.oncePort("select", resolve)),
|
new Promise<Port>(resolve => Bus.oncePort("select", resolve)),
|
||||||
@ -186,8 +187,8 @@ export async function select(links: BaseItem[], media: BaseItem[]) {
|
|||||||
openPrefs();
|
openPrefs();
|
||||||
});
|
});
|
||||||
|
|
||||||
port.on("openUrls", ({urls}) => {
|
port.on("openUrls", ({urls, incognito}) => {
|
||||||
openUrls(urls);
|
openUrls(urls, incognito);
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -21,6 +21,7 @@ export async function single(item: BaseItem | null) {
|
|||||||
type: "popup",
|
type: "popup",
|
||||||
});
|
});
|
||||||
const window = await windows.create(windowOptions);
|
const window = await windows.create(windowOptions);
|
||||||
|
tracker.track(window.id, null);
|
||||||
try {
|
try {
|
||||||
const port: Port = await Promise.race<Port>([
|
const port: Port = await Promise.race<Port>([
|
||||||
new Promise<Port>(resolve => Bus.oncePort("single", resolve)),
|
new Promise<Port>(resolve => Bus.oncePort("single", resolve)),
|
||||||
|
@ -55,14 +55,16 @@ export class WindowStateTracker {
|
|||||||
|
|
||||||
getOptions(options: any) {
|
getOptions(options: any) {
|
||||||
const result = Object.assign(options, {
|
const result = Object.assign(options, {
|
||||||
width: this.width,
|
|
||||||
height: this.height,
|
|
||||||
state: this.state,
|
state: this.state,
|
||||||
});
|
});
|
||||||
|
if (result.state !== "maximized") {
|
||||||
|
result.width = this.width;
|
||||||
|
result.height = this.height;
|
||||||
if (this.top >= 0) {
|
if (this.top >= 0) {
|
||||||
result.top = this.top;
|
result.top = this.top;
|
||||||
result.left = this.left;
|
result.left = this.left;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,37 +8,45 @@ import DEFAULT_ICONS from "../data/icons.json";
|
|||||||
const DONATE_URL = "https://www.downthemall.org/howto/donate/";
|
const DONATE_URL = "https://www.downthemall.org/howto/donate/";
|
||||||
const MANAGER_URL = "/windows/manager.html";
|
const MANAGER_URL = "/windows/manager.html";
|
||||||
|
|
||||||
export async function mostRecentBrowser(): Promise<any> {
|
export async function mostRecentBrowser(incognito: boolean): Promise<any> {
|
||||||
let window;
|
let window;
|
||||||
try {
|
try {
|
||||||
window = await windows.getCurrent({windowTypes: ["normal"]});
|
window = await windows.getCurrent();
|
||||||
if (window.type !== "normal") {
|
if (window.type !== "normal") {
|
||||||
throw new Error("not a normal window");
|
throw new Error("not a normal window");
|
||||||
}
|
}
|
||||||
|
if (incognito && !window.incognito) {
|
||||||
|
throw new Error("Not incognito");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
try {
|
try {
|
||||||
window = await windows.getlastFocused({windowTypes: ["normal"]});
|
window = await windows.getlastFocused();
|
||||||
if (window.type !== "normal") {
|
if (window.type !== "normal") {
|
||||||
throw new Error("not a normal window");
|
throw new Error("not a normal window");
|
||||||
}
|
}
|
||||||
|
if (incognito && !window.incognito) {
|
||||||
|
throw new Error("Not incognito");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
window = Array.from(await windows.getAll({windowTypes: ["normal"]})).
|
window = Array.from(await windows.getAll({windowTypes: ["normal"]})).
|
||||||
filter((w: any) => w.type === "normal").pop();
|
filter(
|
||||||
|
(w: any) => w.type === "normal" && !!w.incognito === !!incognito).
|
||||||
|
pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!window) {
|
if (!window) {
|
||||||
window = await windows.create({
|
window = await windows.create({
|
||||||
url: DONATE_URL,
|
incognito: !!incognito,
|
||||||
type: "normal",
|
type: "normal",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openInTab(url: string) {
|
export async function openInTab(url: string, incognito: boolean) {
|
||||||
const window = await mostRecentBrowser();
|
const window = await mostRecentBrowser(incognito);
|
||||||
await tabs.create({
|
await tabs.create({
|
||||||
active: true,
|
active: true,
|
||||||
url,
|
url,
|
||||||
@ -47,7 +55,7 @@ export async function openInTab(url: string) {
|
|||||||
await windows.update(window.id, {focused: true});
|
await windows.update(window.id, {focused: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openInTabOrFocus(url: string) {
|
export async function openInTabOrFocus(url: string, incognito: boolean) {
|
||||||
const etabs = await tabs.query({
|
const etabs = await tabs.query({
|
||||||
url
|
url
|
||||||
});
|
});
|
||||||
@ -57,21 +65,21 @@ export async function openInTabOrFocus(url: string) {
|
|||||||
await windows.update(tab.windowId, {focused: true});
|
await windows.update(tab.windowId, {focused: true});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await openInTab(url);
|
await openInTab(url, incognito);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function maybeOpenInTab(url: string) {
|
export async function maybeOpenInTab(url: string, incognito: boolean) {
|
||||||
const etabs = await tabs.query({
|
const etabs = await tabs.query({
|
||||||
url
|
url
|
||||||
});
|
});
|
||||||
if (etabs.length) {
|
if (etabs.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await openInTab(url);
|
await openInTab(url, incognito);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function donate() {
|
export async function donate() {
|
||||||
await openInTab(DONATE_URL);
|
await openInTab(DONATE_URL, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openPrefs() {
|
export async function openPrefs() {
|
||||||
@ -86,15 +94,15 @@ export async function openManager(focus = true) {
|
|||||||
console.error(ex.toString(), ex);
|
console.error(ex.toString(), ex);
|
||||||
}
|
}
|
||||||
if (focus) {
|
if (focus) {
|
||||||
await openInTabOrFocus(await runtime.getURL(MANAGER_URL));
|
await openInTabOrFocus(await runtime.getURL(MANAGER_URL), false);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await maybeOpenInTab(await runtime.getURL(MANAGER_URL));
|
await maybeOpenInTab(await runtime.getURL(MANAGER_URL), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openUrls(urls: string) {
|
export async function openUrls(urls: string, incognito: boolean) {
|
||||||
const window = await mostRecentBrowser();
|
const window = await mostRecentBrowser(incognito);
|
||||||
for (const url of urls) {
|
for (const url of urls) {
|
||||||
try {
|
try {
|
||||||
await tabs.create({
|
await tabs.create({
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "DownThemAll!",
|
"name": "DownThemAll!",
|
||||||
"version": "4.0.9",
|
"version": "4.0.12",
|
||||||
|
|
||||||
"description": "__MSG_extensionDescription__",
|
"description": "__MSG_extensionDescription__",
|
||||||
"homepage_url": "https://downthemall.org/",
|
"homepage_url": "https://downthemall.org/",
|
||||||
@ -24,15 +24,18 @@
|
|||||||
"permissions": [
|
"permissions": [
|
||||||
"<all_urls>",
|
"<all_urls>",
|
||||||
"contextMenus",
|
"contextMenus",
|
||||||
"menus",
|
|
||||||
"downloads",
|
"downloads",
|
||||||
"downloads.open",
|
"downloads.open",
|
||||||
"downloads.shelf",
|
"downloads.shelf",
|
||||||
|
"history",
|
||||||
|
"menus",
|
||||||
"notifications",
|
"notifications",
|
||||||
|
"sessions",
|
||||||
"storage",
|
"storage",
|
||||||
"tabs",
|
"tabs",
|
||||||
"webNavigation",
|
"webNavigation",
|
||||||
"webRequest"
|
"webRequest",
|
||||||
|
"webRequestBlocking"
|
||||||
],
|
],
|
||||||
|
|
||||||
"background": {
|
"background": {
|
||||||
|
@ -77,6 +77,8 @@ function urlToUsable(e: any, u: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Gatherer {
|
class Gatherer {
|
||||||
|
private: boolean;
|
||||||
|
|
||||||
textLinks: boolean;
|
textLinks: boolean;
|
||||||
|
|
||||||
selectionOnly: boolean;
|
selectionOnly: boolean;
|
||||||
@ -88,6 +90,7 @@ class Gatherer {
|
|||||||
transferable: string[];
|
transferable: string[];
|
||||||
|
|
||||||
constructor(options: any) {
|
constructor(options: any) {
|
||||||
|
this.private = !!options.private;
|
||||||
this.textLinks = options.textLinks;
|
this.textLinks = options.textLinks;
|
||||||
this.selectionOnly = options.selectionOnly;
|
this.selectionOnly = options.selectionOnly;
|
||||||
this.selection = options.selectionOnly ? getSelection() : null;
|
this.selection = options.selectionOnly ? getSelection() : null;
|
||||||
@ -255,6 +258,7 @@ class Gatherer {
|
|||||||
return {
|
return {
|
||||||
url: url.href,
|
url: url.href,
|
||||||
title,
|
title,
|
||||||
|
private: this.private
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
|
@ -25,6 +25,10 @@ html[data-platform="mac"] {
|
|||||||
--folder-color: rgb(4, 102, 214);
|
--folder-color: rgb(4, 102, 214);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
font-size: 10pt !important;
|
||||||
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'downthemall';
|
font-family: 'downthemall';
|
||||||
src: url('downthemall.woff2?75791791') format('woff2');
|
src: url('downthemall.woff2?75791791') format('woff2');
|
||||||
|
289
tests/test_cdheaderparser.js
Normal file
289
tests/test_cdheaderparser.js
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
/* eslint-disable max-len */
|
||||||
|
/* eslint-env node */
|
||||||
|
"use strict";
|
||||||
|
// License: MPL-2
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const { CDHeaderParser } = require("../lib/cdheaderparser");
|
||||||
|
|
||||||
|
const parser = new CDHeaderParser();
|
||||||
|
|
||||||
|
function check(header, expected) {
|
||||||
|
expect(parser.parse(header)).to.equal(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
function nocheck(header, expected) {
|
||||||
|
expect(parser.parse(header)).not.to.equal(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("CDHeaderParser", function() {
|
||||||
|
it("parse wget", function() {
|
||||||
|
// From wget, test_parse_content_disposition
|
||||||
|
// http://git.savannah.gnu.org/cgit/wget.git/tree/src/http.c?id=8551ceccfedb4390fbfa82c12f0ff714dab1ac76#n5325
|
||||||
|
check("filename=\"file.ext\"", "file.ext");
|
||||||
|
check("attachment; filename=\"file.ext\"", "file.ext");
|
||||||
|
check("attachment; filename=\"file.ext\"; dummy", "file.ext");
|
||||||
|
check("attachment", ""); // wget uses NULL, we use "".
|
||||||
|
check("attachement; filename*=UTF-8'en-US'hello.txt", "hello.txt");
|
||||||
|
check("attachement; filename*0=\"hello\"; filename*1=\"world.txt\"",
|
||||||
|
"helloworld.txt");
|
||||||
|
check("attachment; filename=\"A.ext\"; filename*=\"B.ext\"", "B.ext");
|
||||||
|
check("attachment; filename*=\"A.ext\"; filename*0=\"B\"; filename*1=\"B.ext\"",
|
||||||
|
"A.ext");
|
||||||
|
// This test is faulty - https://savannah.gnu.org/bugs/index.php?52531
|
||||||
|
//check("filename**0=\"A\"; filename**1=\"A.ext\"; filename*0=\"B\";filename*1=\"B\"", "AA.ext");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parse Firefox", function() {
|
||||||
|
// From Firefox
|
||||||
|
// https://searchfox.org/mozilla-central/rev/45a3df4e6b8f653b0103d18d97c34dd666706358/netwerk/test/unit/test_MIME_params.js
|
||||||
|
// Changed as follows:
|
||||||
|
// - Replace error codes with empty string (we never throw).
|
||||||
|
|
||||||
|
const BS = "\\";
|
||||||
|
const DQUOTE = "\"";
|
||||||
|
// No filename parameter: return nothing
|
||||||
|
check("attachment;", "");
|
||||||
|
// basic
|
||||||
|
check("attachment; filename=basic", "basic");
|
||||||
|
// extended
|
||||||
|
check("attachment; filename*=UTF-8''extended", "extended");
|
||||||
|
// prefer extended to basic (bug 588781)
|
||||||
|
check("attachment; filename=basic; filename*=UTF-8''extended", "extended");
|
||||||
|
// prefer extended to basic (bug 588781)
|
||||||
|
check("attachment; filename*=UTF-8''extended; filename=basic", "extended");
|
||||||
|
// use first basic value (invalid; error recovery)
|
||||||
|
check("attachment; filename=first; filename=wrong", "first");
|
||||||
|
// old school bad HTTP servers: missing 'attachment' or 'inline'
|
||||||
|
// (invalid; error recovery)
|
||||||
|
check("filename=old", "old");
|
||||||
|
check("attachment; filename*=UTF-8''extended", "extended");
|
||||||
|
// continuations not part of RFC 5987 (bug 610054)
|
||||||
|
check("attachment; filename*0=foo; filename*1=bar", "foobar");
|
||||||
|
// Return first continuation (invalid; error recovery)
|
||||||
|
check("attachment; filename*0=first; filename*0=wrong; filename=basic", "first");
|
||||||
|
// Only use correctly ordered continuations (invalid; error recovery)
|
||||||
|
check("attachment; filename*0=first; filename*1=second; filename*0=wrong", "firstsecond");
|
||||||
|
// prefer continuation to basic (unless RFC 5987)
|
||||||
|
check("attachment; filename=basic; filename*0=foo; filename*1=bar", "foobar");
|
||||||
|
// Prefer extended to basic and/or (broken or not) continuation
|
||||||
|
// (invalid; error recovery)
|
||||||
|
check("attachment; filename=basic; filename*0=first; filename*0=wrong; filename*=UTF-8''extended", "extended");
|
||||||
|
// RFC 2231 not clear on correct outcome: we prefer non-continued extended
|
||||||
|
// (invalid; error recovery)
|
||||||
|
check("attachment; filename=basic; filename*=UTF-8''extended; filename*0=foo; filename*1=bar", "extended");
|
||||||
|
// Gaps should result in returning only value until gap hit
|
||||||
|
// (invalid; error recovery)
|
||||||
|
check("attachment; filename*0=foo; filename*2=bar", "foo");
|
||||||
|
// Don't allow leading 0's (*01) (invalid; error recovery)
|
||||||
|
check("attachment; filename*0=foo; filename*01=bar", "foo");
|
||||||
|
// continuations should prevail over non-extended (unless RFC 5987)
|
||||||
|
check("attachment; filename=basic; filename*0*=UTF-8''multi;\r\n" +
|
||||||
|
" filename*1=line;\r\n" +
|
||||||
|
" filename*2*=%20extended",
|
||||||
|
"multiline extended");
|
||||||
|
// Gaps should result in returning only value until gap hit
|
||||||
|
// (invalid; error recovery)
|
||||||
|
check("attachment; filename=basic; filename*0*=UTF-8''multi;\r\n" +
|
||||||
|
" filename*1=line;\r\n" +
|
||||||
|
" filename*3*=%20extended",
|
||||||
|
"multiline");
|
||||||
|
// First series, only please, and don't slurp up higher elements (*2 in this
|
||||||
|
// case) from later series into earlier one (invalid; error recovery)
|
||||||
|
check("attachment; filename=basic; filename*0*=UTF-8''multi;\r\n" +
|
||||||
|
" filename*1=line;\r\n" +
|
||||||
|
" filename*0*=UTF-8''wrong;\r\n" +
|
||||||
|
" filename*1=bad;\r\n" +
|
||||||
|
" filename*2=evil",
|
||||||
|
"multiline");
|
||||||
|
// RFC 2231 not clear on correct outcome: we prefer non-continued extended
|
||||||
|
// (invalid; error recovery)
|
||||||
|
check("attachment; filename=basic; filename*0=UTF-8''multi\r\n;" +
|
||||||
|
" filename*=UTF-8''extended;\r\n" +
|
||||||
|
" filename*1=line;\r\n" +
|
||||||
|
" filename*2*=%20extended",
|
||||||
|
"extended");
|
||||||
|
// sneaky: if unescaped, make sure we leave UTF-8'' in value
|
||||||
|
check("attachment; filename*0=UTF-8''unescaped;\r\n" +
|
||||||
|
" filename*1*=%20so%20includes%20UTF-8''%20in%20value",
|
||||||
|
"UTF-8''unescaped so includes UTF-8'' in value");
|
||||||
|
// sneaky: if unescaped, make sure we leave UTF-8'' in value
|
||||||
|
check("attachment; filename=basic; filename*0=UTF-8''unescaped;\r\n" +
|
||||||
|
" filename*1*=%20so%20includes%20UTF-8''%20in%20value",
|
||||||
|
"UTF-8''unescaped so includes UTF-8'' in value");
|
||||||
|
// Prefer basic over invalid continuation
|
||||||
|
// (invalid; error recovery)
|
||||||
|
check("attachment; filename=basic; filename*1=multi;\r\n" +
|
||||||
|
" filename*2=line;\r\n" +
|
||||||
|
" filename*3*=%20extended",
|
||||||
|
"basic");
|
||||||
|
// support digits over 10
|
||||||
|
check("attachment; filename=basic; filename*0*=UTF-8''0;\r\n" +
|
||||||
|
" filename*1=1; filename*2=2;filename*3=3;filename*4=4;filename*5=5;\r\n" +
|
||||||
|
" filename*6=6; filename*7=7;filename*8=8;filename*9=9;filename*10=a;\r\n" +
|
||||||
|
" filename*11=b; filename*12=c;filename*13=d;filename*14=e;filename*15=f\r\n",
|
||||||
|
"0123456789abcdef");
|
||||||
|
// support digits over 10 (detect gaps)
|
||||||
|
check("attachment; filename=basic; filename*0*=UTF-8''0;\r\n" +
|
||||||
|
" filename*1=1; filename*2=2;filename*3=3;filename*4=4;filename*5=5;\r\n" +
|
||||||
|
" filename*6=6; filename*7=7;filename*8=8;filename*9=9;filename*10=a;\r\n" +
|
||||||
|
" filename*11=b; filename*12=c;filename*14=e\r\n",
|
||||||
|
"0123456789abc");
|
||||||
|
// return nothing: invalid
|
||||||
|
// (invalid; error recovery)
|
||||||
|
check("attachment; filename*1=multi;\r\n" +
|
||||||
|
" filename*2=line;\r\n" +
|
||||||
|
" filename*3*=%20extended",
|
||||||
|
"");
|
||||||
|
// Bug 272541: Empty disposition type treated as "attachment"
|
||||||
|
// sanity check
|
||||||
|
check("attachment; filename=foo.html", "foo.html");
|
||||||
|
// the actual bug
|
||||||
|
check("; filename=foo.html", "foo.html");
|
||||||
|
// regression check, but see bug 671204
|
||||||
|
check("filename=foo.html", "foo.html");
|
||||||
|
// Bug 384571: RFC 2231 parameters not decoded when appearing in reversed order
|
||||||
|
// check ordering
|
||||||
|
check("attachment; filename=basic; filename*0*=UTF-8''0;\r\n" +
|
||||||
|
" filename*1=1; filename*2=2;filename*3=3;filename*4=4;filename*5=5;\r\n" +
|
||||||
|
" filename*6=6; filename*7=7;filename*8=8;filename*9=9;filename*10=a;\r\n" +
|
||||||
|
" filename*11=b; filename*12=c;filename*13=d;filename*15=f;filename*14=e;\r\n",
|
||||||
|
"0123456789abcdef");
|
||||||
|
// check non-digits in sequence numbers
|
||||||
|
check("attachment; filename=basic; filename*0*=UTF-8''0;\r\n" +
|
||||||
|
" filename*1a=1\r\n",
|
||||||
|
"0");
|
||||||
|
// check duplicate sequence numbers
|
||||||
|
check("attachment; filename=basic; filename*0*=UTF-8''0;\r\n" +
|
||||||
|
" filename*0=bad; filename*1=1;\r\n",
|
||||||
|
"0");
|
||||||
|
// check overflow
|
||||||
|
check("attachment; filename=basic; filename*0*=UTF-8''0;\r\n" +
|
||||||
|
" filename*11111111111111111111111111111111111111111111111111111111111=1",
|
||||||
|
"0");
|
||||||
|
// check underflow
|
||||||
|
check("attachment; filename=basic; filename*0*=UTF-8''0;\r\n" +
|
||||||
|
" filename*-1=1",
|
||||||
|
"0");
|
||||||
|
// check mixed token/quoted-string
|
||||||
|
check("attachment; filename=basic; filename*0=\"0\";\r\n" +
|
||||||
|
" filename*1=1;\r\n" +
|
||||||
|
" filename*2*=%32",
|
||||||
|
"012");
|
||||||
|
// check empty sequence number
|
||||||
|
check("attachment; filename=basic; filename**=UTF-8''0\r\n", "basic");
|
||||||
|
// Bug 419157: ensure that a MIME parameter with no charset information
|
||||||
|
// fallbacks to Latin-1
|
||||||
|
check("attachment;filename=IT839\x04\xB5(m8)2.pdf;", "IT839\u0004\u00b5(m8)2.pdf");
|
||||||
|
// Bug 588389: unescaping backslashes in quoted string parameters
|
||||||
|
// '\"', should be parsed as '"'
|
||||||
|
check(`attachment; filename=${DQUOTE}${BS + DQUOTE}${DQUOTE}`, DQUOTE);
|
||||||
|
// 'a\"b', should be parsed as 'a"b'
|
||||||
|
check(`attachment; filename=${DQUOTE}a${BS + DQUOTE}b${DQUOTE}`, `a${DQUOTE}b`);
|
||||||
|
// '\x', should be parsed as 'x'
|
||||||
|
check(`attachment; filename=${DQUOTE}${BS}x${DQUOTE}`, "x");
|
||||||
|
// test empty param (quoted-string)
|
||||||
|
check(`attachment; filename=${DQUOTE}${DQUOTE}`, "");
|
||||||
|
// test empty param
|
||||||
|
check("attachment; filename=", "");
|
||||||
|
// Bug 601933: RFC 2047 does not apply to parameters (at least in HTTP)
|
||||||
|
check("attachment; filename==?ISO-8859-1?Q?foo-=E4.html?=", "foo-\u00e4.html");
|
||||||
|
check("attachment; filename=\"=?ISO-8859-1?Q?foo-=E4.html?=\"", "foo-\u00e4.html");
|
||||||
|
// format sent by GMail as of 2012-07-23 (5987 overrides 2047)
|
||||||
|
check("attachment; filename=\"=?ISO-8859-1?Q?foo-=E4.html?=\"; filename*=UTF-8''5987", "5987");
|
||||||
|
// Bug 651185: double quotes around 2231/5987 encoded param
|
||||||
|
// Change reverted to backwards compat issues with various web services,
|
||||||
|
// such as OWA (Bug 703015), plus similar problems in Thunderbird. If this
|
||||||
|
// is tried again in the future, email probably needs to be special-cased.
|
||||||
|
// sanity check
|
||||||
|
check("attachment; filename*=utf-8''%41", "A");
|
||||||
|
// the actual bug
|
||||||
|
check(`attachment; filename*=${DQUOTE}utf-8''%41${DQUOTE}`, "A");
|
||||||
|
// Bug 670333: Content-Disposition parser does not require presence of "="
|
||||||
|
// in params
|
||||||
|
// sanity check
|
||||||
|
check("attachment; filename*=UTF-8''foo-%41.html", "foo-A.html");
|
||||||
|
// the actual bug
|
||||||
|
check("attachment; filename *=UTF-8''foo-%41.html", "");
|
||||||
|
// the actual bug, without 2231/5987 encoding
|
||||||
|
check("attachment; filename X", "");
|
||||||
|
// sanity check with WS on both sides
|
||||||
|
check("attachment; filename = foo-A.html", "foo-A.html");
|
||||||
|
// Bug 685192: in RFC2231/5987 encoding, a missing charset field should be
|
||||||
|
// treated as error
|
||||||
|
// the actual bug
|
||||||
|
check("attachment; filename*=''foo", "foo");
|
||||||
|
// sanity check
|
||||||
|
check("attachment; filename*=a''foo", "foo");
|
||||||
|
// Bug 692574: RFC2231/5987 decoding should not tolerate missing single
|
||||||
|
// quotes
|
||||||
|
// one missing
|
||||||
|
check("attachment; filename*=UTF-8'foo-%41.html", "foo-A.html");
|
||||||
|
// both missing
|
||||||
|
check("attachment; filename*=foo-%41.html", "foo-A.html");
|
||||||
|
// make sure fallback works
|
||||||
|
check("attachment; filename*=UTF-8'foo-%41.html; filename=bar.html", "foo-A.html");
|
||||||
|
// Bug 693806: RFC2231/5987 encoding: charset information should be treated
|
||||||
|
// as authoritative
|
||||||
|
// UTF-8 labeled ISO-8859-1
|
||||||
|
check("attachment; filename*=ISO-8859-1''%c3%a4", "\u00c3\u00a4");
|
||||||
|
// UTF-8 labeled ISO-8859-1, but with octets not allowed in ISO-8859-1
|
||||||
|
// accepts x82, understands it as Win1252, maps it to Unicode \u20a1
|
||||||
|
check("attachment; filename*=ISO-8859-1''%e2%82%ac", "\u00e2\u201a\u00ac");
|
||||||
|
// defective UTF-8
|
||||||
|
nocheck("attachment; filename*=UTF-8''A%e4B", "");
|
||||||
|
// defective UTF-8, with fallback
|
||||||
|
nocheck("attachment; filename*=UTF-8''A%e4B; filename=fallback", "fallback");
|
||||||
|
// defective UTF-8 (continuations), with fallback
|
||||||
|
nocheck("attachment; filename*0*=UTF-8''A%e4B; filename=fallback", "fallback");
|
||||||
|
// check that charsets aren't mixed up
|
||||||
|
check("attachment; filename*0*=ISO-8859-15''euro-sign%3d%a4; filename*=ISO-8859-1''currency-sign%3d%a4", "currency-sign=\u00a4");
|
||||||
|
// same as above, except reversed
|
||||||
|
check("attachment; filename*=ISO-8859-1''currency-sign%3d%a4; filename*0*=ISO-8859-15''euro-sign%3d%a4", "currency-sign=\u00a4");
|
||||||
|
// Bug 704989: add workaround for broken Outlook Web App (OWA)
|
||||||
|
// attachment handling
|
||||||
|
check("attachment; filename*=\"a%20b\"", "a b");
|
||||||
|
// Bug 717121: crash nsMIMEHeaderParamImpl::DoParameterInternal
|
||||||
|
check("attachment; filename=\"", "");
|
||||||
|
// We used to read past string if last param w/o = and ;
|
||||||
|
// Note: was only detected on windows PGO builds
|
||||||
|
check("attachment; filename=foo; trouble", "foo");
|
||||||
|
// Same, followed by space, hits another case
|
||||||
|
check("attachment; filename=foo; trouble ", "foo");
|
||||||
|
check("attachment", "");
|
||||||
|
// Bug 730574: quoted-string in RFC2231-continuations not handled
|
||||||
|
check("attachment; filename=basic; filename*0=\"foo\"; filename*1=\"\\b\\a\\r.html\"", "foobar.html");
|
||||||
|
// unmatched escape char
|
||||||
|
check("attachment; filename=basic; filename*0=\"foo\"; filename*1=\"\\b\\a\\", "fooba\\");
|
||||||
|
// Bug 732369: Content-Disposition parser does not require presence of ";" between params
|
||||||
|
// optimally, this would not even return the disposition type "attachment"
|
||||||
|
check("attachment; extension=bla filename=foo", "");
|
||||||
|
check("attachment; filename=foo extension=bla", "foo");
|
||||||
|
check("attachment filename=foo", "");
|
||||||
|
// Bug 777687: handling of broken %escapes
|
||||||
|
nocheck("attachment; filename*=UTF-8''f%oo; filename=bar", "bar");
|
||||||
|
nocheck("attachment; filename*=UTF-8''foo%; filename=bar", "bar");
|
||||||
|
// Bug 783502 - xpcshell test netwerk/test/unit/test_MIME_params.js fails on AddressSanitizer
|
||||||
|
check("attachment; filename=\"\\b\\a\\", "ba\\");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parse extra", function() {
|
||||||
|
// Extra tests, not covered by above tests.
|
||||||
|
check("inline; FILENAME=file.txt", "file.txt");
|
||||||
|
check("INLINE; FILENAME= \"an example.html\"", "an example.html"); // RFC 6266, section 5.
|
||||||
|
check("inline; filename= \"tl;dr.txt\"", "tl;dr.txt");
|
||||||
|
check("INLINE; FILENAME*= \"an example.html\"", "an example.html");
|
||||||
|
check("inline; filename*= \"tl;dr.txt\"", "tl;dr.txt");
|
||||||
|
check("inline; filename*0=\"tl;dr and \"; filename*1=more.txt", "tl;dr and more.txt");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parse issue 26", function() {
|
||||||
|
// https://github.com/Rob--W/open-in-browser/issues/26
|
||||||
|
check("attachment; filename=\xe5\x9c\x8b.pdf", "\u570b.pdf");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parse issue 35", function() {
|
||||||
|
// https://github.com/Rob--W/open-in-browser/issues/35
|
||||||
|
check("attachment; filename=okre\x9clenia.rtf", "okreœlenia.rtf");
|
||||||
|
});
|
||||||
|
});
|
30
tests/test_mime.js
Normal file
30
tests/test_mime.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
"use strict";
|
||||||
|
// License: CC0 1.0
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const {MimeDB} = require("../lib/mime");
|
||||||
|
|
||||||
|
describe("MIME", function() {
|
||||||
|
it("general", function() {
|
||||||
|
expect(MimeDB.getMime("image/jpeg").major).to.equal("image");
|
||||||
|
expect(MimeDB.getMime("image/jpeg").minor).to.equal("jpeg");
|
||||||
|
expect(MimeDB.getMime("iMage/jPeg").major).to.equal("image");
|
||||||
|
expect(MimeDB.getMime("imAge/jpEg").minor).to.equal("jpeg");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exts", function() {
|
||||||
|
expect(MimeDB.getMime("image/jpeg").primary).to.equal("jpg");
|
||||||
|
expect(MimeDB.getMime("image/jpeg").primary).to.equal(
|
||||||
|
MimeDB.getPrimary("image/jpeg"));
|
||||||
|
expect(MimeDB.getMime("iMage/jPeg").primary).to.equal("jpg");
|
||||||
|
expect(MimeDB.getMime("imAge/jpEg").primary).to.equal(
|
||||||
|
MimeDB.getPrimary("image/jpeg"));
|
||||||
|
expect(Array.from(MimeDB.getMime("imAge/jpEg").extensions)).to.deep.equal(
|
||||||
|
["jpg", "jpeg", "jpe", "jfif"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("application/octet-stream should not yield results", function() {
|
||||||
|
expect(MimeDB.getPrimary("application/octet-stream")).to.equal("");
|
||||||
|
expect(MimeDB.getMime("application/octet-Stream")).to.be.undefined;
|
||||||
|
});
|
||||||
|
});
|
@ -70,7 +70,7 @@ def main():
|
|||||||
if modified:
|
if modified:
|
||||||
try:
|
try:
|
||||||
with open("messages.json.tmp", "w", encoding="utf-8") as outp:
|
with open("messages.json.tmp", "w", encoding="utf-8") as outp:
|
||||||
json.dump(data, outp, sort_keys=True, indent=2)
|
json.dump(data, outp, sort_keys=True, indent=2, ensure_ascii=False)
|
||||||
os.rename("messages.json.tmp", "_locales/en/messages.json")
|
os.rename("messages.json.tmp", "_locales/en/messages.json")
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
|
@ -26,8 +26,8 @@ UNCOMPRESSABLE = set((".png", ".jpg", ".zip", ".woff2"))
|
|||||||
LICENSED = set((".css", ".html", ".js", "*.ts"))
|
LICENSED = set((".css", ".html", ".js", "*.ts"))
|
||||||
IGNORED = set((".DS_Store", "Thumbs.db"))
|
IGNORED = set((".DS_Store", "Thumbs.db"))
|
||||||
|
|
||||||
PERM_IGNORED_FX = set(("downloads.shelf", "webRequest"))
|
PERM_IGNORED_FX = set(("downloads.shelf", "webRequest", "webRequestBlocking"))
|
||||||
PERM_IGNORED_CHROME = set(("menus",))
|
PERM_IGNORED_CHROME = set(("menus", "sessions"))
|
||||||
|
|
||||||
SCRIPTS = [
|
SCRIPTS = [
|
||||||
"yarn build:regexps",
|
"yarn build:regexps",
|
||||||
|
@ -524,8 +524,16 @@ export class DownloadTable extends VirtualTable {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Keys.on("SHIFT-Delete", (event: Event) => {
|
||||||
|
const target = event.target as HTMLElement;
|
||||||
|
if (target.localName === "input") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.removeCompleteDownloads(false);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
ctx.on("ctx-remove-all", () => this.removeAllDownloads());
|
ctx.on("ctx-remove-all", () => this.removeAllDownloads());
|
||||||
ctx.on("ctx-remove-complete", () => this.removeCompleteDownloads(false));
|
|
||||||
ctx.on("ctx-remove-complete-all",
|
ctx.on("ctx-remove-complete-all",
|
||||||
() => this.removeCompleteDownloads(false));
|
() => this.removeCompleteDownloads(false));
|
||||||
ctx.on("ctx-remove-complete-selected",
|
ctx.on("ctx-remove-complete-selected",
|
||||||
@ -743,6 +751,7 @@ export class DownloadTable extends VirtualTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectionChanged() {
|
selectionChanged() {
|
||||||
|
this.dismissTooltip();
|
||||||
const {empty} = this.selection;
|
const {empty} = this.selection;
|
||||||
if (empty) {
|
if (empty) {
|
||||||
for (const d of this.disableSet) {
|
for (const d of this.disableSet) {
|
||||||
|
@ -428,18 +428,43 @@ class SelectionTable extends VirtualTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openSelection() {
|
openSelection() {
|
||||||
const items = this.items.filter((i, idx) => this.selection.contains(idx));
|
const privates: BaseMatchedItem[] = [];
|
||||||
if (!items.length) {
|
const items = this.items.filter((i, idx) => this.selection.contains(idx)).
|
||||||
|
filter(i => {
|
||||||
|
if (i.private) {
|
||||||
|
privates.push(i);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (!items.length && !privates.length) {
|
||||||
if (this.focusRow < 0) {
|
if (this.focusRow < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
items.push(this.items.at(this.focusRow));
|
const item = this.items.at(this.focusRow);
|
||||||
|
if (item.private) {
|
||||||
|
privates.push(item);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
items.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.length) {
|
||||||
PORT.postMessage({
|
PORT.postMessage({
|
||||||
msg: "openUrls",
|
msg: "openUrls",
|
||||||
urls: items.map(e => e.url)
|
urls: items.map(e => e.url),
|
||||||
|
incognito: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (privates.length) {
|
||||||
|
PORT.postMessage({
|
||||||
|
msg: "openUrls",
|
||||||
|
urls: privates.map(e => e.url),
|
||||||
|
incognito: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
applyDeltaTo(delta: ItemDelta[], items: ItemCollection) {
|
applyDeltaTo(delta: ItemDelta[], items: ItemCollection) {
|
||||||
const active = items === this.items;
|
const active = items === this.items;
|
||||||
|
Reference in New Issue
Block a user