Compare commits

...

299 Commits

Author SHA1 Message Date
manxmensch
9b88d3f5e4 Pontoon: Update Malay (ms) localization of Test Pilot: Firefox Send
Localization authors:
- manxmensch <manxmensch@gmail.com>
2017-07-26 04:50:30 +00:00
manxmensch
cd4df5f6d8 Pontoon: Update Malay (ms) localization of Test Pilot: Firefox Send
Localization authors:
- manxmensch <manxmensch@gmail.com>
2017-07-26 04:31:34 +00:00
Pin-guang Chen
7c461eab52 Pontoon: Update Chinese (zh-TW) localization of Test Pilot: Firefox Send
Localization authors:
- Pin-guang Chen <petercpg@mail.moztw.org>
2017-07-26 04:10:49 +00:00
Kohei Yoshino
1b7e94c2d8 Pontoon: Update Japanese (ja) localization of Test Pilot: Firefox Send
Localization authors:
- Kohei Yoshino <kohei.yoshino@gmail.com>
2017-07-26 03:51:07 +00:00
Pin-guang Chen
dcd3c0b3ff Pontoon: Update Chinese (zh-TW) localization of Test Pilot: Firefox Send
Localization authors:
- Pin-guang Chen <petercpg@mail.moztw.org>
2017-07-26 03:51:03 +00:00
Pin-guang Chen
bfe16a5300 Pontoon: Update Chinese (zh-TW) localization of Test Pilot: Firefox Send
Localization authors:
- Pin-guang Chen <petercpg@mail.moztw.org>
2017-07-26 03:31:24 +00:00
Pin-guang Chen
8a1f905831 Pontoon: Update Chinese (zh-TW) localization of Test Pilot: Firefox Send
Localization authors:
- Pin-guang Chen <petercpg@mail.moztw.org>
2017-07-26 03:10:48 +00:00
Håvar Henriksen
e5b4165eff Pontoon: Update Norwegian Bokmål (nb-NO) localization of Test Pilot: Firefox Send
Localization authors:
- Håvar Henriksen <havar@firefox.no>
2017-07-26 00:12:35 +00:00
Marco Aurélio
be596e91ef Pontoon: Update Portuguese (pt-BR) localization of Test Pilot: Firefox Send
Localization authors:
- Marco Aurélio <fxhelp@yahoo.com>
- Cynthia Pereira <cynthiacpereira@gmail.com>
2017-07-25 23:10:46 +00:00
Danny Coates
f20c995d1a Merge pull request #314 from mozilla/i312
added L10N_DEV environment variable for making all languages available
2017-07-25 15:59:31 -07:00
Cynthia Pereira
5f97c130bd Pontoon: Update Portuguese (pt-BR) localization of Test Pilot: Firefox Send
Localization authors:
- Cynthia Pereira <cynthiacpereira@gmail.com>
- Marco Aurélio <fxhelp@yahoo.com>
2017-07-25 22:51:06 +00:00
Håvar Henriksen
13bf765be0 Pontoon: Update Norwegian Bokmål (nb-NO) localization of Test Pilot: Firefox Send
Localization authors:
- Håvar Henriksen <havar@firefox.no>
2017-07-25 22:31:40 +00:00
Matjaž Horvat
443de49db8 Pontoon: Update Swedish (sv-SE) localization of Test Pilot: Firefox Send
Localization authors:
- Matjaž Horvat <matjaz.horvat@gmail.com>
2017-07-25 22:11:56 +00:00
Matjaž Horvat
3d82ce0909 Pontoon: Update Sorbian, Upper (hsb) localization of Test Pilot: Firefox Send
Localization authors:
- Matjaž Horvat <matjaz.horvat@gmail.com>
2017-07-25 22:11:53 +00:00
Matjaž Horvat
b259cba882 Pontoon: Update Slovenian (sl) localization of Test Pilot: Firefox Send
Localization authors:
- Matjaž Horvat <matjaz.horvat@gmail.com>
2017-07-25 22:11:48 +00:00
Matjaž Horvat
a138586dd7 Pontoon: Update Slovak (sk) localization of Test Pilot: Firefox Send
Localization authors:
- Juraj Cigáň <kusavica@gmail.com>
- Matjaž Horvat <matjaz.horvat@gmail.com>
2017-07-25 22:11:44 +00:00
Matjaž Horvat
e427203a45 Pontoon: Update Portuguese (pt-PT) localization of Test Pilot: Firefox Send
Localization authors:
- Matjaž Horvat <matjaz.horvat@gmail.com>
2017-07-25 22:11:41 +00:00
Håvar Henriksen
8882024e8f Pontoon: Update Norwegian Bokmål (nb-NO) localization of Test Pilot: Firefox Send
Localization authors:
- Håvar Henriksen <havar@firefox.no>
- Matjaž Horvat <matjaz.horvat@gmail.com>
2017-07-25 22:11:39 +00:00
Matjaž Horvat
581ae224e7 Pontoon: Update Japanese (ja) localization of Test Pilot: Firefox Send
Localization authors:
- Matjaž Horvat <matjaz.horvat@gmail.com>
2017-07-25 22:11:36 +00:00
Matjaž Horvat
dccf3329ca Pontoon: Update German (de) localization of Test Pilot: Firefox Send
Localization authors:
- Matjaž Horvat <matjaz.horvat@gmail.com>
2017-07-25 22:11:33 +00:00
Matjaž Horvat
1450322585 Pontoon: Update Chinese (zh-CN) localization of Test Pilot: Firefox Send
Localization authors:
- Matjaž Horvat <matjaz.horvat@gmail.com>
2017-07-25 22:11:30 +00:00
Juraj Cigáň
5d1639851b Pontoon: Update Slovak (sk) localization of Test Pilot: Firefox Send
Localization authors:
- Juraj Cigáň <kusavica@gmail.com>
2017-07-25 21:31:39 +00:00
Kohei Yoshino
77939cc280 Pontoon: Update Japanese (ja) localization of Test Pilot: Firefox Send
Localization authors:
- Kohei Yoshino <kohei.yoshino@gmail.com>
2017-07-25 21:31:34 +00:00
Danny Coates
7fd8fb4914 added L10N_DEV environment variable for making all languages available 2017-07-25 14:28:49 -07:00
Juraj Cigáň
91ede1bdec Pontoon: Update Slovak (sk) localization of Test Pilot: Firefox Send
Localization authors:
- Juraj Cigáň <kusavica@gmail.com>
2017-07-25 21:10:29 +00:00
Kohei Yoshino
29698a55c4 Pontoon: Update Japanese (ja) localization of Test Pilot: Firefox Send
Localization authors:
- Kohei Yoshino <kohei.yoshino@gmail.com>
2017-07-25 21:10:27 +00:00
Juraj Cigáň
abfc07b1e3 Pontoon: Update Slovak (sk) localization of Test Pilot: Firefox Send
Localization authors:
- Juraj Cigáň <kusavica@gmail.com>
2017-07-25 20:51:41 +00:00
Rodrigo
a0401b24e8 Pontoon: Update Portuguese (pt-PT) localization of Test Pilot: Firefox Send
Localization authors:
- Rodrigo <rodrigo.mcunha@hotmail.com>
2017-07-25 20:51:32 +00:00
Håvar Henriksen
dbf39efd35 Pontoon: Update Norwegian Bokmål (nb-NO) localization of Test Pilot: Firefox Send
Localization authors:
- Håvar Henriksen <havar@firefox.no>
2017-07-25 20:51:25 +00:00
Kohei Yoshino
fe7d71d165 Pontoon: Update Japanese (ja) localization of Test Pilot: Firefox Send
Localization authors:
- Kohei Yoshino <kohei.yoshino@gmail.com>
2017-07-25 20:51:22 +00:00
Rodrigo
977fe65dce Pontoon: Update Portuguese (pt-PT) localization of Test Pilot: Firefox Send
Localization authors:
- Rodrigo <rodrigo.mcunha@hotmail.com>
2017-07-25 20:31:35 +00:00
Håvar Henriksen
4e79925c7b Pontoon: Update Norwegian Bokmål (nb-NO) localization of Test Pilot: Firefox Send
Localization authors:
- Håvar Henriksen <havar@firefox.no>
2017-07-25 20:31:32 +00:00
Danny Coates
416e8d81f9 Merge pull request #313 from mozilla/driverTimeout
removing timeout limit for front end tests
2017-07-25 13:21:59 -07:00
Abhinav Adduri
215f0f74ad removing timeout limit for front end tests 2017-07-25 13:14:08 -07:00
Rodrigo
93930b91a2 Pontoon: Update Portuguese (pt-PT) localization of Test Pilot: Firefox Send
Localization authors:
- Rodrigo <rodrigo.mcunha@hotmail.com>
2017-07-25 20:11:11 +00:00
Danny Coates
65c24990cd Merge pull request #311 from mozilla/expiredDownloadPage
expired ids should reject instead of returning null
2017-07-25 13:10:46 -07:00
Danny Coates
424eb2c37a refactor all redis calls to reject on null 2017-07-25 13:00:02 -07:00
YFdyh000
d10cbcba3e Pontoon: Update Chinese (zh-CN) localization of Test Pilot: Firefox Send
Localization authors:
- YFdyh000 <yfdyh000@gmail.com>
2017-07-25 19:51:29 +00:00
Andreas Pettersson
553f0958ba Pontoon: Update Swedish (sv-SE) localization of Test Pilot: Firefox Send
Localization authors:
- Andreas Pettersson <az@kth.se>
2017-07-25 19:32:35 +00:00
Michael Wolf
ae2bae88cf Pontoon: Update Sorbian, Upper (hsb) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Wolf <milupo@sorbzilla.de>
2017-07-25 19:32:29 +00:00
Rok Žerdin
313b145297 Pontoon: Update Slovenian (sl) localization of Test Pilot: Firefox Send
Localization authors:
- Rok Žerdin <rok.zerdin1990@gmail.com>
2017-07-25 19:32:22 +00:00
YFdyh000
faf5fd17d3 Pontoon: Update Chinese (zh-CN) localization of Test Pilot: Firefox Send
Localization authors:
- YFdyh000 <yfdyh000@gmail.com>
2017-07-25 19:32:16 +00:00
Danny Coates
dd0ab710de format 2017-07-25 12:21:07 -07:00
Andreas Pettersson
c2bcac76e9 Pontoon: Update Swedish (sv-SE) localization of Test Pilot: Firefox Send
Localization authors:
- Andreas Pettersson <az@kth.se>
2017-07-25 19:11:04 +00:00
Rok Žerdin
bf7024c6d9 Pontoon: Update Slovenian (sl) localization of Test Pilot: Firefox Send
Localization authors:
- Rok Žerdin <rok.zerdin1990@gmail.com>
2017-07-25 19:11:00 +00:00
Francesco Lodolo
992cdcc70e Pontoon: Update Italian (it) localization of Test Pilot: Firefox Send
Localization authors:
- Francesco Lodolo <francesco.lodolo@mozillaitalia.org>
2017-07-25 19:10:58 +00:00
YFdyh000
10e446bb41 Pontoon: Update Chinese (zh-CN) localization of Test Pilot: Firefox Send
Localization authors:
- YFdyh000 <yfdyh000@gmail.com>
2017-07-25 19:10:56 +00:00
Danny Coates
15ac0e1d49 expired ids should reject instead of returning null 2017-07-25 12:00:52 -07:00
Michael Wolf
4946e9c382 Pontoon: Update Sorbian, Upper (hsb) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Wolf <milupo@sorbzilla.de>
2017-07-25 18:55:15 +00:00
Rok Žerdin
ece302342e Pontoon: Update Slovenian (sl) localization of Test Pilot: Firefox Send
Localization authors:
- Rok Žerdin <rok.zerdin1990@gmail.com>
2017-07-25 18:55:11 +00:00
Francesco Lodolo
721aa48d53 Pontoon: Update Italian (it) localization of Test Pilot: Firefox Send
Localization authors:
- Francesco Lodolo <francesco.lodolo@mozillaitalia.org>
2017-07-25 18:55:07 +00:00
Danny Coates
0761fcf902 Merge pull request #302 from youwenliang/ux-tweak
UX Refine WIP
2017-07-25 11:42:25 -07:00
Michael Wolf
33602f1432 Pontoon: Update Sorbian, Upper (hsb) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Wolf <milupo@sorbzilla.de>
2017-07-25 18:39:42 +00:00
Francesco Lodolo
74b305442c Pontoon: Update Italian (it) localization of Test Pilot: Firefox Send
Localization authors:
- Francesco Lodolo <francesco.lodolo@mozillaitalia.org>
2017-07-25 18:39:40 +00:00
Michael Köhler
f30393cf33 Pontoon: Update German (de) localization of Test Pilot: Firefox Send
Localization authors:
- Michael Köhler <michael.koehler1@gmx.de>
2017-07-25 18:39:38 +00:00
Danny Coates
e7688a62c6 Merge branch 'master' into ux-tweak 2017-07-25 11:31:13 -07:00
Danny Coates
109617d817 Merge pull request #310 from mozilla/addresses159
if the download card is pressed, the expired card shows up properly
2017-07-25 11:29:27 -07:00
Abhinav Adduri
8f66db2295 changed from expired to errored 2017-07-25 11:28:33 -07:00
Abhinav Adduri
57e0d17cbc if the download card is pressed, the expired card shows up properly 2017-07-25 10:58:18 -07:00
Francesco Lodolo
93138773ca Pontoon: Update Italian (it) localization of Test Pilot: Firefox Send
Localization authors:
- Francesco Lodolo <francesco.lodolo@mozillaitalia.org>
2017-07-25 17:54:36 +00:00
Abhinav Adduri
1bfa6321b1 Merge pull request #269 from mozilla/ftlFixes
refactored ftl file
2017-07-25 09:59:29 -07:00
Abhinav Adduri
10d489f766 fixed issues, fluentfmt output matches 2017-07-25 09:55:40 -07:00
Abhinav Adduri
4065bbcfd7 removing bundle files 2017-07-25 09:25:49 -07:00
Abhinav Adduri
330da9b258 merging master 2017-07-25 09:23:42 -07:00
Danny Coates
3febcfe1ea Merge pull request #291 from mozilla/legal
added legal page
2017-07-25 09:03:11 -07:00
You-Wen Liang (Mark)
0cdae11456 Update main.css
fix media queries
2017-07-25 23:55:16 +08:00
Daniela Arcese
5996bceef7 Merge pull request #307 from mozilla/ui
don't show error page on upload cancel
2017-07-25 11:48:34 -04:00
Daniela Arcese
ba277b9382 don't show error page on upload cancel 2017-07-25 11:08:41 -04:00
Mark Liang
5046b5022a Unify error image & expire image margin 2017-07-25 18:20:25 +08:00
Mark Liang
74334433cd Modify link to send homepage 2017-07-25 15:52:48 +08:00
Mark Liang
33bf82e963 add hover state for download button 2017-07-25 15:49:26 +08:00
Mark Liang
c72896aeb7 Fix footer responsive 2017-07-25 15:45:58 +08:00
Mark Liang
b306ffec8d Modify Feedback icon + Footer responsive 2017-07-25 15:34:01 +08:00
Mark Liang
e1c21dd9b0 Fix CSS lint failed 2017-07-25 14:52:21 +08:00
Mark Liang
9f8cedc0db Remove unused files 2017-07-25 14:14:17 +08:00
Mark Liang
12c02ef6af Update UI & Responsive 2017-07-25 14:12:26 +08:00
Danny Coates
5a564e2c37 tighten csp 2017-07-24 22:08:43 -07:00
Danny Coates
4b8445191b Merge pull request #299 from mozilla/circleVersion
use CIRCLE_TAG as version.json version if present
2017-07-24 18:35:34 -07:00
Danny Coates
12033edda5 use CIRCLE_TAG as version.json version if present 2017-07-24 18:25:28 -07:00
Danny Coates
8e6d8eaddd bump version 2017-07-24 16:34:00 -07:00
Daniela Arcese
faaa8afe17 Merge pull request #296 from mozilla/ui
restyle delete popup
2017-07-24 17:42:06 -04:00
Daniela Arcese
84e8abf2c5 fix horizontal scroll issue 2017-07-24 17:38:03 -04:00
Danny Coates
5db8a20b9d Merge pull request #295 from mozilla/envvar
renamed environment variables to remove P2P_ prefix
2017-07-24 14:30:09 -07:00
Danny Coates
117c6ea12d Merge branch 'master' into envvar 2017-07-24 14:06:55 -07:00
Danny Coates
83880d97e6 Merge pull request #294 from mozilla/invalidDragAndDrops
dealing with invalid drag and drops
2017-07-24 13:56:00 -07:00
Abhinav Adduri
c3be5228cb removing dialog 2017-07-24 13:35:40 -07:00
Danny Coates
ade214c69c Merge pull request #297 from mozilla/expire
added environment variable for expire time
2017-07-24 13:27:37 -07:00
Danny Coates
4cb040d70d added environment variable for expire time 2017-07-24 13:20:18 -07:00
Danny Coates
73ccce627c renamed environment variables to remove P2P_ prefix 2017-07-24 13:10:30 -07:00
Daniela Arcese
4043d35c8b restyle delete popup 2017-07-24 16:04:31 -04:00
Abhinav Adduri
a576cc0198 dealing with invalid drag and drops 2017-07-24 12:42:16 -07:00
Abhinav Adduri
fa1f0208a4 added tagLength to fileReceiver 2017-07-24 12:16:01 -07:00
Danny Coates
193d3b1aef Merge pull request #292 from mozilla/fixes289
Fixes289
2017-07-24 12:10:12 -07:00
Abhinav Adduri
3b28ce88bf removing bundle from gitignore 2017-07-24 12:03:38 -07:00
Abhinav Adduri
c77983d902 removing bundle file, adding public/bundle.js to gitignore 2017-07-24 10:54:57 -07:00
Abhinav Adduri
6ef9f8fa43 fixes 289 2017-07-24 10:52:54 -07:00
Danny Coates
1eabc1a11e added legal page 2017-07-24 10:51:57 -07:00
Danny Coates
9136694d29 Merge pull request #288 from mozilla/uploads
fix: Don`t allow upload when not on the upload page.
2017-07-24 10:30:52 -07:00
Erica Wright
ec7f058afc don`t allow upload when not on the upload page. 2017-07-24 13:04:46 -04:00
Danny Coates
62989ee2c9 Merge pull request #285 from mozilla/phases
added messages for processing phases
2017-07-24 09:53:45 -07:00
Danny Coates
85068e97ae Merge pull request #267 from mozilla/responsive-and-feedback
make site responsive and add feedback link
2017-07-24 09:52:39 -07:00
Abhinav Adduri
eb73dbfe78 removed empty lines 2017-07-24 09:41:37 -07:00
John Gruen
dde2c4d5a9 Merge branch 'master' of https://github.com/mozilla/send into responsive-and-feedback 2017-07-24 18:31:47 +02:00
Danny Coates
20cf8d0a15 added messages for processing phases 2017-07-24 09:23:56 -07:00
Peter deHaan
b2bd623a49 Merge pull request #286 from pdehaan/issue-205
Update download progress bar color
2017-07-24 08:49:45 -07:00
Peter deHaan
fc3001978c Update download progress bar color 2017-07-23 22:42:40 -07:00
Danny Coates
d6823f492d Merge pull request #281 from pdehaan/deLint
Stop ESLint from linting the /public/ directory
2017-07-23 12:42:53 -07:00
Peter deHaan
c3751c2efc Ignore console statements in test/ directory because yolo 2017-07-23 00:06:24 -07:00
Peter deHaan
70396e2f36 Stop ESLint from linting the /public/ directory 2017-07-22 23:26:42 -07:00
Danny Coates
b557665d04 ignore the firefox installed for selenium in docker 2017-07-21 20:09:59 -07:00
Danny Coates
6393d70a33 rearrange dependencies. fixes #255 2017-07-21 20:00:38 -07:00
Danny Coates
834df8526f Merge pull request #280 from mozilla/unsupported
created /unsupported page and added gcmCompliant to /download page
2017-07-21 17:50:26 -07:00
Danny Coates
f5bd332ff8 created /unsupported page and added gcmCompliant to /download page 2017-07-21 17:42:40 -07:00
Danny Coates
0d5fb1740d npm run format 2017-07-21 17:01:26 -07:00
Danny Coates
5ed4db9689 Merge pull request #279 from mozilla/split
create separate js bundles for upload/download pages
2017-07-21 16:55:50 -07:00
Danny Coates
31b810eb7d create separate js bundles for upload/download pages 2017-07-21 16:23:08 -07:00
Abhinav Adduri
168a711c05 Firefox Send and Test Pilot should not be localized 2017-07-21 14:04:59 -07:00
Abhinav Adduri
7243a10340 Merge pull request #268 from mozilla/testpilotGA
Testpilot ga
2017-07-21 13:49:45 -07:00
Abhinav Adduri
b18bcd3b6e removing initialization on top in favor of default 2017-07-21 13:41:59 -07:00
Abhinav Adduri
09a6192bf5 merging master 2017-07-21 13:36:26 -07:00
Abhinav Adduri
9585850d6d final fixes 2017-07-21 13:25:08 -07:00
Abhinav Adduri
14f3d837f9 if checkexistence removes the last item in the list, call toggleHeader 2017-07-21 12:52:28 -07:00
Abhinav Adduri
d660eda64c refactored localStorage into storage module for frontend 2017-07-21 12:44:55 -07:00
Danny Coates
2bbb0f14f3 bump minor version for new ui 2017-07-21 12:27:01 -07:00
Abhinav Adduri
b123b736e9 added comment to expiry column header 2017-07-21 11:13:45 -07:00
Abhinav Adduri
28e496fe05 more fixes 2017-07-21 11:10:57 -07:00
Abhinav Adduri
8ebfaf9ad9 adding requested changes 2017-07-21 10:56:25 -07:00
Abhinav Adduri
47fd387799 added toml file 2017-07-21 10:39:41 -07:00
Danny Coates
fbf906fe5b Merge pull request #266 from mozilla/sizelimit
abort uploads over maxfilesize
2017-07-21 09:48:06 -07:00
John Gruen
463393552b make site responsive and add feedback link 2017-07-21 11:59:42 +02:00
Danny Coates
18a811aa31 less wiggle 2017-07-20 17:34:53 -07:00
Abhinav Adduri
cb0d69c5cd fixed one bug, added some helper functions 2017-07-20 16:06:06 -07:00
Abhinav Adduri
2c77d94af7 refactored ftl file 2017-07-20 15:47:59 -07:00
Abhinav Adduri
b9eb653f1f linting 2017-07-20 15:20:52 -07:00
Abhinav Adduri
37bb6fd982 updated one metric, cd4 added to upload-deleted 2017-07-20 15:17:11 -07:00
Abhinav Adduri
e5d1e8f028 Merge branch 'master' of github.com:mozilla/send into testpilotGA 2017-07-20 15:16:22 -07:00
Abhinav Adduri
99477774cf finished metrics 2017-07-20 15:16:00 -07:00
Abhinav Adduri
8d419072d7 removing references to cd7 2017-07-20 14:13:40 -07:00
Abhinav Adduri
a2fcce9e40 Merge pull request #264 from mozilla/duplicate-cm
Remove duplicate custom metric.
2017-07-20 14:00:58 -07:00
Danny Coates
55d3d1a792 abort uploads over maxfilesize 2017-07-20 13:22:14 -07:00
Chuck Harmston
88dbda87dc Remove duplicate custom metric.
This fixes a mistake in the metrics documentation: a duplicate metric. This merges `cd6` and `cd4` as `cd4`, then moves `cd7` to `cd6`.
2017-07-20 13:22:15 -06:00
Abhinav Adduri
76a6f02eb7 Merge branch 'master' of github.com:mozilla/send into testpilotGA 2017-07-20 08:47:56 -07:00
Danny Coates
34f26fc017 added git package to docker image 2017-07-20 08:36:05 -07:00
Chuck Harmston
4f36f2befa Adds 'Available on Test Pilot' badge.
Also changes the style of the CircleCI badge to match.
2017-07-20 09:13:02 -06:00
Daniela Arcese
f816c0bc12 Merge pull request #259 from mozilla/ui
add alert when uploading multiple files
2017-07-20 10:43:43 -04:00
Daniela Arcese
60bfd1b67c make alert show when uploading a folder 2017-07-20 10:31:39 -04:00
Erica
fc7c7e2c71 Merge pull request #262 from mozilla/progress-bar
sync download progress bar with percentage
2017-07-20 10:18:30 -04:00
Daniela Arcese
a9e0ab17e5 sync download progress bar with percentage 2017-07-20 09:25:04 -04:00
Daniela Arcese
80cf343516 remove check for uploads on browse 2017-07-20 09:21:26 -04:00
Abhinav Adduri
744dbb3a6f added analytics for copied, unsupported, and restarted metrics 2017-07-19 16:16:46 -07:00
Daniela Arcese
e4301963a2 add alert when uploading multiple files 2017-07-19 17:27:08 -04:00
Daniela Arcese
48da85b12b Merge pull request #258 from mozilla/ui
better sync percent with progress bar
2017-07-19 17:18:49 -04:00
Daniela Arcese
e02bc54172 better sync percent with progress bar 2017-07-19 17:11:46 -04:00
Danny Coates
3bb6a6fa44 Merge pull request #257 from mozilla/jsconfig
add a dynamic js script for page config
2017-07-19 14:10:03 -07:00
Daniela Arcese
64e7182f30 Merge pull request #256 from mozilla/ui
add file size limit message
2017-07-19 17:09:40 -04:00
Daniela Arcese
8c1ba8b45a tadd file size limit message 2017-07-19 17:02:27 -04:00
Danny Coates
85a7be01cb add a dynamic js script for page config 2017-07-19 14:01:34 -07:00
Abhinav Adduri
262322b9e8 Merge branch 'master' of github.com:mozilla/send 2017-07-19 12:30:17 -07:00
Abhinav Adduri
88904621b3 removing comment in server 2017-07-19 12:29:50 -07:00
Danny Coates
da8f7c7172 Merge pull request #253 from pdehaan/yo-favicon
Add favicon.ico version of the Send logo
2017-07-19 12:27:16 -07:00
Danny Coates
caf594877f Merge pull request #254 from pdehaan/yo-nsp
Add nsp check to circle ci
2017-07-19 12:26:57 -07:00
Abhinav Adduri
ffc9386221 Merge pull request #245 from mozilla/localization
Localization
2017-07-19 12:21:43 -07:00
Abhinav Adduri
6670d9ad69 fixing conflicts 2017-07-19 12:17:24 -07:00
Peter deHaan
6d8db25d61 Add nsp check to circle ci 2017-07-19 12:03:59 -07:00
Peter deHaan
a38a27dc60 Add favicon.ico version of the Send logo 2017-07-19 11:46:21 -07:00
Abhinav Adduri
69055a504b Merge branch 'master' of github.com:mozilla/send into localization 2017-07-19 11:37:52 -07:00
Abhinav Adduri
902010704a Merge branch 'master' of github.com:mozilla/send into localization 2017-07-19 11:35:11 -07:00
Erica
1992626da8 Merge pull request #252 from mozilla/ui
only allow drag and drop on upload page
2017-07-19 14:31:15 -04:00
Daniela Arcese
3c8cdbc7df only allow drag and drop on upload page 2017-07-19 14:22:21 -04:00
Erica
72b07b82c9 Merge pull request #250 from mozilla/ui
make footer not overlap
2017-07-19 13:50:38 -04:00
Daniela Arcese
846f5c7254 Merge pull request #251 from mozilla/min-images
minify all images
2017-07-19 13:43:59 -04:00
Erica Wright
b0e18b29ff minify all images 2017-07-19 13:24:44 -04:00
Daniela Arcese
bbc9cab661 make footer not overlap 2017-07-19 13:20:06 -04:00
Erica
6179e07dd8 Merge pull request #249 from mozilla/ui
change how the file upload box expands
2017-07-19 13:18:10 -04:00
Abhinav Adduri
1363f4d6a2 adding alt translations 2017-07-19 10:17:23 -07:00
Danny Coates
3e47556560 Merge pull request #246 from clouserw/224
remove P2P references.  Fixes #224
2017-07-19 09:55:35 -07:00
Daniela Arcese
aa3a2f8c55 change how the file upload box expands 2017-07-19 12:55:05 -04:00
Erica
6e7283664c Merge pull request #242 from mozilla/ui
Make only icons clickable in file list
2017-07-19 12:53:27 -04:00
Wil Clouser
fc89da153d remove P2P references. Fixes #224 2017-07-19 09:23:42 -07:00
Abhinav Adduri
8c4cb90b3a finished l10n 2017-07-19 09:04:27 -07:00
Daniela Arcese
3cdd207953 make only the icons clickable in file list 2017-07-19 10:50:23 -04:00
Daniela Arcese
5b1e2f38f4 change icons 2017-07-19 10:21:56 -04:00
Wil Clouser
5822900508 Merge pull request #236 from clouserw/faq
add FAQ. Fixes #186
2017-07-18 17:41:35 -07:00
Wil Clouser
e898f35c46 add FAQ. Fixes #186 2017-07-18 15:57:26 -07:00
Abhinav Adduri
7ed30f497b finished localizations except for download.js 2017-07-18 15:46:44 -07:00
Danny Coates
e66bc966d2 Merge pull request #235 from mozilla/ui
allow send another file link to open in new tab
2017-07-18 14:59:02 -07:00
Daniela Arcese
acfcae5dec allow send another file link to open in new tab 2017-07-18 17:18:11 -04:00
Danny Coates
95a83922df Merge pull request #234 from mozilla/ui
fix download svg
2017-07-18 12:43:03 -07:00
Danny Coates
f7329e3316 Merge pull request #232 from mozilla/escapeFilename
escape filename in the ui
2017-07-18 12:41:21 -07:00
Danny Coates
cde63595e8 Merge pull request #226 from mozilla/cancel_uploads
added functionality to cancel uploads
2017-07-18 12:39:52 -07:00
Daniela Arcese
ca6efdae55 fix download svg 2017-07-18 15:39:30 -04:00
Danny Coates
b712a9d175 escape filename in the ui 2017-07-18 12:37:39 -07:00
Danny Coates
45eccc1cad Merge pull request #231 from mozilla/ui
move head and html tags to main template
2017-07-18 12:33:51 -07:00
Daniela Arcese
4068291f7c move head and html tags to main template 2017-07-18 14:57:29 -04:00
Erica
e4a724422e Merge pull request #228 from mozilla/send-logo
add send logo
2017-07-18 14:33:51 -04:00
Erica
4fa22a6262 Merge pull request #229 from mozilla/ui
change learn more and github links
2017-07-18 14:32:11 -04:00
Daniela Arcese
1fae4cfd8d change learn more and github links 2017-07-18 14:16:28 -04:00
Daniela Arcese
9a6dbee694 add send logo 2017-07-18 14:12:42 -04:00
Danny Coates
716555f76f Merge pull request #201 from chuckharmston/5-metrics-docs
Adds metrics documentation (closes #5).
2017-07-18 10:54:52 -07:00
Abhinav Adduri
cc35206ee4 added functionality to cancel uploads 2017-07-18 10:52:32 -07:00
Daniela Arcese
359e77f451 add send logo 2017-07-18 12:44:40 -04:00
Danny Coates
b805c78a9a Merge pull request #223 from mozilla/send-new-file
change size of send another file links
2017-07-18 08:44:46 -07:00
Daniela Arcese
a4ee6ad5e5 change size of send another file links 2017-07-18 11:37:51 -04:00
Erica
22b1c3a286 Merge pull request #222 from mozilla/ui
add footer
2017-07-18 11:27:29 -04:00
Daniela Arcese
cd6648be56 add footer 2017-07-18 11:17:16 -04:00
Abhinav Adduri
8d00372824 adding flexibility for circle test times 2017-07-17 18:06:20 -07:00
Abhinav Adduri
729f716e97 Merge pull request #197 from mozilla/fixes195and192
fixes issues 195 and 192
2017-07-17 16:26:16 -07:00
Abhinav Adduri
93d2e91afa formatting 2017-07-17 16:22:43 -07:00
Abhinav Adduri
26b228a976 Merge branch 'master' of github.com:mozilla/send into fixes195and192 2017-07-17 16:18:14 -07:00
Danny Coates
9735aa62bd Merge pull request #204 from mozilla/hsts
added HSTS header
2017-07-17 16:15:32 -07:00
Abhinav Adduri
4122f4dc3e Merge pull request #193 from mozilla/frontend_tests
Frontend tests
2017-07-17 16:08:51 -07:00
Abhinav Adduri
b88cf22d8a Merge branch 'master' of github.com:mozilla/send into frontend_tests 2017-07-17 16:04:10 -07:00
Danny Coates
6970e9228a changed CSP quotes 2017-07-17 15:49:09 -07:00
Danny Coates
5ce0846580 Merge pull request #191 from mozilla/ui
New ui!
2017-07-17 15:35:41 -07:00
Daniela Arcese
61c49fb329 fixing things 2017-07-17 16:29:51 -04:00
Danny Coates
2127857790 added HSTS header 2017-07-17 12:36:32 -07:00
Abhinav Adduri
ba348b6839 Merge branch 'master' of github.com:mozilla/send into frontend_tests 2017-07-17 10:52:27 -07:00
Abhinav Adduri
188521f985 circle is working 2017-07-17 10:43:13 -07:00
Abhinav Adduri
e0847e08c3 testing failure 2017-07-17 10:38:19 -07:00
Abhinav Adduri
d287f67ac0 testing circle again 2017-07-17 10:27:43 -07:00
Abhinav Adduri
8f327fa439 testing circle 2017-07-17 10:19:53 -07:00
Abhinav Adduri
ab60262cc9 testing circle 2017-07-17 09:49:24 -07:00
Abhinav Adduri
e5f2b386bb testing circle install firefox 2017-07-17 09:39:51 -07:00
Chuck Harmston
61f131af58 Adds metrics documentation (closes #5). 2017-07-16 11:43:54 -05:00
Abhinav Adduri
2cf2fcebc9 added tagLength to make app work in edge 2017-07-14 10:51:57 -07:00
Abhinav Adduri
c2e8139c6e moved decodeURIComponent to fileReceiver 2017-07-14 08:59:59 -07:00
Abhinav Adduri
ef9b15c1d7 bracket typo 2017-07-13 14:56:28 -07:00
Abhinav Adduri
e9c49073a8 simplified rejection for one promise 2017-07-13 14:47:53 -07:00
Abhinav Adduri
cec3d6b548 added one more assert to checksum test 2017-07-13 13:47:43 -07:00
Abhinav Adduri
3f89c2bf0a changed to decodeURIComponent in server code 2017-07-13 12:55:34 -07:00
Abhinav Adduri
b419a6025f fixes issues 195 and 192 2017-07-13 12:53:15 -07:00
Abhinav Adduri
b07671719c added comments for fraudulent checksum test case 2017-07-13 12:14:27 -07:00
Abhinav Adduri
6379a360fe fixing conflicts 2017-07-13 10:21:16 -07:00
Abhinav Adduri
89bc51c821 added unsafe and safe events instead of console logging when a checksum is tampered with 2017-07-13 09:53:59 -07:00
Abhinav Adduri
5dd5743871 indenting in frontend.bundle.js 2017-07-13 08:59:35 -07:00
Abhinav Adduri
91cc82d570 removing bundle.js from tests and adding to gitignore 2017-07-13 08:57:29 -07:00
Daniela Arcese
fcdb905430 lint 2017-07-13 11:44:46 -04:00
Daniela Arcese
9032e42912 new ui 2017-07-13 11:24:36 -04:00
Abhinav Adduri
00462947e3 finished frontend tests 2017-07-12 16:23:55 -07:00
Abhinav Adduri
b411447ebb finished sender tests, figuring out array buffer to string conversions 2017-07-12 13:05:06 -07:00
Danny Coates
52173bf6e7 Merge pull request #189 from mozilla/csp
Add CSP directives
2017-07-12 11:21:55 -07:00
Danny Coates
9234bce75d added csp directives 2017-07-12 11:11:17 -07:00
Danny Coates
b32e63c305 reformat 2017-07-12 10:53:29 -07:00
Danny Coates
ee8ff3d220 Merge pull request #188 from mozilla/fixDeleteError
fixes delete button error
2017-07-12 09:10:27 -07:00
Abhinav Adduri
3138f111e1 fixes delete button error 2017-07-12 09:01:31 -07:00
Abhinav Adduri
af0c497aab added front end tests 2017-07-12 09:00:02 -07:00
Danny Coates
dad6132342 Merge pull request #185 from mozilla/events128
added loading, hashing, and encrypting events for uploader; decryptin…
2017-07-11 13:54:43 -07:00
Danny Coates
3ffdbd863b Merge pull request #183 from mozilla/rename
rename to 'Send'
2017-07-11 13:49:58 -07:00
Danny Coates
20b9766742 rename to 'send' 2017-07-11 13:45:31 -07:00
Abhinav Adduri
1e23548539 Merge branch 'events128' of github.com:mozilla/send into events128 2017-07-11 13:38:54 -07:00
Abhinav Adduri
bfdab156e6 added loading, hashing, and encrypting events for uploader; decrypting and hashing events for the downloader 2017-07-11 13:38:23 -07:00
Abhinav Adduri
395c38b644 added loading, hashing, and encrypting events for uploader; decrypting and hashing events for the downloader 2017-07-11 13:30:25 -07:00
Danny Coates
57c7c475fc Merge pull request #184 from mozilla/server_tests
Server tests
2017-07-11 13:01:06 -07:00
Abhinav Adduri
191a0f93ff lint and circle.yml changes 2017-07-11 12:49:24 -07:00
Abhinav Adduri
cdf45de8e2 added server tests 2017-07-11 12:47:40 -07:00
Abhinav Adduri
6181ea6463 Merge pull request #178 from mozilla/fixes158and152
fixed issues in branch title
2017-07-11 12:14:15 -07:00
Abhinav Adduri
8c907c9029 removed extraneous failure 2017-07-11 12:10:11 -07:00
Abhinav Adduri
6231385c74 fixed issues in branch title 2017-07-11 11:18:31 -07:00
Danny Coates
109fd671e0 Merge pull request #177 from mozilla/gcmCompliance
Gcm compliance
2017-07-10 21:45:31 -07:00
Abhinav Adduri
2682f95a2b fixed for id/edge and removed some html 2017-07-10 13:48:00 -07:00
Abhinav Adduri
fce615842d no longer renders 'send another file' 2017-07-10 13:35:32 -07:00
Abhinav Adduri
64998de423 added check to see if browser is gcm compliant 2017-07-10 13:27:01 -07:00
Danny Coates
2031158336 Merge pull request #106 from mozilla/gcm
Gcm
2017-07-10 12:50:18 -07:00
Abhinav Adduri
6aa79472bf fixing small issues 2017-07-10 12:45:20 -07:00
Abhinav Adduri
c4b7a2bd97 linting issues 2017-07-10 12:30:17 -07:00
Abhinav Adduri
6f7930e34d changed localstorage id's to match response from server, refactored meta.delete and newId out of storage module 2017-07-10 12:19:20 -07:00
Abhinav Adduri
dc4682eaf5 added checksums 2017-07-10 11:25:03 -07:00
Danny Coates
125e6ecbdb Merge pull request #168 from mozilla/ui
Show error page if upload fails
2017-07-10 09:37:38 -07:00
Danny Coates
412a785819 Merge pull request #148 from pdehaan/yo-contribute-json
WIP: Add basic contribute.json
2017-07-10 09:36:37 -07:00
Danny Coates
d63e22ab7e Merge pull request #162 from pdehaan/readme-dev-server
Fix dev server URL in README.md file
2017-07-10 09:35:47 -07:00
Danny Coates
97d513db5f Merge pull request #167 from relud/patch-1
build docker image with new name
2017-07-10 09:24:09 -07:00
Abhinav Adduri
be470c6b6e added tagLength property to encrypt and decrypt for functionality in edge 2017-07-07 14:59:42 -07:00
Abhinav Adduri
1ce24f7e08 id is now independent on iv 2017-07-07 14:47:56 -07:00
Daniela Arcese
7ccf89b43b send errors to Raven 2017-07-07 10:37:10 -04:00
Daniela Arcese
63fe2c7099 show error page if upload fails 2017-07-06 17:17:59 -04:00
Peter deHaan
05da4937a1 Update server URLs to send.* 2017-07-06 12:25:49 -07:00
Peter deHaan
d1ee285429 Change README.md server URLs to send.* 2017-07-06 12:12:01 -07:00
Daniel Thorn
adf97a83f9 build docker image with new name 2017-07-06 10:10:32 -07:00
Erica
cbd1daca1e Merge pull request #164 from mozilla/ui
Add word wraps to table
2017-07-05 13:22:53 -04:00
Daniela Arcese
30f2e25903 Add word wraps to table 2017-07-05 13:11:00 -04:00
Peter deHaan
fb41acb438 Update to latest dev server URL 2017-07-05 09:59:02 -07:00
Peter deHaan
f845dd7d59 Fix dev server URL in README.md file 2017-07-05 09:47:22 -07:00
Danny Coates
caa276b33c Merge pull request #149 from pdehaan/yo-robots-txt
Add robots.txt
2017-07-05 07:29:34 -07:00
Danny Coates
ccd8c2995e Merge pull request #161 from mozilla/ui
Hide table header on empty list
2017-07-05 07:14:47 -07:00
Daniela Arcese
88ba5352d4 hide table header on empty list 2017-07-05 09:56:38 -04:00
Daniela Arcese
2735fa577f Merge pull request #154 from mozilla/ui
Remove expired uploads
2017-06-30 11:08:24 -07:00
Daniela Arcese
1908ce084d fix polling function 2017-06-30 10:58:58 -07:00
Daniela Arcese
9026702e7b lint 2017-06-30 09:47:50 -07:00
Daniela Arcese
421dd30c9d remove expired uploads 2017-06-29 16:08:57 -07:00
Danny Coates
a11b4b677c updated storage tests 2017-06-29 15:20:09 -07:00
Peter deHaan
10e64000f4 Merge pull request #146 from pdehaan/yo-readme
Update README with some more details
2017-06-29 14:50:46 -07:00
Danny Coates
67f586b65c format 2017-06-29 10:30:08 -07:00
Danny Coates
05fe534e14 use header for file metadata 2017-06-29 10:27:36 -07:00
Danny Coates
4cb34844aa use 128-bit GCM 2017-06-28 11:30:14 -07:00
Abhinav Adduri
34c367c49f added aad encryption 2017-06-27 14:39:23 -07:00
Abhinav Adduri
50995238bd gcm encryption 2017-06-27 10:50:14 -07:00
Peter deHaan
e00ff0d781 Add robots.txt 2017-06-26 14:42:21 -07:00
Peter deHaan
5d21c7c705 Add basic contribute.json 2017-06-26 14:29:42 -07:00
Peter deHaan
250503b2d3 Update README with some more details 2017-06-26 12:43:08 -07:00
84 changed files with 6894 additions and 2170 deletions

View File

@@ -5,3 +5,4 @@ static
test
scripts
docs
firefox

View File

@@ -1,2 +1,3 @@
public/bundle.js
public/webcrypto-shim.js
public
test/frontend/bundle.js
firefox

View File

@@ -26,4 +26,4 @@ rules:
no-var: error
one-var: [error, never]
prefer-const: error
quotes: [error, single]
quotes: [error, single, {avoidEscape: true}]

5
.gitignore vendored
View File

@@ -1,6 +1,9 @@
.DS_Store
node_modules
public/bundle.js
public/upload.js
public/download.js
public/version.json
public/l20n.min.js
static/*
!static/info.txt
test/frontend/bundle.js

View File

@@ -1,6 +1,6 @@
extends: stylelint-config-standard
rules:
color-hex-case: upper
color-hex-case: lower
declaration-colon-newline-after: null
selector-list-comma-newline-after: null

View File

@@ -1,5 +1,6 @@
FROM node:8-alpine
RUN apk add --no-cache git
RUN addgroup -S -g 10001 app && adduser -S -D -G app -u 10001 app
COPY . /app
RUN chown -R app /app

View File

@@ -1,3 +1,51 @@
* Install the redis server if not installed.
* To run the project, make sure you have a redis server running locally: redis-server /usr/local/etc/redis.conf
* Follow instructions inside the console on the browser.
# Firefox Send
[![CircleCI](https://img.shields.io/circleci/project/github/mozilla/send.svg)](https://circleci.com/gh/mozilla/send)
[![Available on Test Pilot](https://img.shields.io/badge/available_on-Test_Pilot-0996F8.svg)](https://testpilot.firefox.com/experiments/send)
**Docs:** [Docker](docs/docker.md), [Metrics](docs/metrics.md)
## What it does
A file sharing experiment which allows you to send encrypted files to other users.
## Requirements
- [Node.js 8+](https://nodejs.org/)
- [Redis server](https://redis.io/)
**NOTE:** To run the project, make sure you have a Redis server running locally:
```sh
$ redis-server /usr/local/etc/redis.conf
```
## How to use it
| Command | Description |
|------------------|-------------|
| `npm run dev` | Builds and starts the web server locally for development.
| `npm run format` | Formats the frontend and server code using **prettier**.
| `npm run lint` | Lints the CSS and JavaScript code.
| `npm start` | Starts the Express web server.
| `npm test` | Runs the suite of mocha tests.
## Localization
_Coming soon_ (see [#57](https://github.com/mozilla/send/issues/57))
## Contributing
Pull requests are always welcome! Feel free to check out the list of ["good first bugs"](https://github.com/mozilla/send/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+bug%22).
## Testing
| ENVIRONMENT | URL
|-------------|-----
| Production | <https://send.firefox.com/>
| Stage | <https://send.stage.mozaws.net/>
| Development | <https://send.dev.mozaws.net/>
## License
[Mozilla Public License Version 2.0](LICENSE)

View File

@@ -3,25 +3,34 @@ machine:
version: 8
services:
- docker
- redis
environment:
PATH: "/home/ubuntu/send/firefox:$PATH"
dependencies:
pre:
- npm i -g get-firefox geckodriver nsp
- get-firefox --platform linux --extract --target /home/ubuntu/send
deployment:
latest:
branch: master
commands:
- npm run predocker
- npm run build
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
- docker build -t mozilla/fileshare:latest .
- docker push mozilla/fileshare:latest
- docker build -t mozilla/send:latest .
- docker push mozilla/send:latest
tags:
tag: /.*/
owner: mozilla
commands:
- npm run predocker
- npm run build
- docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
- docker build -t mozilla/fileshare:$CIRCLE_TAG .
- docker push mozilla/fileshare:$CIRCLE_TAG
- docker build -t mozilla/send:$CIRCLE_TAG .
- docker push mozilla/send:$CIRCLE_TAG
test:
override:
- npm run lint
- npm test
- nsp check

View File

@@ -7,6 +7,6 @@ services:
ports:
- "1443:1443"
environment:
- P2P_REDIS_HOST=redis
- REDIS_HOST=redis
redis:
image: redis:alpine

View File

@@ -1,10 +1,24 @@
Environment Variables:
## Environment variables:
PORT - port the server will listen on (defaults to 1443)
P2P_S3_BUCKET - the S3 bucket name
P2P_REDIS_HOST - host name of the redis server
NODE_ENV - production
| Name | Description
|------------------|-------------|
| `PORT` | Port the server will listen on (defaults to 1443).
| `S3_BUCKET` | The S3 bucket name.
| `REDIS_HOST` | Host name of the Redis server.
| `GOOGLE_ANALYTICS_ID` | Google Analytics ID
| `SENTRY_CLIENT` | Sentry Client ID
| `SENTRY_DSN` | Sentry DSN
| `MAX_FILE_SIZE` | in bytes (defaults to 2147483648)
| `NODE_ENV` | "production"
Example
## Example:
docker run --net=host -e 'NODE_ENV=production' -e 'P2P_S3_BUCKET=testpilot-p2p-dev' -e 'P2P_REDIS_HOST=dyf9s2r4vo3.bolxr4.0001.usw2.cache.amazonaws.com' mozilla/portal:latest
```sh
$ docker run --net=host -e 'NODE_ENV=production' \
-e 'S3_BUCKET=testpilot-p2p-dev' \
-e 'REDIS_HOST=dyf9s2r4vo3.bolxr4.0001.usw2.cache.amazonaws.com' \
-e 'GOOGLE_ANALYTICS_ID=UA-35433268-78' \
-e 'SENTRY_CLIENT=https://51e23d7263e348a7a3b90a5357c61cb2@sentry.prod.mozaws.net/168' \
-e 'SENTRY_DSN=https://51e23d7263e348a7a3b90a5357c61cb2:65e23d7263e348a7a3b90a5357c61c44@sentry.prod.mozaws.net/168' \
mozilla/send:latest
```

32
docs/faq.md Normal file
View File

@@ -0,0 +1,32 @@
## How big of a file can I transfer with Firefox Send?
There is a 2GB file size limit built in to Send, however, in practice you may
be unable to send files that large. Send encrypts and decrypts the files in
the browser which is great for security but will tax your system resources. In
particular you can expect to see your memory usage go up by at least the size
of the file when the transfer is processing. You can see [the results of some
testing](https://github.com/mozilla/send/issues/170#issuecomment-314107793).
For the most reliable operation on common computers, its probably best to stay
under a few hundred megabytes.
## Why is my browser not supported?
Were using the [Web Cryptography JavaScript API with the AES-GCM
algorithm](https://www.w3.org/TR/WebCryptoAPI/#aes-gcm) for our encryption.
Many browsers support this standard and should work fine, but some have not
implemented it yet (mobile browsers lag behind on this, in
particular).
## How long are files available for?
Files are available to be downloaded for 24 hours, after which they are removed
from the server. They are also removed immediately after a download completes.
## Can a file be downloaded more than once?
Not currently, but we're considering multiple download support in a future
release.
*Disclaimer: Send is an experiment and under active development. The answers
here may change as we get feedback from you and the project matures.*

136
docs/metrics.md Normal file
View File

@@ -0,0 +1,136 @@
# Send Metrics
The metrics collection and analysis plan for Send, a forthcoming Test Pilot experiment.
## Analysis
Data collected by Send will be used to answer the following high-level questions:
- Do users send files?
- How often? How many?
- What is the retention?
- What is the distribution of senders?
- How do recipients interact with promotional UI elements?
- Are file recipients converted to file senders?
- Are non-Firefox users converted to Firefox users?
- Where does it go wrong?
- How often are there errors in uploading or downloading files?
- What types of errors to users commonly see?
- At what point do errors affect retention?
## Collection
Data will be collected with Google Analytics and follow [Test Pilot standards](https://github.com/mozilla/testpilot/blob/master/docs/experiments/ga.md) for reporting.
### Custom Metrics
- `cm1` - the size of the file, in bytes.
- `cm2` - the amount of time it took to complete the file transfer, in milliseconds. Only include if the file completed transferring (ref: `cd2`).
- `cm3` - the rate of the file transfer, in bytes per second. This is computed by dividing `cm1` by `cm2`, not by monitoring transfer speeds. Only include if the file completed transferring (ref: `cd2`).
- `cm4` - the amount of time until the file will expire, in milliseconds.
- `cm5` - the number of files the user has ever uploaded.
- `cm6` - the number of unexpired files the user has uploaded.
- `cm7` - the number of files the user has ever downloaded.
### Custom Dimensions
- `cd1` - the method by which the user initiated an upload. One of `drag`, `click`.
- `cd2` - the reason that the file transfer stopped. One of `completed`, `errored`, `cancelled`.
- `cd3` - the destination of a link click. One of `experiment-page`, `download-firefox`, `twitter`, `github`, `cookies`, `terms`, `privacy`, `about`, `legal`, `mozilla`.
- `cd4` - the location from which the user copied the URL to an upload file. One of `success-screen`, `upload-list`.
- `cd5` - the referring location. One of `completed-download`, `errored-download`, `cancelled-download`, `completed-upload`, `errored-upload`, `cancelled-upload`, `testpilot`, `external`.
- `cd6` - identifying information about an error. Exclude if there is no error involved. **TODO:** enumerate a list of possibilities.
### Events
_NB:_ due to how files are being tracked, there are no events indicating file expiry. This carries some risk: most notably, we can only derive expiration rates by looking at download rates, which is prone to skew if there are problems in data collection.
#### `upload-started`
Triggered whenever a user begins uploading a file. Includes:
- `ec` - `sender`
- `ea` - `upload-started`
- `cm1`
- `cm5`
- `cm6`
- `cm7`
- `cd1`
- `cd5`
#### `upload-stopped`
Triggered whenever a user stops uploading a file. Includes:
- `ec` - `sender`
- `ea` - `upload-stopped`
- `cm1`
- `cm2`
- `cm3`
- `cm5`
- `cm6`
- `cm7`
- `cd1`
- `cd2`
- `cd6`
#### `download-started`
Triggered whenever a user begins downloading a file. Includes:
- `ec` - `recipient`
- `ea` - `download-started`
- `cm1`
- `cm4`
- `cm5`
- `cm6`
- `cm7`
#### `download-stopped`
Triggered whenever a user stops downloading a file.
- `ec` - `recipient`
- `ea` - `download-stopped`
- `cm1`
- `cm2` (if possible and applicable)
- `cm3` (if possible and applicable)
- `cm5`
- `cm6`
- `cm7`
- `cd2`
- `cd6`
#### `exited`
Fired whenever a user follows a link external to Send.
- `ec` - `recipient`, `sender`, or `other`, as applicable.
- `ea` - `exited`
- `cd3`
#### `upload-deleted`
Fired whenever a user deletes a file theyve uploaded.
- `ec` - `sender`
- `ea` - `upload-deleted`
- `cm1`
- `cm2`
- `cm3`
- `cm4`
- `cm5`
- `cm6`
- `cm7`
- `cd1`
- `cd4`
#### `copied`
Fired whenever a user copies the URL of an upload file.
- `ec` - `sender`
- `ea` - `copied`
- `cd4`
#### `restarted`
Fired whenever the user interrupts any part of funnel to return to the start of it (e.g. with a “send another file” or “send your own files” link).
- `ec` - `recipient`, `sender`, or `other`, as applicable.
- `ea` - `restarted`
- `cd2`
#### `unsupported`
Fired whenever a user is presented a message saying that their browser is unsupported due to missing crypto APIs.
- `ec` - `sender`
- `ea` - `unsupported`
- `cd6`

10
frontend/src/common.js Normal file
View File

@@ -0,0 +1,10 @@
window.Raven = require('raven-js');
window.Raven.config(window.dsn).install();
window.dsn = undefined;
const testPilotGA = require('testpilot-ga');
window.analytics = new testPilotGA({
an: 'Firefox Send',
ds: 'web',
tid: window.trackerId
});

View File

@@ -1,52 +1,178 @@
require('./common');
const FileReceiver = require('./fileReceiver');
const { notify } = require('./utils');
const { notify, findMetric, gcmCompliant, sendEvent } = require('./utils');
const bytes = require('bytes');
const Storage = require('./storage');
const storage = new Storage(localStorage);
const $ = require('jquery');
require('jquery-circle-progress');
const Raven = window.Raven;
$(document).ready(function() {
$('#download-progress').hide();
$('#send-file').click(() => {
window.location.replace(`${window.location.origin}`);
gcmCompliant().catch(err => {
$('#download').attr('hidden', true);
sendEvent('recipient', 'unsupported', {
cd6: err
}).then(() => {
location.replace('/unsupported');
});
});
const download = () => {
const fileReceiver = new FileReceiver();
const name = document.createElement('p');
const $btn = $('#download-btn');
//link back to homepage
$('.send-new').attr('href', window.location.origin);
fileReceiver.on('progress', percentComplete => {
$('#download-page-one').hide();
$('.send-new').hide();
$('#download-progress').show();
$('.send-new').click(function(target) {
target.preventDefault();
sendEvent('recipient', 'restarted', {
cd2: 'completed'
}).then(() => {
location.href = target.currentTarget.href;
});
});
$('.legal-links a, .social-links a, #dl-firefox').click(function(target) {
target.preventDefault();
const metric = findMetric(target.currentTarget.href);
// record exited event by recipient
sendEvent('recipient', 'exited', {
cd3: metric
}).then(() => {
location.href = target.currentTarget.href;
});
});
const filename = $('#dl-filename').text();
const bytelength = Number($('#dl-bytelength').text());
const timeToExpiry = Number($('#dl-ttl').text());
//initiate progress bar
$('#dl-progress').circleProgress({
value: 0.0,
startAngle: -Math.PI / 2,
fill: '#3B9DFF',
size: 158,
animation: { duration: 300 }
});
$('#download-btn').click(download);
function download() {
storage.totalDownloads += 1;
const fileReceiver = new FileReceiver();
const unexpiredFiles = storage.numFiles;
fileReceiver.on('progress', progress => {
window.onunload = function() {
storage.referrer = 'cancelled-download';
// record download-stopped (cancelled by tab close or reload)
sendEvent('recipient', 'download-stopped', {
cm1: bytelength,
cm5: storage.totalUploads,
cm6: unexpiredFiles,
cm7: storage.totalDownloads,
cd2: 'cancelled'
});
};
$('#download-page-one').attr('hidden', true);
$('#download-progress').removeAttr('hidden');
const percent = progress[0] / progress[1];
// update progress bar
document
.querySelector('#progress-bar')
.style.setProperty('--progress', percentComplete + '%');
$('#progress-text').html(`${percentComplete}%`);
//on complete
if (percentComplete === 100) {
$('#dl-progress').circleProgress('value', percent);
$('.percent-number').text(`${Math.floor(percent * 100)}`);
$('.progress-text').text(
`${filename} (${bytes(progress[0], {
decimalPlaces: 1,
fixedDecimals: true
})} of ${bytes(progress[1], { decimalPlaces: 1 })})`
);
});
let downloadEnd;
fileReceiver.on('decrypting', isStillDecrypting => {
// The file is being decrypted
if (isStillDecrypting) {
fileReceiver.removeAllListeners('progress');
$('#download-text').html('Download complete!');
$('.send-new').show();
$btn.text('Download complete!');
$btn.attr('disabled', 'true');
notify('Your download has finished.');
window.onunload = null;
document.l10n.formatValue('decryptingFile').then(decryptingFile => {
$('.progress-text').text(decryptingFile);
});
} else {
console.log('Done decrypting');
downloadEnd = Date.now();
}
});
fileReceiver.on('hashing', isStillHashing => {
// The file is being hashed to make sure a malicious user hasn't tampered with it
if (isStillHashing) {
document.l10n.formatValue('verifyingFile').then(verifyingFile => {
$('.progress-text').text(verifyingFile);
});
} else {
$('.progress-text').text(' ');
document.l10n
.formatValues('downloadNotification', 'downloadFinish')
.then(translated => {
notify(translated[0]);
$('.title').text(translated[1]);
});
}
});
const startTime = Date.now();
// record download-started by recipient
sendEvent('recipient', 'download-started', {
cm1: bytelength,
cm4: timeToExpiry,
cm5: storage.totalUploads,
cm6: unexpiredFiles,
cm7: storage.totalDownloads
});
fileReceiver
.download()
.catch(() => {
$('.title').text(
'This link has expired or never existed in the first place.'
);
$('#download-btn').hide();
$('#expired-img').show();
console.log('The file has expired, or has already been deleted.');
.catch(err => {
// record download-stopped (errored) by recipient
sendEvent('recipient', 'download-stopped', {
cm1: bytelength,
cm5: storage.totalUploads,
cm6: unexpiredFiles,
cm7: storage.totalDownloads,
cd2: 'errored',
cd6: err
});
if (err.message === 'notfound') {
location.reload();
} else {
document.l10n.formatValue('errorPageHeader').then(translated => {
$('.title').text(translated);
});
$('#download-btn').attr('hidden', true);
$('#expired-img').removeAttr('hidden');
}
return;
})
.then(([decrypted, fname]) => {
name.innerText = fname;
const endTime = Date.now();
const totalTime = endTime - startTime;
const downloadTime = endTime - downloadEnd;
const downloadSpeed = bytelength / (downloadTime / 1000);
storage.referrer = 'completed-download';
// record download-stopped (completed) by recipient
sendEvent('recipient', 'download-stopped', {
cm1: bytelength,
cm2: totalTime,
cm3: downloadSpeed,
cm5: storage.totalUploads,
cm6: unexpiredFiles,
cm7: storage.totalDownloads,
cd2: 'completed'
});
const dataView = new DataView(decrypted);
const blob = new Blob([dataView]);
const downloadUrl = URL.createObjectURL(blob);
@@ -66,7 +192,5 @@ $(document).ready(function() {
Raven.captureException(err);
return Promise.reject(err);
});
};
window.download = download;
}
});

View File

@@ -1,12 +1,9 @@
const EventEmitter = require('events');
const { strToIv } = require('./utils');
const Raven = window.Raven;
const { hexToArray } = require('./utils');
class FileReceiver extends EventEmitter {
constructor() {
super();
this.salt = strToIv(location.pathname.slice(10, -1));
}
download() {
@@ -15,30 +12,26 @@ class FileReceiver extends EventEmitter {
const xhr = new XMLHttpRequest();
xhr.onprogress = event => {
if (event.lengthComputable) {
const percentComplete = Math.floor(
event.loaded / event.total * 100
);
this.emit('progress', percentComplete);
if (event.lengthComputable && event.target.status !== 404) {
this.emit('progress', [event.loaded, event.total]);
}
};
xhr.onload = function(event) {
if (xhr.status === 404) {
reject(
new Error('The file has expired, or has already been deleted.')
);
reject(new Error('notfound'));
return;
}
const blob = new Blob([this.response]);
const fileReader = new FileReader();
fileReader.onload = function() {
const meta = JSON.parse(xhr.getResponseHeader('X-File-Metadata'));
resolve({
data: this.result,
fname: xhr
.getResponseHeader('Content-Disposition')
.match(/=(.+)/)[1]
aad: meta.aad,
filename: meta.filename,
iv: meta.id
});
};
@@ -54,35 +47,55 @@ class FileReceiver extends EventEmitter {
{
kty: 'oct',
k: location.hash.slice(1),
alg: 'A128CBC',
alg: 'A128GCM',
ext: true
},
{
name: 'AES-CBC'
name: 'AES-GCM'
},
true,
['encrypt', 'decrypt']
)
])
.then(([fdata, key]) => {
const salt = this.salt;
this.emit('decrypting', true);
return Promise.all([
window.crypto.subtle.decrypt(
{
name: 'AES-CBC',
iv: salt
},
key,
fdata.data
),
new Promise((resolve, reject) => {
resolve(fdata.fname);
})
window.crypto.subtle
.decrypt(
{
name: 'AES-GCM',
iv: hexToArray(fdata.iv),
additionalData: hexToArray(fdata.aad),
tagLength: 128
},
key,
fdata.data
)
.then(decrypted => {
this.emit('decrypting', false);
return Promise.resolve(decrypted);
}),
fdata.filename,
hexToArray(fdata.aad)
]);
})
.catch(err => {
Raven.captureException(err);
return Promise.reject(err);
.then(([decrypted, fname, proposedHash]) => {
this.emit('hashing', true);
return window.crypto.subtle
.digest('SHA-256', decrypted)
.then(calculatedHash => {
this.emit('hashing', false);
const integrity =
new Uint8Array(calculatedHash).toString() ===
proposedHash.toString();
if (!integrity) {
this.emit('unsafe', true);
return Promise.reject();
} else {
this.emit('safe', true);
return Promise.all([decrypted, decodeURIComponent(fname)]);
}
});
});
}
}

View File

@@ -1,5 +1,5 @@
const EventEmitter = require('events');
const { ivToStr } = require('./utils');
const { arrayToHex } = require('./utils');
const Raven = window.Raven;
@@ -7,7 +7,8 @@ class FileSender extends EventEmitter {
constructor(file) {
super();
this.file = file;
this.iv = window.crypto.getRandomValues(new Uint8Array(16));
this.iv = window.crypto.getRandomValues(new Uint8Array(12));
this.uploadXHR = new XMLHttpRequest();
}
static delete(fileId, token) {
@@ -35,70 +36,110 @@ class FileSender extends EventEmitter {
});
}
cancel() {
this.uploadXHR.abort();
}
upload() {
const self = this;
self.emit('loading', true);
return Promise.all([
window.crypto.subtle.generateKey(
{
name: 'AES-CBC',
length: 128
},
true,
['encrypt', 'decrypt']
),
window.crypto.subtle
.generateKey(
{
name: 'AES-GCM',
length: 128
},
true,
['encrypt', 'decrypt']
)
.catch(err =>
console.log('There was an error generating a crypto key')
),
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsArrayBuffer(this.file);
reader.onload = function(event) {
resolve(new Uint8Array(this.result));
self.emit('loading', false);
self.emit('hashing', true);
const plaintext = new Uint8Array(this.result);
window.crypto.subtle.digest('SHA-256', plaintext).then(hash => {
self.emit('hashing', false);
self.emit('encrypting', true);
resolve({ plaintext: plaintext, hash: new Uint8Array(hash) });
});
};
reader.onerror = function(err) {
reject(err);
};
})
])
.then(([secretKey, plaintext]) => {
.then(([secretKey, file]) => {
return Promise.all([
window.crypto.subtle.encrypt(
{
name: 'AES-CBC',
iv: this.iv
},
secretKey,
plaintext
),
window.crypto.subtle.exportKey('jwk', secretKey)
window.crypto.subtle
.encrypt(
{
name: 'AES-GCM',
iv: this.iv,
additionalData: file.hash,
tagLength: 128
},
secretKey,
file.plaintext
)
.then(encrypted => {
self.emit('encrypting', false);
return new Promise((resolve, reject) => {
resolve(encrypted);
});
}),
window.crypto.subtle.exportKey('jwk', secretKey),
new Promise((resolve, reject) => {
resolve(file.hash);
})
]);
})
.then(([encrypted, keydata]) => {
.then(([encrypted, keydata, hash]) => {
return new Promise((resolve, reject) => {
const file = this.file;
const fileId = ivToStr(this.iv);
const fileId = arrayToHex(this.iv);
const dataView = new DataView(encrypted);
const blob = new Blob([dataView], { type: file.type });
const fd = new FormData();
fd.append('fname', file.name);
fd.append('data', blob, file.name);
const xhr = new XMLHttpRequest();
const xhr = self.uploadXHR;
xhr.upload.addEventListener('progress', e => {
if (e.lengthComputable) {
const percentComplete = Math.floor(e.loaded / e.total * 100);
this.emit('progress', percentComplete);
self.emit('progress', [e.loaded, e.total]);
}
});
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
// uuid field and url field
const responseObj = JSON.parse(xhr.responseText);
resolve({
url: responseObj.url,
fileId: fileId,
secretKey: keydata.k,
deleteToken: responseObj.uuid
});
if (xhr.status === 200) {
const responseObj = JSON.parse(xhr.responseText);
return resolve({
url: responseObj.url,
fileId: responseObj.id,
secretKey: keydata.k,
deleteToken: responseObj.delete
});
}
reject(xhr.status);
}
};
xhr.open('post', '/upload/' + fileId, true);
xhr.open('post', '/upload', true);
xhr.setRequestHeader(
'X-File-Metadata',
JSON.stringify({
aad: arrayToHex(hash),
id: fileId,
filename: encodeURIComponent(file.name)
})
);
xhr.send(fd);
});
})

View File

@@ -1,5 +0,0 @@
window.Raven = require('raven-js');
window.Raven.config(window.dsn).install();
window.dsn = undefined;
require('./upload');
require('./download');

66
frontend/src/storage.js Normal file
View File

@@ -0,0 +1,66 @@
const { isFile } = require('./utils');
class Storage {
constructor(engine) {
this.engine = engine;
}
get totalDownloads() {
return Number(this.engine.getItem('totalDownloads'));
}
set totalDownloads(n) {
this.engine.setItem('totalDownloads', n);
}
get totalUploads() {
return Number(this.engine.getItem('totalUploads'));
}
set totalUploads(n) {
this.engine.setItem('totalUploads', n);
}
get referrer() {
return this.engine.getItem('referrer');
}
set referrer(str) {
this.engine.setItem('referrer', str);
}
get files() {
const fs = [];
for (let i = 0; i < this.engine.length; i++) {
const k = this.engine.key(i);
if (isFile(k)) {
fs.push(JSON.parse(this.engine.getItem(k))); // parse or whatever else
}
}
return fs;
}
get numFiles() {
let length = 0;
for (let i = 0; i < this.engine.length; i++) {
const k = this.engine.key(i);
if (isFile(k)) {
length += 1;
}
}
return length;
}
getFileById(id) {
return this.engine.getItem(id);
}
has(property) {
return this.engine.hasOwnProperty(property);
}
remove(property) {
this.engine.removeItem(property);
}
addFile(id, file) {
this.engine.setItem(id, JSON.stringify(file));
}
}
module.exports = Storage;

View File

@@ -1,25 +1,98 @@
/* global MAXFILESIZE EXPIRE_SECONDS */
require('./common');
const FileSender = require('./fileSender');
const { notify } = require('./utils');
const {
notify,
gcmCompliant,
findMetric,
sendEvent,
ONE_DAY_IN_MS
} = require('./utils');
const bytes = require('bytes');
const Storage = require('./storage');
const storage = new Storage(localStorage);
const $ = require('jquery');
require('jquery-circle-progress');
const Raven = window.Raven;
if (storage.has('referrer')) {
window.referrer = storage.referrer;
storage.remove('referrer');
} else {
window.referrer = 'external';
}
$(document).ready(function() {
gcmCompliant().catch(err => {
$('#page-one').attr('hidden', true);
sendEvent('sender', 'unsupported', {
cd6: err
}).then(() => {
location.replace('/unsupported');
});
});
$('#file-upload').change(onUpload);
$('.legal-links a, .social-links a, #dl-firefox').click(function(target) {
target.preventDefault();
const metric = findMetric(target.currentTarget.href);
// record exited event by recipient
sendEvent('sender', 'exited', {
cd3: metric
}).then(() => {
location.href = target.currentTarget.href;
});
});
$('#send-new-completed').click(function(target) {
target.preventDefault();
// record restarted event
sendEvent('sender', 'restarted', {
cd2: 'completed'
}).then(() => {
storage.referrer = 'completed-upload';
location.href = target.currentTarget.href;
});
});
$('#send-new-error').click(function(target) {
target.preventDefault();
// record restarted event
sendEvent('sender', 'restarted', {
cd2: 'errored'
}).then(() => {
storage.referrer = 'errored-upload';
location.href = target.currentTarget.href;
});
});
$('body').on('dragover', allowDrop).on('drop', onUpload);
// reset copy button
const $copyBtn = $('#copy-btn');
$copyBtn.attr('disabled', false);
$copyBtn.html('Copy');
$('#link').attr('disabled', false);
$copyBtn.attr('data-l10n-id', 'copyUrlFormButton');
$('#page-one').show();
$('#file-list').show();
$('#upload-progress').hide();
$('#share-link').hide();
for (let i = 0; i < localStorage.length; i++) {
const id = localStorage.key(i);
populateFileList(localStorage.getItem(id));
const files = storage.files;
if (files.length === 0) {
toggleHeader();
} else {
for (const index in files) {
const id = files[index].fileId;
//check if file still exists before adding to list
checkExistence(id, files[index], true);
}
}
// copy link to clipboard
$copyBtn.click(() => {
// record copied event from success screen
sendEvent('sender', 'copied', {
cd4: 'success-screen'
});
const aux = document.createElement('input');
aux.setAttribute('value', $('#link').attr('value'));
document.body.appendChild(aux);
@@ -28,135 +101,463 @@ $(document).ready(function() {
document.body.removeChild(aux);
//disable button for 3s
$copyBtn.attr('disabled', true);
$copyBtn.html('Copied!');
$('#link').attr('disabled', true);
$copyBtn.html(
'<img src="/resources/check-16.svg" class="icon-check"></img>'
);
window.setTimeout(() => {
$copyBtn.attr('disabled', false);
$copyBtn.html('Copy');
$('#link').attr('disabled', false);
$copyBtn.attr('data-l10n-id', 'copyUrlFormButton');
}, 3000);
});
// link back to home page
$('.send-new').click(() => {
$('#page-one').show();
$('#file-list').show();
$('#upload-progress').hide();
$('#share-link').hide();
$copyBtn.attr('disabled', false);
$copyBtn.html('Copy');
$('.upload-window').on('dragover', () => {
$('.upload-window').addClass('ondrag');
});
$('.upload-window').on('dragleave', () => {
$('.upload-window').removeClass('ondrag');
});
//initiate progress bar
$('#ul-progress').circleProgress({
value: 0.0,
startAngle: -Math.PI / 2,
fill: '#3B9DFF',
size: 158,
animation: { duration: 300 }
});
//link back to homepage
$('.send-new').attr('href', window.location);
// on file upload by browse or drag & drop
window.onUpload = event => {
function onUpload(event) {
event.preventDefault();
// don't allow upload if not on upload page
if ($('#page-one').attr('hidden')) {
return;
}
storage.totalUploads += 1;
let file = '';
if (event.type === 'drop') {
file = event.dataTransfer.files[0];
if (!event.originalEvent.dataTransfer.files[0]) {
$('.upload-window').removeClass('ondrag');
return;
}
if (
event.originalEvent.dataTransfer.files.length > 1 ||
event.originalEvent.dataTransfer.files[0].size === 0
) {
$('.upload-window').removeClass('ondrag');
document.l10n.formatValue('uploadPageMultipleFilesAlert').then(str => {
alert(str);
});
return;
}
file = event.originalEvent.dataTransfer.files[0];
} else {
file = event.target.files[0];
}
const fileSender = new FileSender(file);
fileSender.on('progress', percentComplete => {
$('#page-one').hide();
$('#file-list').hide();
$('#upload-progress').show();
$('#upload-filename').innerHTML += file.name;
// update progress bar
document
.querySelector('#progress-bar')
.style.setProperty('--progress', percentComplete + '%');
$('#progress-text').html(`${percentComplete}%`);
});
fileSender.upload().then(info => {
const url = info.url.trim() + `#${info.secretKey}`.trim();
$('#link').attr('value', url);
const fileData = {
name: file.name,
fileId: info.fileId,
url: info.url,
secretKey: info.secretKey,
deleteToken: info.deleteToken
};
localStorage.setItem(info.fileId, JSON.stringify(fileData));
$('#page-one').hide();
$('#file-list').hide();
$('#upload-progress').hide();
$('#share-link').show();
populateFileList(JSON.stringify(fileData));
notify('Your upload has finished.');
});
};
window.allowDrop = function(ev) {
ev.preventDefault();
};
//update file table with current files in localStorage
function populateFileList(file) {
try {
file = JSON.parse(file);
} catch (e) {
return;
if (file.size > MAXFILESIZE) {
return document.l10n
.formatValue('fileTooBig', { size: bytes(MAXFILESIZE) })
.then(alert);
}
$('#page-one').attr('hidden', true);
$('#upload-error').attr('hidden', true);
$('#upload-progress').removeAttr('hidden');
document.l10n.formatValue('importingFile').then(importingFile => {
$('.progress-text').text(importingFile);
});
//don't allow drag and drop when not on page-one
$('body').off('drop', onUpload);
const fileSender = new FileSender(file);
$('#cancel-upload').click(() => {
fileSender.cancel();
location.reload();
document.l10n.formatValue('uploadCancelNotification').then(str => {
notify(str);
});
storage.referrer = 'cancelled-upload';
// record upload-stopped (cancelled) by sender
sendEvent('sender', 'upload-stopped', {
cm1: file.size,
cm5: storage.totalUploads,
cm6: unexpiredFiles,
cm7: storage.totalDownloads,
cd1: event.type === 'drop' ? 'drop' : 'click',
cd2: 'cancelled'
});
});
fileSender.on('progress', progress => {
const percent = progress[0] / progress[1];
// update progress bar
$('#ul-progress').circleProgress('value', percent);
$('#ul-progress').circleProgress().on('circle-animation-end', function() {
$('.percent-number').text(`${Math.floor(percent * 100)}`);
});
$('.progress-text').text(
`${file.name} (${bytes(progress[0], {
decimalPlaces: 1,
fixedDecimals: true
})} of ${bytes(progress[1], { decimalPlaces: 1 })})`
);
});
fileSender.on('hashing', isStillHashing => {
// The file is being hashed
if (isStillHashing) {
document.l10n.formatValue('verifyingFile').then(verifyingFile => {
$('.progress-text').text(verifyingFile);
});
} else {
console.log('Finished hashing');
}
});
let uploadStart;
fileSender.on('encrypting', isStillEncrypting => {
// The file is being encrypted
if (isStillEncrypting) {
document.l10n.formatValue('encryptingFile').then(encryptingFile => {
$('.progress-text').text(encryptingFile);
});
} else {
console.log('Finished encrypting');
uploadStart = Date.now();
}
});
let t;
const startTime = Date.now();
const unexpiredFiles = storage.numFiles + 1;
// record upload-started event by sender
sendEvent('sender', 'upload-started', {
cm1: file.size,
cm5: storage.totalUploads,
cm6: unexpiredFiles,
cm7: storage.totalDownloads,
cd1: event.type === 'drop' ? 'drop' : 'click',
cd5: window.referrer
});
// For large files we need to give the ui a tick to breathe and update
// before we kick off the FileSender
setTimeout(() => {
fileSender
.upload()
.then(info => {
const endTime = Date.now();
const totalTime = endTime - startTime;
const uploadTime = endTime - uploadStart;
const uploadSpeed = file.size / (uploadTime / 1000);
const expiration = EXPIRE_SECONDS * 1000;
// record upload-stopped (completed) by sender
sendEvent('sender', 'upload-stopped', {
cm1: file.size,
cm2: totalTime,
cm3: uploadSpeed,
cm5: storage.totalUploads,
cm6: unexpiredFiles,
cm7: storage.totalDownloads,
cd1: event.type === 'drop' ? 'drop' : 'click',
cd2: 'completed'
});
const fileData = {
name: file.name,
size: file.size,
fileId: info.fileId,
url: info.url,
secretKey: info.secretKey,
deleteToken: info.deleteToken,
creationDate: new Date(),
expiry: expiration,
totalTime: totalTime,
typeOfUpload: event.type === 'drop' ? 'drop' : 'click',
uploadSpeed: uploadSpeed
};
storage.addFile(info.fileId, fileData);
$('#upload-filename').attr(
'data-l10n-id',
'uploadSuccessConfirmHeader'
);
t = window.setTimeout(() => {
$('#page-one').attr('hidden', true);
$('#upload-progress').attr('hidden', true);
$('#upload-error').attr('hidden', true);
$('#share-link').removeAttr('hidden');
}, 1000);
populateFileList(fileData);
document.l10n.formatValue('notifyUploadDone').then(str => {
notify(str);
});
})
.catch(err => {
// err is 0 when coming from a cancel upload event
if (err === 0) {
return;
}
// only show error page when the error is anything other than user cancelling the upload
Raven.captureException(err);
$('#page-one').attr('hidden', true);
$('#upload-progress').attr('hidden', true);
$('#upload-error').removeAttr('hidden');
window.clearTimeout(t);
// record upload-stopped (errored) by sender
sendEvent('sender', 'upload-stopped', {
cm1: file.size,
cm5: storage.totalUploads,
cm6: unexpiredFiles,
cm7: storage.totalDownloads,
cd1: event.type === 'drop' ? 'drop' : 'click',
cd2: 'errored',
cd6: err
});
});
}, 10);
}
function allowDrop(ev) {
ev.preventDefault();
}
function checkExistence(id, file, populate) {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
if (populate) {
populateFileList(file);
}
} else if (xhr.status === 404) {
storage.remove(id);
if (storage.numFiles === 0) {
toggleHeader();
}
}
}
};
xhr.open('get', '/exists/' + id, true);
xhr.send();
}
//update file table with current files in storage
function populateFileList(file) {
const row = document.createElement('tr');
const name = document.createElement('td');
const link = document.createElement('td');
const $copyIcon = $('<img>', {
src: '/resources/copy-16.svg',
class: 'icon-copy',
'data-l10n-id': 'copyUrlHover'
});
const expiry = document.createElement('td');
const del = document.createElement('td');
del.setAttribute('align', 'center');
const btn = document.createElement('button');
const $delIcon = $('<img>', {
src: '/resources/close-16.svg',
class: 'icon-delete',
'data-l10n-id': 'deleteButtonHover'
});
const popupDiv = document.createElement('div');
const $popupText = $('<span>', { class: 'popuptext' });
const $popupText = $('<div>', { class: 'popuptext' });
const cellText = document.createTextNode(file.name);
const url = file.url.trim() + `#${file.secretKey}`.trim();
$('#link').attr('value', url);
$('#copy-text').attr('data-l10n-args', '{"filename": "' + file.name + '"}');
$('#copy-text').attr('data-l10n-id', 'copyUrlFormLabelWithName');
$popupText.attr('tabindex', '-1');
name.appendChild(cellText);
// create delete button
btn.innerHTML = 'x';
btn.classList.add('delete-btn');
link.innerHTML = file.url.trim() + `#${file.secretKey}`.trim();
const delSpan = document.createElement('span');
$(delSpan).addClass('icon-cancel-1');
$(delSpan).attr('data-l10n-id', 'deleteButtonHover');
del.appendChild(delSpan);
const linkSpan = document.createElement('span');
$(linkSpan).addClass('icon-docs');
$(linkSpan).attr('data-l10n-id', 'copyUrlHover');
link.appendChild(linkSpan);
link.style.color = '#0A8DFF';
//copy link to clipboard when icon clicked
$copyIcon.click(function() {
// record copied event from upload list
sendEvent('sender', 'copied', {
cd4: 'upload-list'
});
const aux = document.createElement('input');
aux.setAttribute('value', url);
document.body.appendChild(aux);
aux.select();
document.execCommand('copy');
document.body.removeChild(aux);
document.l10n.formatValue('copiedUrl').then(translated => {
link.innerHTML = translated;
});
window.setTimeout(() => {
const linkImg = document.createElement('img');
$(linkImg).addClass('icon-copy');
$(linkImg).attr('data-l10n-id', 'copyUrlHover');
$(linkImg).attr('src', '/resources/copy-16.svg');
$(link).html(linkImg);
}, 500);
});
file.creationDate = new Date(file.creationDate);
const future = new Date();
future.setTime(file.creationDate.getTime() + file.expiry);
let countdown = 0;
countdown = future.getTime() - Date.now();
let minutes = Math.floor(countdown / 1000 / 60);
let hours = Math.floor(minutes / 60);
let seconds = Math.floor(countdown / 1000 % 60);
poll();
function poll() {
countdown = future.getTime() - Date.now();
minutes = Math.floor(countdown / 1000 / 60);
hours = Math.floor(minutes / 60);
seconds = Math.floor(countdown / 1000 % 60);
let t;
if (hours >= 1) {
expiry.innerHTML = hours + 'h ' + minutes % 60 + 'm';
t = window.setTimeout(() => {
poll();
}, 60000);
} else if (hours === 0) {
expiry.innerHTML = minutes + 'm ' + seconds + 's';
t = window.setTimeout(() => {
poll();
}, 1000);
}
//remove from list when expired
if (countdown <= 0) {
storage.remove(file.fileId);
$(expiry).parents('tr').remove();
window.clearTimeout(t);
toggleHeader();
}
}
// create popup
popupDiv.classList.add('popup');
$popupText.html(
'<span class="del-file">Delete</span><span class="nvm" > Nevermind</span>'
);
const $popupMessage = $('<div>', { class: 'popup-message' });
$popupMessage.attr('data-l10n-id', 'deletePopupText');
const $popupDelSpan = $('<span>', { class: 'popup-yes' });
$popupDelSpan.attr('data-l10n-id', 'deletePopupYes');
const $popupNvmSpan = $('<span>', { class: 'popup-no' });
$popupNvmSpan.attr('data-l10n-id', 'deletePopupCancel');
// delete file
$popupText.find('.del-file').click(e => {
FileSender.delete(file.fileId, file.deleteToken).then(() => {
$(e.target).parents('tr').remove();
localStorage.removeItem(file.fileId);
});
});
$popupText.html([$popupMessage, $popupDelSpan, $popupNvmSpan]);
// add data cells to table row
row.appendChild(name);
$(link).append($copyIcon);
row.appendChild(link);
row.appendChild(expiry);
popupDiv.appendChild(btn);
$(popupDiv).append($popupText);
$(del).append($delIcon);
del.appendChild(popupDiv);
row.appendChild(del);
$('tbody').append(row); //add row to table
const unexpiredFiles = storage.numFiles;
// delete file
$popupText.find('.popup-yes').click(e => {
FileSender.delete(file.fileId, file.deleteToken).then(() => {
$(e.target).parents('tr').remove();
const timeToExpiry =
ONE_DAY_IN_MS - (Date.now() - file.creationDate.getTime());
// record upload-deleted from file list
sendEvent('sender', 'upload-deleted', {
cm1: file.size,
cm2: file.totalTime,
cm3: file.uploadSpeed,
cm4: timeToExpiry,
cm5: storage.totalUploads,
cm6: unexpiredFiles,
cm7: storage.totalDownloads,
cd1: file.typeOfUpload,
cd4: 'upload-list'
}).then(() => {
storage.remove(file.fileId);
});
toggleHeader();
});
});
document.getElementById('delete-file').onclick = () => {
FileSender.delete(file.fileId, file.deleteToken).then(() => {
const timeToExpiry =
ONE_DAY_IN_MS - (Date.now() - file.creationDate.getTime());
// record upload-deleted from success screen
sendEvent('sender', 'upload-deleted', {
cm1: file.size,
cm2: file.totalTime,
cm3: file.uploadSpeed,
cm4: timeToExpiry,
cm5: storage.totalUploads,
cm6: unexpiredFiles,
cm7: storage.totalDownloads,
cd1: file.typeOfUpload,
cd4: 'success-screen'
}).then(() => {
storage.remove(file.fileId);
location.reload();
});
});
};
// show popup
del.addEventListener('click', toggleShow);
$delIcon.click(function() {
$popupText.addClass('show');
$popupText.focus();
});
// hide popup
$popupText.find('.nvm').click(function(e) {
$popupText.find('.popup-no').click(function(e) {
e.stopPropagation();
toggleShow();
$popupText.removeClass('show');
});
$popupText.click(function(e) {
e.stopPropagation();
});
//close when popup loses focus
$popupText.blur(() => {
$popupText.removeClass('show');
});
$('tbody').append(row); //add row to table
function toggleShow() {
$popupText.toggleClass('show');
toggleHeader();
}
function toggleHeader() {
//hide table header if empty list
if (document.querySelector('tbody').childNodes.length === 1) {
$('#file-list').attr('hidden', true);
} else {
$('#file-list').removeAttr('hidden');
}
}
});

View File

@@ -1,4 +1,4 @@
function ivToStr(iv) {
function arrayToHex(iv) {
let hexStr = '';
for (const i in iv) {
if (iv[i] < 16) {
@@ -7,12 +7,11 @@ function ivToStr(iv) {
hexStr += iv[i].toString(16);
}
}
window.hexStr = hexStr;
return hexStr;
}
function strToIv(str) {
const iv = new Uint8Array(16);
function hexToArray(str) {
const iv = new Uint8Array(str.length / 2);
for (let i = 0; i < str.length; i += 2) {
iv[i / 2] = parseInt(str.charAt(i) + str.charAt(i + 1), 16);
}
@@ -32,8 +31,93 @@ function notify(str) {
}
}
function gcmCompliant() {
try {
return window.crypto.subtle
.generateKey(
{
name: 'AES-GCM',
length: 128
},
true,
['encrypt', 'decrypt']
)
.then(key => {
return window.crypto.subtle
.encrypt(
{
name: 'AES-GCM',
iv: window.crypto.getRandomValues(new Uint8Array(12)),
additionalData: window.crypto.getRandomValues(new Uint8Array(6)),
tagLength: 128
},
key,
new ArrayBuffer(8)
)
.then(() => {
return Promise.resolve();
})
.catch(err => {
return Promise.reject();
});
})
.catch(err => {
return Promise.reject();
});
} catch (err) {
return Promise.reject();
}
}
function findMetric(href) {
switch (href) {
case 'https://www.mozilla.org/':
return 'mozilla';
case 'https://www.mozilla.org/about/legal':
return 'legal';
case 'https://testpilot.firefox.com/about':
return 'about';
case 'https://testpilot.firefox.com/privacy':
return 'privacy';
case 'https://testpilot.firefox.com/terms':
return 'terms';
case 'https://www.mozilla.org/en-US/privacy/websites/#cookies':
return 'cookies';
case 'https://github.com/mozilla/send':
return 'github';
case 'https://twitter.com/FxTestPilot':
return 'twitter';
case 'https://www.mozilla.org/firefox/new/?scene=2':
return 'download-firefox';
default:
return 'other';
}
}
function isFile(id) {
return ![
'referrer',
'totalDownloads',
'totalUploads',
'testpilot_ga__cid'
].includes(id);
}
function sendEvent() {
return window.analytics.sendEvent
.apply(window.analytics, arguments)
.catch(() => 0);
}
const ONE_DAY_IN_MS = 86400000;
module.exports = {
ivToStr,
strToIv,
notify
arrayToHex,
hexToArray,
notify,
gcmCompliant,
findMetric,
isFile,
sendEvent,
ONE_DAY_IN_MS
};

12
l10n.toml Normal file
View File

@@ -0,0 +1,12 @@
# 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/.
basepath = "."
[env]
l = "{l10n_base}/public/locales/{locale}/"
[[paths]]
reference = "public/locales/en-US/**"
l10n = "{l}**"

3885
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,56 +1,66 @@
{
"name": "portal-alpha",
"name": "firefox-send",
"description": "File Sharing Experiment",
"version": "0.1.2",
"version": "0.2.1",
"author": "Mozilla (https://mozilla.org)",
"dependencies": {
"aws-sdk": "^2.62.0",
"aws-sdk": "^2.89.0",
"body-parser": "^1.17.2",
"bytes": "^2.5.0",
"connect-busboy": "0.0.2",
"convict": "^3.0.0",
"cross-env": "^5.0.1",
"express": "^4.15.3",
"express-handlebars": "^3.0.0",
"helmet": "^3.6.1",
"jquery": "^3.2.1",
"helmet": "^3.8.0",
"mozlog": "^2.1.1",
"raven": "^2.1.0",
"raven-js": "^3.16.0",
"redis": "^2.7.1",
"uglify-es": "3.0.19"
"redis": "^2.7.1"
},
"devDependencies": {
"browserify": "^14.4.0",
"eslint": "^4.0.0",
"eslint": "^4.3.0",
"eslint-plugin-mocha": "^4.11.0",
"eslint-plugin-node": "^5.0.0",
"eslint-plugin-node": "^5.1.1",
"eslint-plugin-security": "^1.4.0",
"git-rev-sync": "^1.9.1",
"jquery": "^3.2.1",
"jquery-circle-progress": "^1.2.2",
"l20n": "^5.0.0",
"mocha": "^3.4.2",
"npm-run-all": "^4.0.2",
"prettier": "^1.4.4",
"prettier": "^1.5.3",
"proxyquire": "^1.8.0",
"sinon": "^2.3.5",
"stylelint": "^7.11.0",
"raven-js": "^3.17.0",
"selenium-webdriver": "^3.5.0",
"sinon": "^2.3.8",
"stylelint": "^7.13.0",
"stylelint-config-standard": "^16.0.0",
"watchify": "^3.9.0"
"supertest": "^3.0.0",
"testpilot-ga": "^0.3.0",
"uglifyify": "^4.0.3"
},
"engines": {
"node": ">=8.0.0"
},
"homepage": "https://github.com/mozilla/something-awesome/",
"homepage": "https://github.com/mozilla/send/",
"license": "MPL-2.0",
"repository": "mozilla/something-awesome",
"repository": "mozilla/send",
"availableLanguages": ["en-US"],
"scripts": {
"predocker": "browserify frontend/src/main.js | uglifyjs > public/bundle.js && npm run version",
"dev": "npm run version && watchify frontend/src/main.js -o public/bundle.js -d | node server/portal_server",
"format": "prettier '{frontend/src/,scripts/,server/,test/}*.js' 'public/*.css' --single-quote --write",
"build": "npm-run-all build:*",
"build:upload": "browserify frontend/src/upload.js -g uglifyify -o public/upload.js",
"build:download": "browserify frontend/src/download.js -g uglifyify -o public/download.js",
"build:version": "node scripts/version",
"build:l10n": "cp node_modules/l20n/dist/web/l20n.min.js public",
"dev": "npm run build && npm start",
"format": "prettier '{frontend/src/,scripts/,server/,test/**/}*.js' 'public/*.css' --single-quote --write",
"lint": "npm-run-all lint:*",
"lint:css": "stylelint 'public/*.css'",
"lint:js": "eslint .",
"start": "node server/portal_server",
"test": "mocha",
"version": "node scripts/version"
"start": "node server/server",
"test": "npm-run-all test:*",
"test:unit": "mocha test/unit",
"test:server": "mocha test/server",
"test:browser": "browserify test/frontend/frontend.bundle.js -o test/frontend/bundle.js -d && node test/frontend/driver.js"
}
}

27
public/contribute.json Normal file
View File

@@ -0,0 +1,27 @@
{
"name": "firefox-send",
"description": "File Sharing Experiment",
"repository": {
"url": "https://github.com/mozilla/send/",
"license": "MPL-2.0"
},
"participate": {
"home": "https://github.com/mozilla/send/blob/master/README.md",
"docs": "https://github.com/mozilla/send/blob/master/README.md"
},
"bugs": {
"list": "https://github.com/mozilla/send/issues",
"report": "https://github.com/mozilla/send/issues/new"
},
"urls": {
"prod": "https://send.firefox.com/",
"stage": "https://send.stage.mozaws.net/",
"dev": "https://send.dev.mozaws.net/"
},
"keywords": [
"JavaScript",
"jQuery",
"Node",
"Redis"
]
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,95 @@
// Firefox Send is a brand name and should not be localized.
title = Firefox Send
siteSubtitle = Web-Experiment
siteFeedback = Feedback
uploadPageHeader = Privates, verschlüsseltes Austauschen von Dateien
uploadPageExplainer = Senden Sie Dateien über einen sicheren, privaten und verschlüsselten Link, der automatisch abläuft, damit Ihre Daten nicht für immer im Internet bleiben.
uploadPageLearnMore = Mehr erfahren
uploadPageDropMessage = Ziehen Sie eine Datei zum Hochladen hierher
uploadPageSizeMessage = Dateien unter 1 GB sorgen für erhöhte Zuverlässigkeit des Betriebs
uploadPageBrowseButton = Wählen Sie eine Datei auf Ihrem Computer aus
.title = Wählen Sie eine Datei auf Ihrem Computer aus
uploadPageMultipleFilesAlert = Hochladen mehrerer Dateien oder eines Ordners wird derzeit nicht unterstützt.
uploadPageBrowseButtonTitle = Datei hochladen
uploadingPageHeader = Ihre Datei wird hochgeladen
importingFile = Wird importiert…
verifyingFile = Wird überprüft…
encryptingFile = Wird verschlüsselt
decryptingFile = Wird entschlüsselt...
notifyUploadDone = Ihr Upload ist abgeschlossen.
uploadingPageMessage = Sobald Ihre Datei hochgeladen wird, können Sie die Optionen zum Ablaufdatum auswählen.
uploadingPageCancel = Hochladen abbrechen
.title = Hochladen abbrechen
uploadCancelNotification = Ihr Upload wurde abgebrochen.
uploadingPageLargeFileMessage = Diese Datei ist groß, sodass das hochladen einige Zeit dauern könnte. Haben Sie Geduld!
uploadingFileNotification = Mich benachrichtigen, wenn der Upload abgeschlossen ist.
uploadSuccessConfirmHeader = Bereit zum Senden
uploadSvgAlt
.alt = Hochladen
uploadSuccessTimingHeader = Der Link zu Ihrer Datei läuft nach einem Download oder in 24 Stunden ab.
copyUrlFormLabelWithName = Kopieren und teilen Sie den Link, um Ihre Datei zu senden: { $filename }
// Note: Title text for button should be the same.
copyUrlFormButton = In Zwischenablage kopieren
.title = In Zwischenablage kopieren
copiedUrl = Kopiert!
// Note: Title text for button should be the same.
deleteFileButton = Datei löschen
// Note: Title text for button should be the same.
sendAnotherFileLink = Eine weitere Datei senden
.title = Eine weitere Datei senden
// Alternative text used on the download link/button (indicates an action).
downloadAltText
.alt = Herunterladen
downloadFileName = { $filename } herunterladen
downloadFileSize = ({ $size })
// Firefox Send is a brand name and should not be localized.
downloadMessage = Ihr Freund schickt Ihnen eine Datei mit Firefox Send, einem Dienst, mit dem Sie Dateien über einen sicheren, privaten und verschlüsselten Link teilen können, der automatisch abläuft, damit Ihre Daten nicht für immer im Internet bleiben.
// Text and title used on the download link/button (indicates an action).
downloadButtonLabel = Herunterladen
.title = Herunterladen
downloadNotification = Der Download wurde abgeschlossen.
downloadFinish = Download abgeschlossen
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
sendYourFilesLink = Firefox Send ausprobieren
.title = Firefox Send ausprobieren
downloadingPageProgress = { $filename } ({ $size }) wird heruntergeladen
downloadingPageMessage = Bitte lassen Sie diesen Tab geöffnet, während Ihre Datei heruntergeladen und entschlüsselt wird.
errorAltText
.alt = Fehler beim Hochladen
errorPageHeader = Ein Fehler ist aufgetreten!
errorPageMessage = Beim Hochladen der Datei ist ein Fehler aufgetreten.
errorPageLink = Eine weitere Datei senden
fileTooBig = Die Datei ist zu groß zum Hochladen. Sie sollte maximal { $size } groß sein.
linkExpiredAlt
.alt = Link abgelaufen
expiredPageHeader = Dieser Link ist abgelaufen oder hat nie existiert!
notSupportedHeader = Ihr Browser wird nicht unterstützt.
// Firefox Send is a brand name and should not be localized.
notSupportedDetail = Leider unterstützt dieser Browser die Web-Technologie nicht, auf der Firefox Send basiert. Sie benötigen einen anderen Browser. Wir empfehlen Firefox!
downloadFirefoxButtonSub = Kostenloser Download
uploadedFile = Datei
copyFileList = Adresse kopieren
// expiryFileList is used as a column header
expiryFileList = Läuft ab in
deleteFileList = Löschen
nevermindButton = Egal
deleteButtonHover
.title = Löschen
copyUrlHover
.title = Adresse kopieren
legalHeader = Nutzungsbedingungen und Datenschutz
legalNoticeTestPilot = Firefox Send ist aktuell ein Test-Pilot-Experiment und unterliegt den <a>Nutzungsbedingungen</a> und dem <a>Datenschutzhinweis</a> von Test Pilot. Mehr über diese Experiment und die Daten, die es sammelt, erfahren Sie <a>hier</a>.
legalNoticeMozilla = Die Nutzung der Website von Firefox Send unterliegt außerdem Mozillas <a>Datenschutzhinweis für Websites</a> und <a>Nutzungsbedingungen für Websites</a>.
deletePopupText = Diese Datei löschen?
deletePopupYes = Ja
deletePopupCancel = Abbrechen
deleteButtonHover
.title = Löschen
copyUrlHover
.title = Adresse kopieren
footerLinkLegal = Rechtliches
// Test Pilot is a proper name and should not be localized.
footerLinkAbout = Über Test Pilot
footerLinkPrivacy = Datenschutz
footerLinkTerms = Nutzungsbedingungen
footerLinkCookies = Cookies

View File

@@ -0,0 +1,96 @@
// Firefox Send is a brand name and should not be localized.
title = Firefox Send
siteSubtitle = web experiment
siteFeedback = Feedback
uploadPageHeader = Private, Encrypted File Sharing
uploadPageExplainer = Send files through a safe, private, and encrypted link that automatically expires to ensure your stuff does not remain online forever.
uploadPageLearnMore = Learn more
uploadPageDropMessage = Drop your file here to start uploading
uploadPageSizeMessage = For the most reliable operation, its best to keep your file under 1GB
uploadPageBrowseButton = Select a file on your computer
.title = Select a file on your computer
uploadPageMultipleFilesAlert = Uploading multiple files or a folder is currently not supported.
uploadPageBrowseButtonTitle = Upload file
uploadingPageHeader = Uploading Your File
importingFile = Importing…
verifyingFile = Verifying…
encryptingFile = Encrypting…
decryptingFile = Decrypting…
notifyUploadDone = Your upload has finished.
uploadingPageMessage = Once your file uploads you will be able to set expiry options.
uploadingPageCancel = Cancel upload
.title = Cancel upload
uploadCancelNotification = Your upload was cancelled.
uploadingPageLargeFileMessage = This file is large and may take a while to upload. Sit tight!
uploadingFileNotification = Notify me when the upload is complete.
uploadSuccessConfirmHeader = Ready to Send
uploadSvgAlt
.alt = Upload
uploadSuccessTimingHeader = The link to your file will expire after 1 download or in 24 hours.
copyUrlFormLabelWithName = Copy and share the link to send your file: { $filename }
// Note: Title text for button should be the same.
copyUrlFormButton = Copy to clipboard
.title = Copy to clipboard
copiedUrl = Copied!
// Note: Title text for button should be the same.
deleteFileButton = Delete file
.title = Delete file
// Note: Title text for button should be the same.
sendAnotherFileLink = Send another file
.title = Send another file
// Alternative text used on the download link/button (indicates an action).
downloadAltText
.alt = Download
downloadFileName = Download { $filename }
downloadFileSize = ({ $size })
// Firefox Send is a brand name and should not be localized.
downloadMessage = Your friend is sending you a file with Firefox Send, a service that allows you to share files with a safe, private, and encrypted link that automatically expires to ensure your stuff does not remain online forever.
// Text and title used on the download link/button (indicates an action).
downloadButtonLabel = Download
.title = Download
downloadNotification = Your download has completed.
downloadFinish = Download Complete
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
sendYourFilesLink = Try Firefox Send
.title = Try Firefox Send
downloadingPageProgress = Downloading { $filename } ({ $size })
downloadingPageMessage = Please leave this tab open while we fetch your file and decrypt it.
errorAltText
.alt = Upload error
errorPageHeader = Something went wrong!
errorPageMessage = There has been an error uploading the file.
errorPageLink = Send another file
fileTooBig = That file is too big to upload. It should be less than { $size }.
linkExpiredAlt
.alt = Link expired
expiredPageHeader = This link has expired or never existed in the first place!
notSupportedHeader = Your browser is not supported.
// Firefox Send is a brand name and should not be localized.
notSupportedDetail = Unfortunately this browser does not support the web technology that powers Firefox Send. Youll need to try another browser. We recommend Firefox!
downloadFirefoxButtonSub = Free Download
uploadedFile = File
copyFileList = Copy URL
// expiryFileList is used as a column header
expiryFileList = Expires In
deleteFileList = Delete
nevermindButton = Never mind
deleteButtonHover
.title = Delete
copyUrlHover
.title = Copy URL
legalHeader = Terms & Privacy
legalNoticeTestPilot = Firefox Send is currently a Test Pilot experiment, and subject to the Test Pilot <a>Terms of Service</a> and <a>Privacy Notice</a>. You can learn more about this experiment and its data collection <a>here</a>.
legalNoticeMozilla = Use of the Firefox Send website is also subject to Mozillas <a>Websites Privacy Notice</a> and <a>Websites Terms of Use</a>.
deletePopupText = Delete this file?
deletePopupYes = Yes
deletePopupCancel = Cancel
deleteButtonHover
.title = Delete
copyUrlHover
.title = Copy URL
footerLinkLegal = Legal
// Test Pilot is a proper name and should not be localized.
footerLinkAbout = About Test Pilot
footerLinkPrivacy = Privacy
footerLinkTerms = Terms
footerLinkCookies = Cookies

View File

@@ -0,0 +1,88 @@
// Firefox Send is a brand name and should not be localized.
title = Firefox Send
siteSubtitle = webeksperiment
siteFeedback = Komentar
uploadPageHeader = Priwatne, zaklučowane dźělenje datajow
uploadPageExplainer = Pósćelće dataje přez wěstny, priwatny a zaklučowany wotkaz, kotryž awtomatisće spadnje, zo njebychu waše daty na přeco online wostali.
uploadPageLearnMore = Dalše informacije
uploadPageDropMessage = Ćehńće swoju dataju sem, zo byšće ju nahrał
uploadPageSizeMessage = Wužiwajće najlěpje dataje, kotrež su mjeńše hač 1 GB za lěpšu spušćomnosć.
uploadPageBrowseButton = Wubjerće dataju na swojim ličaku
.title = Wubjerće dataju na swojim ličaku
uploadPageMultipleFilesAlert = Nahrawanje wjacorych datajow abo rjadowaka so tuchwilu njepodpěruje.
uploadPageBrowseButtonTitle = Dataju nahrać
uploadingPageHeader = Waša dataja so nahrawa
importingFile = Importuje so...
verifyingFile = Přepruwuje so...
encryptingFile = Zaklučuje so...
decryptingFile = Dešifruje so...
notifyUploadDone = Waše nahraće je dokónčene.
uploadingPageMessage = Hdyž do waša dataja nahrawa, móžeće nastajenja spadnjenja postajić.
uploadingPageCancel = Nahraće přetorhnyć
.title = Nahraće přetorhnyć
uploadCancelNotification = Waše nahraće je so přetorhnyło.
uploadingFileNotification = Zdźělić, hdyž nahraće je dokónčene.
uploadSuccessConfirmHeader = Hotowy za słanje
uploadSvgAlt
.alt = Nahrać
uploadSuccessTimingHeader = Wotkaz k wašej dataji po 1 sćehnjenju abo 24 hodźinach spadnje.
copyUrlFormLabelWithName = Kopěrujće a dźělće wotkaz, zo byšće swoju dataju pósłał: { $filename }
// Note: Title text for button should be the same.
copyUrlFormButton = Do mjezyskłada kopěrować
.title = Do mjezyskłada kopěrować
copiedUrl = Kopěrowany!
// Note: Title text for button should be the same.
deleteFileButton = Dataju zhašeć
.title = Dataju zhašeć
// Note: Title text for button should be the same.
sendAnotherFileLink = Druhu dataju pósłać
.title = Druhu dataju pósłać
// Alternative text used on the download link/button (indicates an action).
downloadAltText
.alt = Sćahnyć
downloadFileName = { $filename } sćahnyć
downloadFileSize = ({ $size })
// Text and title used on the download link/button (indicates an action).
downloadButtonLabel = Sćahnyć
.title = Sćahnyć
downloadNotification = Waše sćehnjenje je dokónčene.
downloadFinish = Sćehnjenje dokónčene
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
sendYourFilesLink = Firefox Send wupruwować
.title = Firefox Send wupruwować
downloadingPageProgress = { $filename } ({ $size }) so sćahuje
errorAltText
.alt = Nahrawanski zmylk
errorPageHeader = Něšto je so nimokuliło!
errorPageMessage = Při nahrawanju dataje je zmylk wustupił.
errorPageLink = Druhu dataju pósłać
fileTooBig = Tuta dataja je přewulka za nahraće. Měła mjeńša hač { $size } być.
linkExpiredAlt
.alt = Wotkaz je spadnjeny
expiredPageHeader = Tutón wotkaz je spadnjeny abo njeje ženje eksistował.
notSupportedHeader = Waš wobhladowak so njepodpěruje.
downloadFirefoxButtonSub = Darmotne sćehnjenje
uploadedFile = Dataja
copyFileList = URL kopěrować
// expiryFileList is used as a column header
expiryFileList = Spadnje za
deleteFileList = Zhašeć
nevermindButton = Wšojedne
deleteButtonHover
.title = Zhašeć
copyUrlHover
.title = URL kopěrować
legalHeader = Wuměnjenja a priwatnosć
deletePopupText = Tutu dataju zhašeć?
deletePopupYes = Haj
deletePopupCancel = Přetorhnyć
deleteButtonHover
.title = Zhašeć
copyUrlHover
.title = URL kopěrować
footerLinkLegal = Prawniske
// Test Pilot is a proper name and should not be localized.
footerLinkAbout = Wo Test Pilot
footerLinkPrivacy = Priwatnosć
footerLinkTerms = Wuměnjenja
footerLinkCookies = Placki

View File

@@ -0,0 +1,96 @@
// Firefox Send is a brand name and should not be localized.
title = Firefox Send
siteSubtitle = esperimento web
siteFeedback = Feedback
uploadPageHeader = Condivisione di file riservata e cifrata
uploadPageExplainer = Invia file in modo sicuro, riservato e crittato, con un link che scade automaticamente per garantire che i tuoi dati non rimangano online per sempre.
uploadPageLearnMore = Ulteriori informazioni
uploadPageDropMessage = Trascina qui i tuoi file per caricarli
uploadPageSizeMessage = Per un risultato affidabile è consigliabile mantenere il file al di sotto di 1 GB
uploadPageBrowseButton = Seleziona un file sul computer
.title = Seleziona un file sul computer
uploadPageMultipleFilesAlert = Il caricamento di più file o cartelle non è attualmente supportato.
uploadPageBrowseButtonTitle = Carica file
uploadingPageHeader = Caricamento file
importingFile = Importazione in corso…
verifyingFile = Verifica in corso…
encryptingFile = Crittografia in corso...
decryptingFile = Decrittografia in corso...
notifyUploadDone = Il caricamento è stato completato.
uploadingPageMessage = È possibile impostare le opzioni di scadenza una volta caricato il file.
uploadingPageCancel = Annulla caricamento
.title = Annulla caricamento
uploadCancelNotification = Il caricamento è stato annullato.
uploadingPageLargeFileMessage = Si tratta di un file di grandi dimensioni e potrebbe volerci un po' di tempo.
uploadingFileNotification = Invia una notifica quando il caricamento è completato.
uploadSuccessConfirmHeader = Pronto per l'invio
uploadSvgAlt
.alt = Carica
uploadSuccessTimingHeader = Il link al file scadrà dopo 1 download o in 24 ore.
copyUrlFormLabelWithName = Copia e condividi il link per inviare il tuo file: { $filename }
// Note: Title text for button should be the same.
copyUrlFormButton = Copia negli appunti
.title = Copia negli appunti
copiedUrl = Copiato
// Note: Title text for button should be the same.
deleteFileButton = Elimina file
.title = Elimina file
// Note: Title text for button should be the same.
sendAnotherFileLink = Invia un altro file
.title = Invia un altro file
// Alternative text used on the download link/button (indicates an action).
downloadAltText
.alt = Scarica
downloadFileName = Scarica { $filename }
downloadFileSize = ({ $size })
// Firefox Send is a brand name and should not be localized.
downloadMessage = Qualcuno ti ha inviato un file con Firefox Send: è un servizio che permette di condividere file in modo sicuro, riservato e crittato, attraverso un link che scade automaticamente, garantendo che i tuoi dati non rimangano online per sempre.
// Text and title used on the download link/button (indicates an action).
downloadButtonLabel = Scarica
.title = Scarica
downloadNotification = Download completato.
downloadFinish = Download completato
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
sendYourFilesLink = Prova Firefox Send
.title = Prova Firefox Send
downloadingPageProgress = Download in corso di { $filename } ({ $size })
downloadingPageMessage = Mantieni aperta questa scheda mentre il file viene scaricato e decrittato.
errorAltText
.alt = Errore durante il caricamento
errorPageHeader = Si è verificato un errore.
errorPageMessage = Si è verificato un errore durante il caricamento del file.
errorPageLink = Invia un altro file
fileTooBig = Le dimensioni di questo file sono eccessive. Dovrebbe essere inferiore a { $size }.
linkExpiredAlt
.alt = Link scaduto
expiredPageHeader = Questo link è scaduto oppure non è mai esistito.
notSupportedHeader = Il browser in uso non è supportato.
// Firefox Send is a brand name and should not be localized.
notSupportedDetail = Sfortunatamente questo browser non supporta le tecnologie web alla base di Firefox Send. Devi utilizzare un altro browser. Ti consigliamo Firefox!
downloadFirefoxButtonSub = Download gratuito
uploadedFile = File
copyFileList = Copia indirizzo
// expiryFileList is used as a column header
expiryFileList = Scade in
deleteFileList = Elimina
nevermindButton = No, grazie
deleteButtonHover
.title = Elimina
copyUrlHover
.title = Copia indirizzo
legalHeader = Termini di utilizzo e privacy
legalNoticeTestPilot = Firefox Send è attualmente un esperimento di Test Pilot ed è soggetto alle <a>Condizioni di utilizzo</a> e all<a>Informativa sulla privacy</a> di Test Pilot. Per ulteriori informazioni su questo esperimento e i dati raccolti, consulta <a>questa pagina<a>.
legalNoticeMozilla = Lutilizzo del sito di Firefox Send è soggetto all<a>Informativa sulla privacy</a> e le <a>Condizioni di utilizzo</a> dei siti web Mozilla.
deletePopupText = Eliminare questo file?
deletePopupYes = Sì
deletePopupCancel = Annulla
deleteButtonHover
.title = Elimina
copyUrlHover
.title = Copia indirizzo
footerLinkLegal = Note legali
// Test Pilot is a proper name and should not be localized.
footerLinkAbout = Informazioni su Test Pilot
footerLinkPrivacy = Privacy
footerLinkTerms = Condizioni di utilizzo
footerLinkCookies = Cookie

View File

@@ -0,0 +1,96 @@
// Firefox Send is a brand name and should not be localized.
title = Firefox Send
siteSubtitle = ウェブ実験
siteFeedback = フィードバック
uploadPageHeader = プライベートな暗号化されたファイル共有
uploadPageExplainer = 安全で、プライベートで、暗号化されたリンクを通じてファイルを送信。あなたのものがずっとオンラインに残らないよう、リンクは自動的に期限切れとなります。
uploadPageLearnMore = 詳しくはこちら
uploadPageDropMessage = ここにファイルをドロップしてアップロードを開始
uploadPageSizeMessage = 確実に処理できるよう、ファイルサイズは 1 GB 以下にすることを推奨します。
uploadPageBrowseButton = コンピューター上のファイルを選択
.title = コンピューター上のファイルを選択
uploadPageMultipleFilesAlert = 今のところ複数ファイルやフォルダーのアップロードには対応していません。
uploadPageBrowseButtonTitle = ファイルをアップロード
uploadingPageHeader = ファイルをアップロードしています
importingFile = インポート中...
verifyingFile = 検証中...
encryptingFile = 暗号化中...
decryptingFile = 復号化中...
notifyUploadDone = アップロードが完了しました。
uploadingPageMessage = ファイルのアップロード完了後に期限を設定できます。
uploadingPageCancel = アップロードを中止
.title = アップロードを中止
uploadCancelNotification = アップロードは中止されました。
uploadingPageLargeFileMessage = このファイルは大きいのでアップロードに多少時間が掛かるかもしれません。しばらくお待ちください。
uploadingFileNotification = アップロード完了時に通知を受け取る
uploadSuccessConfirmHeader = 送信準備完了
uploadSvgAlt
.alt = アップロード
uploadSuccessTimingHeader = ファイルへのリンクは、1 回ダウンロードされた後、もしくは 24 時間以内に期限切れとなります。
copyUrlFormLabelWithName = ファイルを送信するにはこのリンクをコピー、共有してください: { $filename }
// Note: Title text for button should be the same.
copyUrlFormButton = クリップボードへコピー
.title = クリップボードへコピー
copiedUrl = コピー完了!
// Note: Title text for button should be the same.
deleteFileButton = ファイルを削除
.title = ファイルを削除
// Note: Title text for button should be the same.
sendAnotherFileLink = 他のファイルを送信
.title = 他のファイルを送信
// Alternative text used on the download link/button (indicates an action).
downloadAltText
.alt = ダウンロード
downloadFileName = { $filename } をダウンロード
downloadFileSize = ({ $size })
// Firefox Send is a brand name and should not be localized.
downloadMessage = あなたの友人が Firefox Send を通じてファイルを送ってきています。これは、安全で、プライベートで、暗号化されたリンクを通じてファイルを共有できるサービスです。あなたのものがずっとオンラインに残らないよう、リンクは自動的に期限切れとなります。
// Text and title used on the download link/button (indicates an action).
downloadButtonLabel = ダウンロード
.title = ダウンロード
downloadNotification = ダウンロードが完了しました。
downloadFinish = ダウンロード完了
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
sendYourFilesLink = Firefox Send を試す
.title = Firefox Send を試す
downloadingPageProgress = { $filename } ({ $size }) をダウンロードしています
downloadingPageMessage = ファイルの取得と暗号化が完了するまでこのタブを開いたままにしておいてください。
errorAltText
.alt = アップロードエラー
errorPageHeader = 何か問題が発生しました。
errorPageMessage = ファイルのアップロード中に問題が発生しました。
errorPageLink = 他のファイルを送信
fileTooBig = このファイルは大きすぎるためアップロードできません。上限は { $size } です。
linkExpiredAlt
.alt = リンク期限切れ
expiredPageHeader = このリンクは期限切れとなったか元々存在していません。
notSupportedHeader = お使いのブラウザーには対応していません。
// Firefox Send is a brand name and should not be localized.
notSupportedDetail = 残念ながらこのブラウザーは Firefox Send が活用しているウェブ技術に対応していません。他のブラウザーで試してください。私たちは Firefox をお勧めします!
downloadFirefoxButtonSub = 無料ダウンロード
uploadedFile = ファイル
copyFileList = URL をコピー
// expiryFileList is used as a column header
expiryFileList = 有効期限:
deleteFileList = 削除
nevermindButton = 気にしないでください
deleteButtonHover
.title = 削除
copyUrlHover
.title = URL をコピー
legalHeader = 利用規約とプライバシー
legalNoticeTestPilot = Firefox Send は今のところ Test Pilot 実験のひとつであり、Test Pilot <a>利用規約</a> と <a>プライバシー通知</a> が適用されます。この実験とそのデータ収集に関する詳細は <a>こちら</a> をご覧ください。
legalNoticeMozilla = Firefox Send のサイトの利用には、Mozilla の <a>ウェブサイトプライバシー通知</a> と <a>ウェブサイト利用規約</a> も適用されます。
deletePopupText = このファイルを削除しますか?
deletePopupYes = はい
deletePopupCancel = キャンセル
deleteButtonHover
.title = 削除
copyUrlHover
.title = URL をコピー
footerLinkLegal = 法的情報
// Test Pilot is a proper name and should not be localized.
footerLinkAbout = Test Pilot について
footerLinkPrivacy = プライバシー
footerLinkTerms = 利用規約
footerLinkCookies = Cookie

View File

@@ -0,0 +1,11 @@
// Firefox Send is a brand name and should not be localized.
title = Firefox Send
siteSubtitle = experimen web
siteFeedback = Maklum balas
uploadPageHeader = Peribadi, Perkongsian Fail Dienkrip
uploadPageExplainer = Hantar fail melalui pautan yang selamat, peribadi dan dienkrip, yang akan luput secara automatik untuk memastikan fail anda itu tidak terus berada dalam talian selama-lamanya.
uploadPageLearnMore = Ketahui selanjutnya
uploadPageDropMessage = Letakkan fail anda di sini untuk mulakan memuat naik
uploadPageSizeMessage = Untuk operasi yang paling selamat, lebih baik pastikan fail anda itu kurang 1GB
uploadPageBrowseButton = Pilih fail dalam komputer anda
.title = Pilih fail dalam komputer anda

View File

@@ -0,0 +1,81 @@
// Firefox Send is a brand name and should not be localized.
title = Firefox Send
siteSubtitle = netteksperiment
siteFeedback = Tilbakemelding
uploadPageHeader = Privat, kryptert fildeling
uploadPageLearnMore = Les mer
uploadPageDropMessage = Slipp din fil her for å starte opplastingen
uploadPageSizeMessage = For den mest problemfrie bruken, er det best å holde filen under 1 GB
uploadPageBrowseButton = Velg en fil på din datamaskin
.title = Velg en fil på din datamaskin
uploadPageBrowseButtonTitle = Last opp fil
uploadingPageHeader = Laster opp din fil
importingFile = Importerer…
verifyingFile = Verifiserer...
encryptingFile = Krypterer...
decryptingFile = Dekrypterer...
uploadingPageCancel = Avbryt opplasting
.title = Avbryt opplasting
uploadCancelNotification = Din opplasting ble avbrutt
uploadSuccessConfirmHeader = Klar til å sende
uploadSvgAlt
.alt = Last opp
uploadSuccessTimingHeader = Lenken til filen din utløper etter 1 nedlasting eller om 24 timer.
// Note: Title text for button should be the same.
copyUrlFormButton = Kopier til utklippstavle
.title = Kopier til utklippstavle
copiedUrl = Kopiert!
// Note: Title text for button should be the same.
deleteFileButton = Slett fil
.title = Slett fil
// Note: Title text for button should be the same.
sendAnotherFileLink = Send en annen fil
.title = Send en annen fil
// Alternative text used on the download link/button (indicates an action).
downloadAltText
.alt = Last ned
downloadFileName = Last ned { $filename }
downloadFileSize = ({ $size })
// Text and title used on the download link/button (indicates an action).
downloadButtonLabel = Last ned
.title = Last ned
downloadNotification = Nedlastingen er fullført.
downloadFinish = Nedlastingen er fullført.
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
sendYourFilesLink = Prøv Firefox Send
.title = Prøv Firefox Send
downloadingPageProgress = Laster ned { $filename } ({ $size })
errorAltText
.alt = Opplastingsfeil
errorPageHeader = Det oppstod en feil.
errorPageLink = Send en annen fil
linkExpiredAlt
.alt = Lenke utløpt
notSupportedHeader = Din nettleser er ikke støttet.
// Firefox Send is a brand name and should not be localized.
notSupportedDetail = Dessverre støtter denne nettleseren ikke webteknologien som driver Firefox Send. Du må prøve en annen nettleser. Vi anbefaler Firefox!
downloadFirefoxButtonSub = Gratis nedlasting
uploadedFile = Fil
copyFileList = Kopier URL
// expiryFileList is used as a column header
expiryFileList = Utløper om
deleteFileList = Slett
nevermindButton = Glem det
deleteButtonHover
.title = Slett
copyUrlHover
.title = Kopier URL
legalHeader = Vilkår og personvern
deletePopupText = Slett denne filen?
deletePopupYes = Ja
deletePopupCancel = Avbryt
deleteButtonHover
.title = Slett
copyUrlHover
.title = Kopier URL
footerLinkLegal = Juridisk informasjon
// Test Pilot is a proper name and should not be localized.
footerLinkAbout = Om Test Pilot
footerLinkPrivacy = Personvern
footerLinkTerms = Vilkår
footerLinkCookies = Infokapsler

View File

@@ -0,0 +1,35 @@
// Firefox Send is a brand name and should not be localized.
title = Firefox Send
siteSubtitle = experimento web
siteFeedback = Opinião
uploadPageHeader = Compartilhamento de arquivos privados e criptografados
uploadPageExplainer = Envie arquivos por meio de um link seguro, privado e criptografado que expira automaticamente para garantir que as suas coisas não permaneçam on-line para sempre.
uploadPageLearnMore = Saiba mais
uploadPageSizeMessage = Para uma operação mais confiável, é melhor manter seu arquivo menor que 1GB
uploadPageBrowseButton = Selecione um arquivo em seu computador
.title = Selecione um arquivo em seu computador
importingFile = Importando…
verifyingFile = Verificando…
encryptingFile = Criptografando...
decryptingFile = Descriptografando...
uploadSuccessConfirmHeader = Pronto para enviar
uploadSuccessTimingHeader = O link para o seu arquivo expirará após 1 download ou em 24 horas.
copyUrlFormLabelWithName = Copie e compartilhe o link para enviar o seu arquivo: { $filename }
// Note: Title text for button should be the same.
copyUrlFormButton = Copiar para área de transferência
.title = Copiar para área de transferência
copiedUrl = Copiado!
uploadedFile = Arquivo
copyFileList = Copiar URL
deleteFileList = Excluir
deleteButtonHover
.title = Excluir
legalHeader = Termos e privacidade
deletePopupText = Excluir este arquivo
deletePopupYes = Sim
deletePopupCancel = Cancelar
deleteButtonHover
.title = Excluir
footerLinkPrivacy = Privacidade
footerLinkTerms = Termos
footerLinkCookies = Cookies

View File

@@ -0,0 +1,96 @@
// Firefox Send is a brand name and should not be localized.
title = Firefox Send
siteSubtitle = experiência web
siteFeedback = Feedback
uploadPageHeader = Partilha de ficheiros privada e encriptada
uploadPageExplainer = Envie ficheiros através de uma ligação segura, privada e encriptada que automaticamente expira para garantir que as suas coisas não fiquem online para sempre.
uploadPageLearnMore = Saber mais
uploadPageDropMessage = Largue o seu ficheiro aqui para começar a carregar
uploadPageSizeMessage = Para uma operação mais confiável, é melhor manter o seu ficheiro abaixo de 1GB
uploadPageBrowseButton = Selecione um ficheiro no seu computador
.title = Selecione um ficheiro no seu computador
uploadPageMultipleFilesAlert = Carregar múltiplos ficheiros ou uma pasta não é atualmente suportado.
uploadPageBrowseButtonTitle = Carregar ficheiro
uploadingPageHeader = A carregar o seu ficheiro
importingFile = A importar...
verifyingFile = A verificar...
encryptingFile = A encriptar...
decryptingFile = A desencriptar...
notifyUploadDone = O seu carregamento terminou.
uploadingPageMessage = Assim que o seu ficheiro carregar irá poder definir as opções de expiração.
uploadingPageCancel = Cancelar carregamento
.title = Cancelar carregamento
uploadCancelNotification = O seu carregamento foi cancelado.
uploadingPageLargeFileMessage = Este ficheiro é grande e pode demorar um pouco a carregar. Fique onde está!
uploadingFileNotification = Notificar-me quando o carregamento estiver completo.
uploadSuccessConfirmHeader = Pronto para enviar
uploadSvgAlt
.alt = Carregar
uploadSuccessTimingHeader = A ligação para o seu ficheiro irá expirar depois de 1 descarga ou em 24 horas.
copyUrlFormLabelWithName = Copie e partilhe a ligação para enviar o seu ficheiro: { $filename }
// Note: Title text for button should be the same.
copyUrlFormButton = Copiar para a área de transferência
.title = Copiar para a área de transferência
copiedUrl = Copiado!
// Note: Title text for button should be the same.
deleteFileButton = Apagar ficheiro
.title = Apagar ficheiro
// Note: Title text for button should be the same.
sendAnotherFileLink = Enviar outro ficheiro
.title = Enviar outro ficheiro
// Alternative text used on the download link/button (indicates an action).
downloadAltText
.alt = Descarregar
downloadFileName = Descarregar { $filename }
downloadFileSize = ({ $size })
// Firefox Send is a brand name and should not be localized.
downloadMessage = O seu amigo está a enviar-lhe um ficheiro com o Firefox Send, um serviço que lhe permite partilhar ficheiro com uma ligação segura, privada e encriptada que automaticamente expira para garantir que as suas coisas não fiquem online para sempre.
// Text and title used on the download link/button (indicates an action).
downloadButtonLabel = Descarregar
.title = Descarregar
downloadNotification = A sua descarga foi completada.
downloadFinish = Descarga completada
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
sendYourFilesLink = Tentar o Firefox Send
.title = Tentar o Firefox Send
downloadingPageProgress = A descarregar { $filename } ({ $size })
downloadingPageMessage = Por favor deixe este separador aberto enquanto obtemos o seu ficheiro e o desencriptamos.
errorAltText
.alt = Erro ao carregar
errorPageHeader = Algo correu mal.
errorPageMessage = Houve um erro ao carregar o ficheiro.
errorPageLink = Enviar outro ficheiro
fileTooBig = Esse ficheiro é muito grande para carregar. Deve ser menor do que { $size }.
linkExpiredAlt
.alt = Ligação expirada
expiredPageHeader = Esta ligação expirou ou nunca existiu em primeiro lugar!
notSupportedHeader = O seu navegador não é suportado.
// Firefox Send is a brand name and should not be localized.
notSupportedDetail = Infelizmente este navegador não suporta a tecnologia web que faz o Firefox Send funcionar. Irá precisar de tentar outro navegador. Nós recomendamos o Firefox!
downloadFirefoxButtonSub = Descarga gratuita
uploadedFile = Ficheiro
copyFileList = Copiar URL
// expiryFileList is used as a column header
expiryFileList = Expira em
deleteFileList = Apagar
nevermindButton = Esquecer
deleteButtonHover
.title = Apagar
copyUrlHover
.title = Copiar URL
legalHeader = Termos e privacidade
legalNoticeTestPilot = O Firefox Send é atualmente uma experiência do Test Pilot, e sujeita aos <a>Termos de serviço</a> e <a>Aviso de privacidade</a> do Test Pilot. Pode saber mais acerca desta experiência e a sua recolha de dados <a>aqui</a>.
legalNoticeMozilla = A utilização do website do Firefox Send está também sujeita ao <a>Aviso de privacidade dos websites</a> e <a>Termos de serviço dos websites</a> da Mozilla.
deletePopupText = Apagar este ficheiro?
deletePopupYes = Sim
deletePopupCancel = Cancelar
deleteButtonHover
.title = Apagar
copyUrlHover
.title = Copiar URL
footerLinkLegal = Legal
// Test Pilot is a proper name and should not be localized.
footerLinkAbout = Acerca do Test Pilot
footerLinkPrivacy = Privacidade
footerLinkTerms = Termos
footerLinkCookies = Cookies

View File

@@ -0,0 +1,96 @@
// Firefox Send is a brand name and should not be localized.
title = Firefox Send
siteSubtitle = webový experiment
siteFeedback = Spätná väzba
uploadPageHeader = Súkromné, šifrované zdieľanie súborov
uploadPageExplainer = Odosielajte súbory pomocou bezpečného, súkromného a šifrovaného odkazu, ktorého platnosť automaticky vyprší. Vďaka tomu máte istotu, že vaše súbory nezostanú na internete naveky.
uploadPageLearnMore = Ďalšie informácie
uploadPageDropMessage = Presunutím súboru sem začnete nahrávanie
uploadPageSizeMessage = Pre zaistenie čo najväčšej spoľahlivosti vám odporúčame nahrávať súbory menšie než 1GB.
uploadPageBrowseButton = Vyberte súbor vo vašom počítači
.title = Vyberte súbor vo vašom počítači
uploadPageMultipleFilesAlert = Nahrávanie viacerých súborov alebo priečinkov momentálne nie je podporované.
uploadPageBrowseButtonTitle = Nahrať súbor
uploadingPageHeader = Nahrávanie vášho súboru
importingFile = Importuje sa…
verifyingFile = Overuje sa…
encryptingFile = Šifruje sa...
decryptingFile = Dešifruje sa...
notifyUploadDone = Vaše nahrávanie sa dokončilo.
uploadingPageMessage = Po nahratí súboru budete môcť nastaviť vypršanie platnosti.
uploadingPageCancel = Zrušiť nahrávanie
.title = Zrušiť nahrávanie
uploadCancelNotification = Vaše nahrávanie bolo zrušené.
uploadingPageLargeFileMessage = Tento súbor je veľký. Nahrávanie tak môže chvíľu trvať.
uploadingFileNotification = Upozorniť ma na ukončenie nahrávania
uploadSuccessConfirmHeader = Pripravené na odoslanie
uploadSvgAlt
.alt = Nahrať
uploadSuccessTimingHeader = Platnosť odkazu vyprší po 1 prevzatí alebo po uplynutí 24 hodín.
copyUrlFormLabelWithName = Skopírovaním a zdieľaním odkazu odošlete váš súbor: { $filename }
// Note: Title text for button should be the same.
copyUrlFormButton = Kopírovať do schránky
.title = Kopírovať do schránky
copiedUrl = Skopírované!
// Note: Title text for button should be the same.
deleteFileButton = Odstrániť súbor
.title = Odstrániť súbor
// Note: Title text for button should be the same.
sendAnotherFileLink = Odoslať ďalší súbor
.title = Odoslať ďalší súbor
// Alternative text used on the download link/button (indicates an action).
downloadAltText
.alt = Prevziať
downloadFileName = Prevziať { $filename }
downloadFileSize = ({ $size })
// Firefox Send is a brand name and should not be localized.
downloadMessage = Váš priateľ vám odoslal súbor pomocou služby Firefox Sync - táto vám umožňuje zdieľať súbory pomocou bezpečného, súkromného a zašifrovaného odkazu, ktorého platnosť automaticky vyprší. Vďaka tomu máte istotu, že vaše súbory neostanú na internete naveky.
// Text and title used on the download link/button (indicates an action).
downloadButtonLabel = Prevziať
.title = Prevziať
downloadNotification = Vaše preberanie bolo dokončené.
downloadFinish = Preberanie bolo dokončené
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
sendYourFilesLink = Vyskúšajte Firefox Send
.title = Vyskúšajte Firefox Send
downloadingPageProgress = Preberá sa { $filename } ({ $size })
downloadingPageMessage = Prosím, ponechajte túto kartu otvorenú zatiaľ čo váš súbor prevezmeme a dešifrujeme.
errorAltText
.alt = Pri nahrávaní sa vyskytla chyba
errorPageHeader = Vyskytol sa problém.
errorPageMessage = Pri nahrávaní súboru nastala chyba.
errorPageLink = Odošlite ďalší súbor
fileTooBig = Súbor je príliš veľký. Mal by byť menší než { $size }.
linkExpiredAlt
.alt = Platnosť odkazu vypršala
expiredPageHeader = Platnosť tohto odkazu vypršala alebo daný odkaz nikdy neexistoval.
notSupportedHeader = Váš prehliadač nie je podporovaný.
// Firefox Send is a brand name and should not be localized.
notSupportedDetail = Žiaľ, tento prehliadač nepodporuje webovú technológiu, ktorá poháňa službu Firefox Send. Budete musieť vyskúšať iný prehliadač. My vám odporúčame Firefox!
downloadFirefoxButtonSub = Prevziať zadarmo
uploadedFile = Súbor
copyFileList = Kopírovať adresu URL
// expiryFileList is used as a column header
expiryFileList = Platnosť vyprší
deleteFileList = Odstrániť
nevermindButton = Zrušiť
deleteButtonHover
.title = Odstrániť
copyUrlHover
.title = Skopírovať adresu URL
legalHeader = Podmienky používania a súkromie
legalNoticeTestPilot = Firefox Send je v súčasnosti experimentom projektu Test Pilot a vzťahujú sa naň <a>podmienky používania</a> a <a>zásady ochrany súkromia</a> Test Pilotu. Viac sa o zbieraní údajov experimentami dozviete <a>tu</a>.
legalNoticeMozilla = Na použitie webovej stránky služby Firefox Send sa vzťahujú <a>zásady ochrany súkromia na webových stránkach</a> a <a>podmienky použitia webových stránok</a> Mozilly.
deletePopupText = Naozaj chcete odstrániť tento súbor?
deletePopupYes = Áno
deletePopupCancel = Zrušiť
deleteButtonHover
.title = Odstrániť
copyUrlHover
.title = Skopírovať adresu URL
footerLinkLegal = Právne informácie
// Test Pilot is a proper name and should not be localized.
footerLinkAbout = O projekte Test Pilot
footerLinkPrivacy = Súkromie
footerLinkTerms = Podmienky používania
footerLinkCookies = Cookies

View File

@@ -0,0 +1,77 @@
// Firefox Send is a brand name and should not be localized.
title = Firefox Send
siteSubtitle = poskus za splet
siteFeedback = Povratne informacije
uploadPageHeader = Zasebno, šifrirano deljenje datotek
uploadPageLearnMore = Več o tem
uploadPageBrowseButton = Izberite datoteko na računalniku
.title = Izberite datoteko na računalniku
uploadPageMultipleFilesAlert = Nalaganje več datotek ali map trenutno ni podprto.
uploadPageBrowseButtonTitle = Prenesi datoteko
importingFile = Uvažanje …
verifyingFile = Preverjanje …
encryptingFile = Šifriranje ...
decryptingFile = Dešifriranje ...
uploadingPageCancel = Prekliči prenos
.title = Prekliči prenos
uploadCancelNotification = Vaš prenos je preklican.
uploadingFileNotification = Obvesti me, ko bo prenos končan.
uploadSvgAlt
.alt = Prenesi
uploadSuccessTimingHeader = Povezava do vaše datoteke bo potekla po enem prenosu ali v 24 urah.
// Note: Title text for button should be the same.
copyUrlFormButton = Kopiraj v odložišče
.title = Kopiraj v odložišče
copiedUrl = Kopirano!
// Note: Title text for button should be the same.
deleteFileButton = Izbriši datoteko
.title = Izbriši datoteko
// Note: Title text for button should be the same.
sendAnotherFileLink = Pošlji drugo datoteko
.title = Pošlji drugo datoteko
// Alternative text used on the download link/button (indicates an action).
downloadAltText
.alt = Prenesi
downloadFileName = Prenesi { $filename }
downloadFileSize = ({ $size })
// Text and title used on the download link/button (indicates an action).
downloadButtonLabel = Prenesi
.title = Prenesi
downloadFinish = Prenos končan
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
sendYourFilesLink = Preskusite Firefox Send
.title = Preskusite Firefox Send
downloadingPageProgress = Prenašanje { $filename } ({ $size })
errorAltText
.alt = Napaka pri prenosu
errorPageHeader = Prišlo je do težave!
errorPageLink = Pošlji drugo datoteko
fileTooBig = Ta datoteka je prevelika za prenos. Morala bi biti manjša od { $size }.
linkExpiredAlt
.alt = Povezava je potekla
notSupportedHeader = Vaš brskalnik ni podprt.
// Firefox Send is a brand name and should not be localized.
notSupportedDetail = Ta brskalnik na žalost ne podpira tehnologije, ki poganja Firefox Send. Uporabiti boste morali drug brskalnik. Priporočamo Firefox!
downloadFirefoxButtonSub = Brezplačen prenos
uploadedFile = Datoteka
copyFileList = Kopiraj URL
deleteFileList = Izbriši
nevermindButton = Pozabi
deleteButtonHover
.title = Izbriši
copyUrlHover
.title = Kopiraj URL
legalHeader = Pogoji in zasebnost
deletePopupText = Izbrišem to datoteko?
deletePopupYes = Da
deletePopupCancel = Prekliči
deleteButtonHover
.title = Izbriši
copyUrlHover
.title = Kopiraj URL
footerLinkLegal = Pravno obvestilo
// Test Pilot is a proper name and should not be localized.
footerLinkAbout = O Test Pilotu
footerLinkPrivacy = Zasebnost
footerLinkTerms = Pogoji
footerLinkCookies = Piškotki

View File

@@ -0,0 +1,96 @@
// Firefox Send is a brand name and should not be localized.
title = Firefox Send
siteSubtitle = webbexperiment
siteFeedback = Återkoppling
uploadPageHeader = Privat, krypterad fildelning
uploadPageExplainer = Skicka filer via en säker, privat och krypterad länk som automatiskt upphör för att säkerställa att dina saker inte förblir på nätet för alltid.
uploadPageLearnMore = Läs mer
uploadPageDropMessage = Släpp filen här för att börja ladda upp
uploadPageSizeMessage = För den mest tillförlitliga driften är det bäst att hålla din fil under 1 GB
uploadPageBrowseButton = Välj en fil på din dator
.title = Välj en fil på din dator
uploadPageMultipleFilesAlert = Överföring av flera filer eller en mapp stöds för närvarande inte.
uploadPageBrowseButtonTitle = Ladda upp fil
uploadingPageHeader = Överför din fil
importingFile = Importerar…
verifyingFile = Verifierar…
encryptingFile = Krypterar…
decryptingFile = Avkodar…
notifyUploadDone = Din uppladdning har slutförts.
uploadingPageMessage = När din filuppladdning är klar kommer du att kunna ange alternativ för upphörande.
uploadingPageCancel = Avbryt uppladdning
.title = Avbryt uppladdning
uploadCancelNotification = Din uppladdning avbröts.
uploadingPageLargeFileMessage = Den här filen är stor och kan ta ett tag att ladda upp. Ha tålamod!
uploadingFileNotification = Meddela mig när uppladdningen är klar.
uploadSuccessConfirmHeader = Klar för att skicka
uploadSvgAlt
.alt = Ladda upp
uploadSuccessTimingHeader = Länken till din fil upphör att gälla efter 1 nedladdning eller om 24 timmar.
copyUrlFormLabelWithName = Kopiera och dela länken för att skicka din fil: { $filename }
// Note: Title text for button should be the same.
copyUrlFormButton = Kopiera till urklipp
.title = Kopiera till urklipp
copiedUrl = Kopierad!
// Note: Title text for button should be the same.
deleteFileButton = Ta bort fil
.title = Ta bort fil
// Note: Title text for button should be the same.
sendAnotherFileLink = Skicka en annan fil
.title = Skicka en annan fil
// Alternative text used on the download link/button (indicates an action).
downloadAltText
.alt = Ladda ner
downloadFileName = Ladda ner { $filename }
downloadFileSize = ({ $size })
// Firefox Send is a brand name and should not be localized.
downloadMessage = Din vän skickar dig en fil med Firefox Send, en tjänst som låter dig dela filer med en säker, privat och krypterad länk som automatiskt upphör för att säkerställa att dina saker inte förblir på nätet för alltid.
// Text and title used on the download link/button (indicates an action).
downloadButtonLabel = Ladda ner
.title = Ladda ner
downloadNotification = Din nedladdning har slutförts.
downloadFinish = Nedladdning klar
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
sendYourFilesLink = Testa Firefox Send
.title = Testa Firefox Send
downloadingPageProgress = Laddar ner { $filename } ({ $size })
downloadingPageMessage = Lämna den här fliken öppen medan vi laddar ner din fil och dekrypterar den.
errorAltText
.alt = Uppladdningsfel
errorPageHeader = Något gick fel!
errorPageMessage = Det har uppstått ett fel vid uppladdning av filen.
errorPageLink = Skicka en annan fil
fileTooBig = Den filen är för stor för att ladda upp. Det ska vara mindre än { $size }.
linkExpiredAlt
.alt = Länk upphörd
expiredPageHeader = Den här länken har upphört eller har aldrig existerat i första hand!
notSupportedHeader = Din webbläsare stöds inte.
// Firefox Send is a brand name and should not be localized.
notSupportedDetail = Tyvärr stöder inte webbläsaren den webbteknologi som används av Firefox Send. Du måste försöka med en annan webbläsare. Vi rekommenderar Firefox!
downloadFirefoxButtonSub = Gratis nedladdning
uploadedFile = Fil
copyFileList = Kopiera URL
// expiryFileList is used as a column header
expiryFileList = Upphör
deleteFileList = Ta bort
nevermindButton = Glöm det
deleteButtonHover
.title = Ta bort
copyUrlHover
.title = Kopiera URL
legalHeader = Villkor och sekretess
legalNoticeTestPilot = Firefox Send är för närvarande ett Test Pilot experiment och omfattas av Test Pilots <a>användarvillkor</a> och <a>sekretesspolicy</a>. Du kan läsa dig mer om detta experiment och dess datainsamling <a>här</a>.
legalNoticeMozilla = Användning av webbplatsen för Firefox Send är också föremål för Mozillas <a>sekretesspolicy för webbplatser</a> och <a>användarvillkor för webbplatser</a>.
deletePopupText = Ta bort den här filen?
deletePopupYes = Ja
deletePopupCancel = Avbryt
deleteButtonHover
.title = Ta bort
copyUrlHover
.title = Kopiera URL
footerLinkLegal = Juridisk information
// Test Pilot is a proper name and should not be localized.
footerLinkAbout = Om Test Pilot
footerLinkPrivacy = Sekretess
footerLinkTerms = Villkor
footerLinkCookies = Kakor

View File

@@ -0,0 +1,96 @@
// Firefox Send is a brand name and should not be localized.
title = Firefox Send
siteSubtitle = Web 实验
siteFeedback = 反馈
uploadPageHeader = 私密、安全的文件分享
uploadPageExplainer = 通过一个安全、私密且已加密的链接发送文件,文件将在有效期后自动清除。
uploadPageLearnMore = 详细了解
uploadPageDropMessage = 拖放您的文件到此处以开始上传
uploadPageSizeMessage = 为保证运行稳定,建议文件大小不超过 1GB
uploadPageBrowseButton = 选择一个在您的计算机上的文件
.title = 选择一个在您的计算机上的文件
uploadPageMultipleFilesAlert = 目前不支持上传多个文件或上传文件夹。
uploadPageBrowseButtonTitle = 上传文件
uploadingPageHeader = 正在上传您的文件
importingFile = 正在导入…
verifyingFile = 正在验证…
encryptingFile = 正在加密…
decryptingFile = 正在解密…
notifyUploadDone = 您的上传已完成。
uploadingPageMessage = 在文件上传后,您可以设定过期选项。
uploadingPageCancel = 取消上传
.title = 取消上传
uploadCancelNotification = 您的上传已取消。
uploadingPageLargeFileMessage = 此文件较大,可能要花费一些时间。请稍候。
uploadingFileNotification = 上传完成后通知我。
uploadSuccessConfirmHeader = 准备好发送
uploadSvgAlt
.alt = 上传
uploadSuccessTimingHeader = 您的文件的链接将在第一次下载的 24 小时后过期。
copyUrlFormLabelWithName = 复制并分享链接以发送您的文件:{ $filename }
// Note: Title text for button should be the same.
copyUrlFormButton = 复制到剪贴板
.title = 复制到剪贴板
copiedUrl = 已复制!
// Note: Title text for button should be the same.
deleteFileButton = 删除文件
.title = 删除文件
// Note: Title text for button should be the same.
sendAnotherFileLink = 发送其他文件
.title = 发送其他文件
// Alternative text used on the download link/button (indicates an action).
downloadAltText
.alt = 下载
downloadFileName = 下载 { $filename }
downloadFileSize = ({ $size })
// Firefox Send is a brand name and should not be localized.
downloadMessage = 您的朋友使用 Firefox Send 向您发送一个文件。该服务允许用户安全、私密、加密的以链接分享一个文件,链接将会自动过期失效。
// Text and title used on the download link/button (indicates an action).
downloadButtonLabel = 下载
.title = 下载
downloadNotification = 您的下载已完成。
downloadFinish = 下载完成
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
sendYourFilesLink = 尝试 Firefox Send
.title = 尝试 Firefox Send
downloadingPageProgress = 正在下载 { $filename } ({ $size })
downloadingPageMessage = 在我们获取您的文件并解密的期间,请不要关闭此网页。
errorAltText
.alt = 上传出错
errorPageHeader = 我们遇到错误。
errorPageMessage = 上传文件时发生错误。
errorPageLink = 发送其他文件
fileTooBig = 此文件太大,它应该小于 { $size }。
linkExpiredAlt
.alt = 链接已过期
expiredPageHeader = 此链接已过期或者从未生效。
notSupportedHeader = 不支持您的浏览器。
// Firefox Send is a brand name and should not be localized.
notSupportedDetail = 很遗憾,您的浏览器不支持 Firefox Send 所使用的技术。您需要尝试其他浏览器。我们推荐使用 Firefox。
downloadFirefoxButtonSub = 免费下载
uploadedFile = 文件
copyFileList = 复制网址
// expiryFileList is used as a column header
expiryFileList = 过期时间
deleteFileList = 删除
nevermindButton = 没关系
deleteButtonHover
.title = 删除
copyUrlHover
.title = 复制网址
legalHeader = 条款和隐私
legalNoticeTestPilot = Firefox Send 目前是一个 Test Pilot 实验,并遵守 Test Pilot <a>服务条款</a>和<a>隐私声明</a>。您可以在<a>这里</a>了解此实验及数据收集的有关信息。
legalNoticeMozilla = 使用 Firefox Send 网站亦受到 Mozilla <a>网站隐私声明</a>和<a>网站使用条款</a>的约束。
deletePopupText = 删除此文件?
deletePopupYes = 是
deletePopupCancel = 取消
deleteButtonHover
.title = 删除
copyUrlHover
.title = 复制网址
footerLinkLegal = 法律
// Test Pilot is a proper name and should not be localized.
footerLinkAbout = 关于 Test Pilot
footerLinkPrivacy = 隐私
footerLinkTerms = 条款
footerLinkCookies = Cookie

View File

@@ -0,0 +1,96 @@
// Firefox Send is a brand name and should not be localized.
title = Firefox Send
siteSubtitle = 網頁實驗
siteFeedback = 意見回饋
uploadPageHeader = 私密、有加密的檔案分享服務
uploadPageExplainer = 透過安全、隱私、加密過的管道來傳送檔案,而且鏈結會自動過期,可確保您的東西不會在網路上無限停留。
uploadPageLearnMore = 了解更多
uploadPageDropMessage = 將檔案放到此處開始上傳
uploadPageSizeMessage = 為了讓系統能最穩定地執行,請盡量將檔案控制在 1GB 以下。
uploadPageBrowseButton = 選擇您電腦上的檔案
.title = 選擇您電腦上的檔案
uploadPageMultipleFilesAlert = 目前暫不支援上傳多個檔案或資料夾。
uploadPageBrowseButtonTitle = 上傳檔案
uploadingPageHeader = 正在上傳檔案
importingFile = 匯入中…
verifyingFile = 驗證中…
encryptingFile = 加密中…
decryptingFile = 解密中…
notifyUploadDone = 已完成上傳。
uploadingPageMessage = 檔案上傳後,即可設定過期時間。
uploadingPageCancel = 取消上傳
.title = 取消上傳
uploadCancelNotification = 已取消上傳。
uploadingPageLargeFileMessage = 這個檔案有點大,可能需要花點時間上傳,再等會兒!
uploadingFileNotification = 上傳完成時通知我。
uploadSuccessConfirmHeader = 準備好傳送
uploadSvgAlt
.alt = 上傳
uploadSuccessTimingHeader = 您的檔案鏈結將會在首次下載,或 24 小時後失效。
copyUrlFormLabelWithName = 複製並分享鏈結來傳送您的檔案: { $filename }
// Note: Title text for button should be the same.
copyUrlFormButton = 複製到剪貼簿
.title = 複製到剪貼簿
copiedUrl = 已複製!
// Note: Title text for button should be the same.
deleteFileButton = 刪除檔案
.title = 刪除檔案
// Note: Title text for button should be the same.
sendAnotherFileLink = 傳送另一個檔案
.title = 傳送另一個檔案
// Alternative text used on the download link/button (indicates an action).
downloadAltText
.alt = 下載
downloadFileName = 下載 { $filename }
downloadFileSize = { $size }
// Firefox Send is a brand name and should not be localized.
downloadMessage = 您的朋友正透過 Firefox Send 傳送檔案給您。這是一個可讓您透過安全、隱密、並且會將鏈結加密過,自動失效以確保檔案不會在網路上無限停留的檔案分享服務。
// Text and title used on the download link/button (indicates an action).
downloadButtonLabel = 下載
.title = 下載
downloadNotification = 下載完成。
downloadFinish = 下載完成
// Firefox Send is a brand name and should not be localized. Title text for button should be the same.
sendYourFilesLink = 試用 Firefox Send
.title = 試用 Firefox Send
downloadingPageProgress = 正在下載 { $filename }{ $size }
downloadingPageMessage = 請保留此分頁開啟,我們將取回這個檔案並進行解密。
errorAltText
.alt = 上傳錯誤
errorPageHeader = 有些東西不對勁!
errorPageMessage = 上傳檔案時發生錯誤。
errorPageLink = 傳送另一個檔案
fileTooBig = 檔案太大無法上傳。檔案大小限制為 { $size }。
linkExpiredAlt
.alt = 鏈結已過期
expiredPageHeader = 鏈結已失效,或根本不存在!
notSupportedHeader = 不支援您的瀏覽器。
// Firefox Send is a brand name and should not be localized.
notSupportedDetail = 很可惜,您使用的瀏覽器並不支援 Firefox Send 所需的 Web 技術。請改用其他瀏覽器,我們推薦使用 Firefox
downloadFirefoxButtonSub = 免費下載
uploadedFile = 檔案
copyFileList = 複製網址
// expiryFileList is used as a column header
expiryFileList = 失效於
deleteFileList = 刪除
nevermindButton = 沒關係
deleteButtonHover
.title = 刪除
copyUrlHover
.title = 複製網址
legalHeader = 使用條款及隱私權
legalNoticeTestPilot = Firefox Send 目前是一個 Test Pilot 實驗,依照 Test Pilot 的<a>服務條款</a>及<a>隱私權公告</a>提供服務。您可以在<a>此處</a>了解實驗內容,以及所收集資料的詳細資訊。
legalNoticeMozilla = 使用 Firefox Send 網站時,亦受到 Mozilla 的<a>網站隱私權公告</a>以及<a>網站使用條款</a>約束。
deletePopupText = 真的要刪除這個檔案嗎?
deletePopupYes = 好的,刪除
deletePopupCancel = 不要刪除
deleteButtonHover
.title = 刪除
copyUrlHover
.title = 複製網址
footerLinkLegal = 法律資訊
// Test Pilot is a proper name and should not be localized.
footerLinkAbout = 關於 Test Pilot
footerLinkPrivacy = 隱私權
footerLinkTerms = 使用條款
footerLinkCookies = Cookie

View File

@@ -1,44 +1,176 @@
/*** index.html ***/
html {
background: url('resources/background.png');
font-family: 'Fira Sans';
font-weight: 300;
font-style: normal;
background-size: contain;
background: url('resources/send_bg.svg');
font-family: -apple-system,
BlinkMacSystemFont,
'SF Pro Text',
Helvetica,
Arial,
sans-serif;
font-weight: 200;
background-size: 110%;
background-repeat: no-repeat;
background-position: center top;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
align-content: center;
flex-direction: column;
max-width: 1440px;
margin: auto;
}
input, select, textarea, button {
body {
display: flex;
flex-direction: column;
margin: 0;
min-height: 100vh;
position: relative;
}
.header {
align-items: flex-start;
box-sizing: border-box;
display: flex;
justify-content: space-between;
padding: 31px;
width: 100%;
}
.send-logo {
display: flex;
position: relative;
align-items: center;
}
.send-logo > a {
display: flex;
flex-direction: row;
}
.site-title {
color: #3e3d40;
font-size: 32px;
font-weight: 500;
margin: 0;
position: relative;
top: -1px;
letter-spacing: 1px;
margin-left: 8px;
}
.site-subtitle {
color: #3e3d40;
font-size: 12px;
margin: 0 8px;
}
.site-subtitle a {
font-weight: bold;
color: #3e3d40;
transition: color 50ms;
}
.send-logo:hover a {
color: #0297f8;
}
.feedback {
background-color: #0297f8;
background-image: url('resources/feedback.svg');
background-position: 2px 4px;
background-repeat: no-repeat;
background-size: 18px;
border-radius: 3px;
border: 1px solid #0297f8;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
color: #fff;
cursor: pointer;
display: block;
float: right;
font-size: 12px;
line-height: 12px;
opacity: 0.9;
padding: 5px;
overflow: hidden;
width: 12px;
text-indent: 17px;
transition: all 150ms ease-in-out;
}
.feedback:hover,
.feedback:focus {
width: 57px;
text-indent: 2px;
padding: 5px 5px 5px 20px;
background-color: #0287e8;
transition: all 150ms ease-in-out;
}
.feedback:active {
background-color: #0277d8;
}
.all {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-start;
max-width: 630px;
margin: 0 auto;
padding: 0 20px;
box-sizing: border-box;
width: 96%;
}
input,
select,
textarea,
button {
font-family: inherit;
}
/** page-one **/
.main-window {
border: 1px solid;
width: 606px;
min-height: 447px;
background-color: white;
border-radius: 5px;
a {
text-decoration: none;
}
/** page-one **/
.title {
font-size: 14px;
width: 80%;
margin: 40px auto;
font-size: 33px;
line-height: 40px;
margin: 20px auto;
text-align: center;
max-width: 520px;
font-family: 'SF Pro Display', sans-serif;
}
.description {
font-size: 15px;
line-height: 23px;
max-width: 630px;
text-align: center;
margin: 0 auto 60px;
color: #0c0c0d;
width: 92%;
}
.upload-window {
border: 1px dashed;
border: 1px dashed rgba(0, 148, 251, 0.5);
margin: 0 auto;
width: 470px;
height: 250px;
border-radius: 5px;
height: 255px;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
text-align: center;
transition: transform 150ms;
padding: 15px;
}
.upload-window.ondrag {
border: 3px dashed rgba(0, 148, 251, 0.5);
margin: 0 auto;
height: 251px;
transform: scale(1.04);
border-radius: 4.2px;
display: flex;
justify-content: center;
align-items: center;
@@ -46,92 +178,116 @@ input, select, textarea, button {
text-align: center;
}
.link {
color: #0094fb;
text-decoration: none;
}
.link:hover {
color: #0287e8;
}
#upload-text {
font-size: 22px;
color: #737373;
margin: 20px 0 10px;
font-family: 'SF Pro Display', sans-serif;
}
#browse {
float: right;
color: #2D7EFF;
}
#browse-text {
float: left;
width: 128px;
}
#upload-img {
padding-right: 20px;
}
.upload-window > div:nth-child(2) {
font-size: 26px;
}
.upload {
font-size: 12px;
width: auto;
overflow: hidden;
}
.file-upload {
background: #0297f8;
border-radius: 5px;
font-size: 15px;
color: #fff;
width: 240px;
height: 44px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
#browse:hover {
background-color: #0287e8;
}
input[type="file"] {
display: none;
}
form {
width: 45px;
float: right;
#file-size-msg {
font-size: 12px;
line-height: 16px;
color: #737373;
margin-bottom: 22px;
}
/** file-list **/
th {
font-size: 10px;
color: #737373;
font-weight: normal;
font-size: 16px;
color: #858585;
font-weight: lighter;
text-align: left;
background: rgba(0, 148, 251, 0.05);
height: 40px;
border-top: 1px solid rgba(0, 148, 251, 0.1);
padding: 0 19px;
}
td {
font-size: 12px;
font-size: 15px;
vertical-align: top;
color: #4a4a4a;
padding: 17px 19px 0;
line-height: 23px;
position: relative;
}
table {
border-collapse: collapse;
font-family: 'Segoe UI', 'SF Pro Text', sans-serif;
}
tbody {
word-wrap: break-word;
word-break: break-all;
}
#uploaded-files {
width: 472px;
margin: 10px auto;
margin: 45.3px auto;
table-layout: fixed;
}
.delete-btn {
padding: 0;
border: none;
background: none;
.icon-delete,
.icon-copy,
.icon-check {
cursor: pointer;
}
/* Popup container */
.popup {
position: relative;
position: absolute;
display: inline-block;
cursor: pointer;
}
/* The actual popup (appears on top) */
.popup .popuptext {
visibility: hidden;
width: 160px;
background-color: #555;
color: #FFF;
min-width: 115px;
background-color: #fff;
color: #000;
border: 1px solid #0297f8;
text-align: center;
border-radius: 6px;
padding: 8px 0;
border-radius: 5px;
padding: 7px 8px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -80px;
bottom: 8px;
right: -28px;
transition: opacity 0.5s;
opacity: 0;
outline: 0;
box-shadow: 3px 3px 7px #888;
}
/* Popup arrow */
@@ -139,11 +295,11 @@ td {
content: "";
position: absolute;
top: 100%;
left: 50%;
right: 30px;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: #555 transparent transparent;
border-color: #0297f8 transparent transparent;
}
.popup .show {
@@ -151,32 +307,79 @@ td {
opacity: 1;
}
/** upload-progress **/
#progress-bar {
width: 300px;
height: 5px;
background: linear-gradient(
90deg,
#FD9800,
#D73000 var(--progress),
white var(--progress)
);
border: 0.5px solid;
border-radius: 5px;
.popup-message {
margin-bottom: 4px;
}
/** share-link **/
.share-window {
margin: 0 auto;
width: 470px;
height: 250px;
.popup-yes {
color: #fff;
background-color: #0297f8;
border-radius: 5px;
padding: 2px 11px;
cursor: pointer;
}
.popup-yes:hover {
background-color: #0287e8;
}
.popup-no {
color: #4a4a4a;
border-radius: 6px;
padding: 3px 5px;
cursor: pointer;
}
/** upload-progress **/
.progress-bar {
margin-top: 3px;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
#share-window-r {
width: 50%;
.percentage {
position: absolute;
letter-spacing: -0.78px;
font-family: 'Segoe UI', 'SF Pro Text', sans-serif;
}
.percent-number {
font-size: 43.2px;
line-height: 58px;
}
.percent-sign {
font-size: 28.8px;
color: rgb(104, 104, 104);
}
.upload {
margin: 0 auto;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
text-align: center;
font-size: 15px;
}
.progress-text {
color: rgba(0, 0, 0, 0.5);
letter-spacing: -0.4px;
margin-top: 24px;
margin-bottom: 74px;
}
#cancel-upload {
color: #d70022;
cursor: pointer;
text-decoration: underline;
}
/** share-link **/
#share-window {
margin: 0 auto;
display: flex;
justify-content: center;
@@ -192,61 +395,188 @@ td {
#copy {
display: flex;
flex-wrap: nowrap;
width: 640px;
}
#copy-text {
align-self: flex-start;
margin-top: 60px;
margin-bottom: 10px;
color: #0c0c0d;
}
#link {
width: 216px;
height: 41px;
border: 1px solid #979797;
flex: 1;
height: 56px;
border: 1px solid #0297f8;
border-radius: 6px 0 0 6px;
font-size: 24px;
color: #737373;
font-family: 'SF Pro Display', sans-serif;
letter-spacing: 0;
line-height: 23px;
font-weight: 300;
padding-left: 10px;
padding-right: 10px;
}
#link:disabled {
border: 1px solid #05a700;
background: #fff;
}
#copy-btn {
width: 60px;
height: 45px;
background: #337FEB;
border: 1px solid #979797;
flex: 0 1 165px;
background: #0297f8;
border-radius: 0 6px 6px 0;
border: 1px solid #0297f8;
color: white;
cursor: pointer;
font-size: 15px;
height: 60px;
padding-left: 10px;
padding-right: 10px;
white-space: nowrap;
}
#copy-btn:hover {
background-color: #0287e8;
}
#copy-btn:disabled {
background: #47B04B;
background: #05a700;
border: 1px solid #05a700;
cursor: auto;
}
.send-new {
font-size: 14px;
margin: auto;
width: 80%;
text-align: center;
color: #2D7EFF;
#delete-file {
width: 176px;
height: 44px;
background: #fff;
border: 1px solid rgba(12, 12, 13, 0.3);
border-radius: 5px;
font-size: 15px;
margin-top: 50px;
margin-bottom: 12px;
cursor: pointer;
color: #313131;
}
#delete-file:hover {
background: #efeff1;
}
.send-new {
font-size: 15px;
margin: auto;
text-align: center;
color: #0094fb;
cursor: pointer;
text-decoration: underline;
}
.send-new:hover,
.send-new:focus,
.send-new:active {
color: #0287e8;
}
/* upload-error */
#upload-error {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
text-align: center;
}
#upload-error[hidden],
#unsupported-browser[hidden] {
display: none;
}
#upload-error-img {
margin: 51px 0 71px;
}
/* unsupported-browser */
#unsupported-browser {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.unsupported-description {
font-size: 13px;
line-height: 23px;
text-align: center;
color: #7d7d7d;
margin: 0 auto 23px;
}
#firefox-logo {
width: 70px;
}
#dl-firefox {
margin-bottom: 181px;
width: 260px;
height: 80px;
background: #12bc00;
border-radius: 3px;
cursor: pointer;
border: 0;
box-shadow: 0 5px 3px rgb(234, 234, 234);
font-family: 'Fira Sans';
font-weight: 500;
color: #fff;
font-size: 26px;
display: flex;
justify-content: center;
align-items: center;
line-height: 1;
}
#dl-firefox-text {
text-align: left;
margin-left: 20.4px;
}
#dl-firefox-text > span {
font-family: 'Fira Sans';
font-weight: 300;
font-size: 18px;
letter-spacing: -0.69px;
}
/** download.html **/
#download-btn {
font-size: 18px;
font-size: 15px;
color: white;
width: 214px;
height: 87px;
margin: 50px auto;
width: 180px;
height: 44px;
margin-top: 20px;
margin-bottom: 30px;
text-align: center;
background: #337FEB;
border: 1px solid #3EA050;
border-radius: 6px;
background: #0297f8;
border: 1px solid #0297f8;
border-radius: 5px;
font-weight: 300;
cursor: pointer;
}
#download-btn:hover {
background-color: #0287e8;
}
#download-btn:disabled {
background: #47B04B;
background: #47b04b;
cursor: auto;
}
#download-page-one {
#download {
margin: 0 auto;
width: 470px;
height: 250px;
display: flex;
justify-content: center;
align-items: center;
@@ -255,13 +585,19 @@ td {
}
#expired-img {
display: none;
margin: 51px 0 71px;
}
.expired-description {
font-size: 15px;
line-height: 23px;
text-align: center;
color: #7d7d7d;
margin: 0 auto 23px;
}
#download-progress {
margin: 0 auto;
width: 470px;
height: 250px;
display: flex;
justify-content: center;
align-items: center;
@@ -269,6 +605,156 @@ td {
text-align: center;
}
#download-text {
margin-bottom: 40px;
#download-progress[hidden] {
display: none;
}
#download-img {
width: 283px;
height: 196px;
}
/* footer */
.footer {
right: 0;
bottom: 0;
left: 0;
font-size: 15px;
display: flex;
align-items: flex-end;
flex-direction: row;
justify-content: space-between;
padding: 50px 31px 41px;
width: 100%;
box-sizing: border-box;
}
.mozilla-logo {
width: 112px;
height: 32px;
margin-bottom: -5px;
}
.legal-links {
max-width: 600px;
width: 80vw;
display: flex;
align-items: center;
flex-direction: row;
justify-content: space-between;
}
.legal-links > a {
color: #858585;
opacity: 0.9;
}
.legal-links > a:hover {
opacity: 1;
}
.legal-links > a:visited {
color: #858585;
}
.social-links {
display: flex;
justify-content: space-between;
width: 94px;
}
.social-links > a {
opacity: 0.9;
}
.social-links > a:hover {
opacity: 1;
}
.github,
.twitter {
width: 32px;
height: 32px;
margin-bottom: -5px;
}
@media (max-device-width: 768px), (max-width: 768px) {
.description {
margin: 0 auto 25px;
}
#copy {
width: 100%;
}
#link {
font-size: 18px;
}
.footer {
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
max-width: 630px;
margin: auto;
}
.mozilla-logo {
margin-left: -7px;
}
.legal-links {
flex-direction: column;
margin: auto;
width: 100%;
max-width: 100%;
}
.legal-links > * {
display: block;
padding: 10px 0;
align-self: flex-start;
}
.social-links {
margin-top: 20px;
align-self: flex-start;
}
}
@media (max-device-width: 520px), (max-width: 520px) {
.header {
flex-direction: column;
justify-content: flex-start;
}
.feedback {
margin-top: 10px;
}
#copy {
width: 100%;
flex-direction: column;
}
#link {
font-size: 22px;
padding: 15px 10px;
border-radius: 6px 6px 0 0;
}
#copy-btn {
border-radius: 0 0 6px 6px;
flex: 0 1 65px;
}
th {
font-size: 14px;
padding: 0 5px;
}
td {
font-size: 13px;
padding: 17px 5px 0;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 16 16"><path fill="#fff" d="M6 14a1 1 0 0 1-.707-.293l-3-3a1 1 0 0 1 1.414-1.414l2.157 2.157 6.316-9.023a1 1 0 0 1 1.639 1.146l-7 10a1 1 0 0 1-.732.427A.863.863 0 0 1 6 14z"/></svg>

After

Width:  |  Height:  |  Size: 257 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="#4A4A4A" d="M9.414 8l5.293-5.293a1 1 0 0 0-1.414-1.414L8 6.586 2.707 1.293a1 1 0 0 0-1.414 1.414L6.586 8l-5.293 5.293a1 1 0 1 0 1.414 1.414L8 9.414l5.293 5.293a1 1 0 0 0 1.414-1.414z"/></svg>

After

Width:  |  Height:  |  Size: 286 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path class="icon-copy" fill="#0A8DFF" d="M14.707 8.293l-3-3A1 1 0 0 0 11 5h-1V4a1 1 0 0 0-.293-.707l-3-3A1 1 0 0 0 6 0H3a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3v3a2 2 0 0 0 2 2h5a2 2 0 0 0 2-2V9a1 1 0 0 0-.293-.707zM12.586 9H11V7.414zm-5-5H6V2.414zM6 7v2H3V2h2v2.5a.5.5 0 0 0 .5.5H8a2 2 0 0 0-2 2zm2 7V7h2v2.5a.5.5 0 0 0 .5.5H13v4z"/></svg>

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1 @@
<svg width="15" height="13" viewBox="0 0 15 13" xmlns="http://www.w3.org/2000/svg"><title>Combined Shape</title><path d="M10.274 9.193a5.957 5.957 0 0 1-2.98.778C4.37 9.97 2 7.963 2 5.485 2 3.008 4.37 1 7.294 1c2.924 0 5.294 2.008 5.294 4.485 0 .843-.274 1.632-.751 2.305l.577 2.21-2.14-.807zm-5.983-2.96a.756.756 0 0 0 .763-.748.756.756 0 0 0-.763-.747.756.756 0 0 0-.764.747c0 .413.342.748.764.748zm3.054 0a.756.756 0 0 0 .764-.748.756.756 0 0 0-.764-.747.756.756 0 0 0-.764.747c0 .413.342.748.764.748zm3.054 0a.756.756 0 0 0 .764-.748.756.756 0 0 0-.764-.747.756.756 0 0 0-.763.747c0 .413.342.748.763.748z" fill="#FFF" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 649 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 239 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="438.549" height="438.549" viewBox="0 0 438.549 438.549"><path d="M409.132 114.573c-19.608-33.596-46.205-60.194-79.798-79.8-33.598-19.607-70.277-29.408-110.063-29.408-39.781 0-76.472 9.804-110.063 29.408-33.596 19.605-60.192 46.204-79.8 79.8C9.803 148.168 0 184.854 0 224.63c0 47.78 13.94 90.745 41.827 128.906 27.884 38.164 63.906 64.572 108.063 79.227 5.14.954 8.945.283 11.419-1.996 2.475-2.282 3.711-5.14 3.711-8.562 0-.571-.049-5.708-.144-15.417a2549.81 2549.81 0 0 1-.144-25.406l-6.567 1.136c-4.187.767-9.469 1.092-15.846 1-6.374-.089-12.991-.757-19.842-1.999-6.854-1.231-13.229-4.086-19.13-8.559-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-.951-2.568-2.098-3.711-3.429-1.142-1.331-1.997-2.663-2.568-3.997-.572-1.335-.098-2.43 1.427-3.289 1.525-.859 4.281-1.276 8.28-1.276l5.708.853c3.807.763 8.516 3.042 14.133 6.851 5.614 3.806 10.229 8.754 13.846 14.842 4.38 7.806 9.657 13.754 15.846 17.847 6.184 4.093 12.419 6.136 18.699 6.136 6.28 0 11.704-.476 16.274-1.423 4.565-.952 8.848-2.383 12.847-4.285 1.713-12.758 6.377-22.559 13.988-29.41-10.848-1.14-20.601-2.857-29.264-5.14-8.658-2.286-17.605-5.996-26.835-11.14-9.235-5.137-16.896-11.516-22.985-19.126-6.09-7.614-11.088-17.61-14.987-29.979-3.901-12.374-5.852-26.648-5.852-42.826 0-23.035 7.52-42.637 22.557-58.817-7.044-17.318-6.379-36.732 1.997-58.24 5.52-1.715 13.706-.428 24.554 3.853 10.85 4.283 18.794 7.952 23.84 10.994 5.046 3.041 9.089 5.618 12.135 7.708 17.705-4.947 35.976-7.421 54.818-7.421s37.117 2.474 54.823 7.421l10.849-6.849c7.419-4.57 16.18-8.758 26.262-12.565 10.088-3.805 17.802-4.853 23.134-3.138 8.562 21.509 9.325 40.922 2.279 58.24 15.036 16.18 22.559 35.787 22.559 58.817 0 16.178-1.958 30.497-5.853 42.966-3.9 12.471-8.941 22.457-15.125 29.979-6.191 7.521-13.901 13.85-23.131 18.986-9.232 5.14-18.182 8.85-26.84 11.136-8.662 2.286-18.415 4.004-29.263 5.146 9.894 8.562 14.842 22.077 14.842 40.539v60.237c0 3.422 1.19 6.279 3.572 8.562 2.379 2.279 6.136 2.95 11.276 1.995 44.163-14.653 80.185-41.062 108.068-79.226 27.88-38.161 41.825-81.126 41.825-128.906-.01-39.771-9.818-76.454-29.414-110.049z"/></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 578.55 185.54"><path d="M503.5 117.21c0 4.92 2.37 8.82 9 8.82 7.8 0 16.11-5.6 16.61-18.31a80.86 80.86 0 0 0-11-1c-7.83-.01-14.61 2.19-14.61 10.49z"/><path d="M0 0v185.54h578.55V0zm163.78 139.93h-32V96.87c0-13.22-4.41-18.31-13.05-18.31-10.51 0-14.75 7.46-14.75 18.14v26.64h10.12v16.61h-32V96.87c0-13.22-4.4-18.31-13.05-18.31-10.51 0-14.75 7.46-14.75 18.14v26.64h14.54v16.61H22.22v-16.61h10.17V80.09h-11V63.48h32.87V75c4.58-8.13 12.55-13.05 23.22-13.05 11 0 21.19 5.26 24.92 16.45 4.24-10.17 12.88-16.45 24.92-16.45 13.73 0 26.28 8.31 26.28 26.45v34.94h10.17zm48.65 1.69c-23.56 0-39.84-14.41-39.84-38.82 0-22.38 13.56-40.86 41-40.86s40.86 18.48 40.86 39.84c.02 24.42-17.61 39.85-42.02 39.85zm121.72-1.69h-66.8l-2.2-11.53 42-48.32h-23.9l-3.39 11.87-15.77-1.69 2.71-26.79H334L335.69 75l-42.4 48.34H318l3.56-11.87 17.29 1.69zm41.36 0h-22.89v-27.46h22.89zm0-49h-22.89V63.48h22.89zm12 49L420.6 23.34h21.53l-33.06 116.59zm44.42 0L465 23.34h21.53l-33.04 116.59zm113.92 1.69c-10.17 0-15.76-5.94-16.78-15.26-4.41 7.8-12.21 15.26-24.58 15.26-11 0-23.56-5.94-23.56-21.87 0-18.82 18.14-23.22 35.6-23.22a100.23 100.23 0 0 1 12.55.68v-2.54c0-7.8-.17-17.12-12.55-17.12-4.58 0-8.14.34-11.7 2.2L502 90.6l-17.46-1.87 3.39-19.83c13.39-5.43 20.17-7 32.72-7 16.45 0 30.35 8.48 30.35 25.94v33.23c0 4.41 1.69 5.94 5.26 5.94a11.5 11.5 0 0 0 3.22-.51l.17 11.53a29.57 29.57 0 0 1-13.77 3.6z"/><path d="M213.27 78.73c-11.19 0-18.14 8.3-18.14 22.72 0 13.22 6.1 23.39 18 23.39 11.36 0 18.82-9.15 18.82-23.73-.03-15.43-8.33-22.38-18.68-22.38z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -0,0 +1 @@
<svg width="30" height="27" viewBox="0 0 30 27" xmlns="http://www.w3.org/2000/svg"><title>send logo</title><g stroke="#3E3D40" fill="none" fill-rule="evenodd"><path d="M22.364 19.989l-2.153-2.103a2.046 2.046 0 0 0-2.665-.151l3.402 3.323a.531.531 0 0 1 0 .766l-2.466 2.408a.563.563 0 0 1-.784 0l-3.398-3.32a1.932 1.932 0 0 0 .188 2.564l2.153 2.103c.788.77 2.066.77 2.855 0l2.868-2.802a1.94 1.94 0 0 0 0-2.788M8.77 14.745a.534.534 0 0 0 0 .766l3.399 3.32a2.05 2.05 0 0 1-2.625-.184l-2.153-2.102a1.94 1.94 0 0 1 0-2.79l2.869-2.801a2.052 2.052 0 0 1 2.854 0l2.153 2.103c.73.713.775 1.83.154 2.603l-3.401-3.323a.565.565 0 0 0-.784 0L8.77 14.745zm9.464 5.682a.777.777 0 0 1 0 1.118.822.822 0 0 1-1.144 0l-5.6-5.47a.777.777 0 0 1 0-1.118.822.822 0 0 1 1.144 0l5.6 5.47z" stroke-width=".618" fill="#3E3D40"/><path d="M6.065 20.606c-2.913-1.586-3.988-3.656-3.988-6.468 0-2.81 2.265-6.425 5.786-6.289.1.004.55-.006.649 0 .895-3.27 2.508-6.353 6.898-6.353 4.557 0 7.336 3.716 6.75 7.785.08-.005 1.232.17 1.31.186 3.096.644 4.915 3.275 4.915 5.18 0 1.905-.107 3.029-2.023 4.947" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 612 612"><path d="M612 116.258a250.714 250.714 0 0 1-72.088 19.772c25.929-15.527 45.777-40.155 55.184-69.411-24.322 14.379-51.169 24.82-79.775 30.48-22.907-24.437-55.49-39.658-91.63-39.658-69.334 0-125.551 56.217-125.551 125.513 0 9.828 1.109 19.427 3.251 28.606-104.326-5.24-196.835-55.223-258.75-131.174-10.823 18.51-16.98 40.078-16.98 63.101 0 43.559 22.181 81.993 55.835 104.479a125.556 125.556 0 0 1-56.867-15.756v1.568c0 60.806 43.291 111.554 100.693 123.104-10.517 2.83-21.607 4.398-33.08 4.398-8.107 0-15.947-.803-23.634-2.333 15.985 49.907 62.336 86.199 117.253 87.194-42.947 33.654-97.099 53.655-155.916 53.655-10.134 0-20.116-.612-29.944-1.721 55.567 35.681 121.536 56.485 192.438 56.485 230.948 0 357.188-191.291 357.188-357.188l-.421-16.253c24.666-17.593 46.005-39.697 62.794-64.861z" fill="#010002"/></svg>

After

Width:  |  Height:  |  Size: 873 B

View File

@@ -1,93 +1 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="148px" height="113px" viewBox="0 0 148 113" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 44.1 (41455) - http://www.bohemiancoding.com/sketch -->
<title>Group 14</title>
<desc>Created with Sketch.</desc>
<defs>
<filter x="-81.6%" y="-21.2%" width="210.5%" height="180.8%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="-10" dy="10" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.126754982 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<filter x="-83.8%" y="-21.6%" width="213.5%" height="182.4%" filterUnits="objectBoundingBox" id="filter-2">
<feOffset dx="-10" dy="10" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.126754982 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<filter x="-78.0%" y="-21.6%" width="204.9%" height="182.4%" filterUnits="objectBoundingBox" id="filter-3">
<feOffset dx="-10" dy="10" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.126754982 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<filter x="-58.8%" y="-21.7%" width="178.4%" height="189.1%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="-10" dy="10" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.13 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
</defs>
<g id="Sidebar" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group-14" transform="translate(19.000000, 1.000000)">
<g id="Group-11" filter="url(#filter-1)" transform="translate(28.573415, 37.189410) rotate(-25.000000) translate(-28.573415, -37.189410) translate(9.573415, 11.189410)">
<g id="noun_775738_cc" stroke="#00C8D8" stroke-width="2">
<g id="Group-10" transform="translate(0.143028, 0.571288)">
<path d="M0,3.11187997 C0,1.44994393 1.33795046,0.102678571 2.99700349,0.102678571 L24.2431693,0.102678571 L36.6460185,10.8658088 L36.6460185,46.9983075 C36.6460185,48.658562 35.2983063,50.0044643 33.647121,50.0044643 L2.9988975,50.0044643 C1.34265214,50.0044643 0,48.6697121 0,46.9952629 L0,3.11187997 Z" id="Rectangle-7" fill="#BFF1F5"></path>
<path d="M24.430679,0.631293163 C24.430679,0.0822437139 24.7706307,-0.0632105526 25.1876619,0.304366627 L36.3833191,10.1723775 C36.8013892,10.5408704 36.6879598,10.8395929 36.1429992,10.8395929 L27.4265882,10.8395929 C25.7719932,10.8395929 24.430679,9.48831667 24.430679,7.83754409 L24.430679,0.631293163 Z" id="Rectangle-8" fill="#2FD2DF"></path>
</g>
</g>
<g id="noun_1029125_cc" transform="translate(3.901518, 8.349380)" fill-rule="nonzero" fill="#2FD2DF">
<g id="Group">
<path d="M24.1801115,8.08668825 C23.3856986,7.07396809 21.8961743,7.07396809 21.1514122,8.08668825 L12.1646161,19.682334 C11.9660128,19.9355141 11.6184572,19.9355141 11.419854,19.73297 L7.89464663,15.8339974 C7.10023371,14.9225493 5.7100111,14.9225493 4.91559818,15.8339974 L0.943533576,20.2393301 C0.546327116,20.6950542 0.347723886,21.2520503 0.347723886,21.8596824 L0.347723886,36.5947607 C0.347723886,37.1517567 0.794581154,37.5568448 1.29108923,37.5568448 L27.8542712,37.5568448 C28.4004301,37.5568448 28.7976366,37.1011207 28.7976366,36.5947607 L28.7976366,14.7706413 C28.7976366,14.2136452 28.5990334,13.7072851 28.3011285,13.3021971 L24.1801115,8.08668825 Z" id="Shape"></path>
<ellipse id="Oval" cx="5.51140787" cy="3.98517161" rx="3.17765168" ry="3.2407045"></ellipse>
</g>
</g>
</g>
<g id="Group-10" filter="url(#filter-2)" transform="translate(44.984738, 0.000000)">
<path d="M0,3.11187997 C0,1.44994393 1.33795046,0.102678571 2.99700349,0.102678571 L24.2431693,0.102678571 L36.6460185,10.8658088 L36.6460185,46.9983075 C36.6460185,48.658562 35.2983063,50.0044643 33.647121,50.0044643 L2.9988975,50.0044643 C1.34265214,50.0044643 0,48.6697121 0,46.9952629 L0,3.11187997 Z" id="Rectangle-7" stroke="#FF5B6D" stroke-width="2" fill="#FFDEE2"></path>
<path d="M5.44129888,23.9323409 L30.6937957,23.9323409 C31.424931,23.9323409 32.0211705,23.4883258 32.0211705,22.9438547 C32.0211705,22.3993836 31.424931,21.9553685 30.6937957,21.9553685 L5.44129888,21.9553685 C4.71016356,21.9553685 4.11392405,22.3993836 4.11392405,22.9438547 C4.11392405,23.4883258 4.71016356,23.9323409 5.44129888,23.9323409 Z" id="Shape" fill="#FF9CA7" fill-rule="nonzero"></path>
<path d="M5.44129888,17.9049859 L30.6937957,17.9049859 C31.424931,17.9049859 32.0211705,17.4609707 32.0211705,16.9164996 C32.0211705,16.3720286 31.424931,15.9280134 30.6937957,15.9280134 L5.44129888,15.9280134 C4.71016356,15.9280134 4.11392405,16.3720286 4.11392405,16.9164996 C4.11392405,17.4609707 4.71016356,17.9049859 5.44129888,17.9049859 Z" id="Shape" fill="#FF9CA7" fill-rule="nonzero"></path>
<path d="M5.44129888,29.959696 L30.6937957,29.959696 C31.424931,29.959696 32.0211705,29.5156809 32.0211705,28.9712098 C32.0211705,28.4267387 31.424931,27.9827235 30.6937957,27.9827235 L5.44129888,27.9827235 C4.71016356,27.9827235 4.11392405,28.4267387 4.11392405,28.9712098 C4.11392405,29.5156809 4.71016356,29.959696 5.44129888,29.959696 Z" id="Shape" fill="#FF9CA7" fill-rule="nonzero"></path>
<path d="M5.44129888,35.9870511 L30.6937957,35.9870511 C31.424931,35.9870511 32.0211705,35.5430359 32.0211705,34.9985649 C32.0211705,34.4540938 31.424931,34.0100786 30.6937957,34.0100786 L5.44129888,34.0100786 C4.71016356,34.0100786 4.11392405,34.4540938 4.11392405,34.9985649 C4.11392405,35.5430359 4.71016356,35.9870511 5.44129888,35.9870511 Z" id="Shape" fill="#FF9CA7" fill-rule="nonzero"></path>
<path d="M5.44129888,42.0144062 L30.6937957,42.0144062 C31.424931,42.0144062 32.0211705,41.570391 32.0211705,41.0259199 C32.0211705,40.4814489 31.424931,40.0374337 30.6937957,40.0374337 L5.44129888,40.0374337 C4.71016356,40.0374337 4.11392405,40.4814489 4.11392405,41.0259199 C4.11392405,41.5724001 4.71016356,42.0144062 5.44129888,42.0144062 Z" id="Shape" fill="#FF9CA7" fill-rule="nonzero"></path>
<path d="M24.430679,0.631293163 C24.430679,0.0822437139 24.7706307,-0.0632105526 25.1876619,0.304366627 L36.3833191,10.1723775 C36.8013892,10.5408704 36.6879598,10.8395929 36.1429992,10.8395929 L27.4265882,10.8395929 C25.7719932,10.8395929 24.430679,9.48831667 24.430679,7.83754409 L24.430679,0.631293163 Z" id="Rectangle-8" stroke="#FF5B6D" stroke-width="2" fill="#FF9CA7"></path>
</g>
<g id="Group-10" filter="url(#filter-3)" transform="translate(99.755713, 40.795908) rotate(25.000000) translate(-99.755713, -40.795908) translate(79.255713, 15.295908)">
<path d="M-2.72848411e-12,3.11187997 C-2.72848411e-12,1.44994393 1.34238993,0.102678571 2.99799713,0.102678571 L37.4077263,0.102678571 L36.6460185,10.8658088 L36.6460185,46.9983075 C36.6460185,48.658562 35.2983063,50.0044643 33.647121,50.0044643 L2.9988975,50.0044643 C1.34265214,50.0044643 -2.72848411e-12,48.6697121 -2.72848411e-12,46.9952629 L-2.72848411e-12,3.11187997 Z" id="Rectangle-7" stroke="#FFBC38" stroke-width="2" fill="#FFEECD"></path>
<g id="Group" transform="translate(11.566360, 15.461370)" stroke-linecap="round" stroke="#FFCD6A" stroke-linejoin="round">
<g transform="translate(3.291139, 15.607143)" id="Shape" stroke-width="1.5">
<path d="M0.507658228,0 L6.07462025,0"></path>
<path d="M0.507658228,1.64285714 L6.07462025,1.64285714"></path>
<path d="M1.80189873,3.28571429 L4.78037975,3.28571429"></path>
</g>
<g id="Shape" stroke-width="2">
<path d="M12.8823418,6.68478571 C12.8823418,3.197 10.0618354,0.369642857 6.58227848,0.369642857 C3.10272152,0.369642857 0.28221519,3.197 0.28221519,6.68478571 C0.28221519,8.54367857 1.08772152,10.2095357 2.36221519,11.3652857 L3.79879747,13.7646786 L9.36658228,13.7646786 L10.8023418,11.3661071 C12.0768354,10.2095357 12.8823418,8.54367857 12.8823418,6.68478571 Z"></path>
<polyline points="5.85164557 13.7991786 4.73759494 9.82264286 8.42696203 9.82264286 7.31291139 13.7991786"></polyline>
</g>
</g>
<path d="M59.4642883,25.2355179 L31.4194394,25.2355179 C30.0529755,25.2355179 29.4086334,24.2834821 28.8404333,23.4439821 C28.2288404,22.5387679 27.6508156,21.6844821 26.2032973,21.6844821 L15.5761548,21.6844821 C14.0655941,21.6844821 12.8366772,23.3429464 12.8366772,25.3809107 L12.8366772,27.8451964 C13.4900254,27.0204821 14.4684103,26.4824464 15.5761548,26.4824464 L59.4642883,26.4824464 C60.5720328,26.4824464 61.5504176,27.0204821 62.2037658,27.8451964 L62.2037658,27.2882679 C62.2029471,25.251125 60.9740302,25.2355179 59.4642883,25.2355179 Z" id="Shape" stroke="#FFBC38" stroke-width="2" fill="#FFCD6A" transform="translate(37.520222, 24.764839) rotate(90.000000) translate(-37.520222, -24.764839) "></path>
</g>
<g id="Group-12" filter="url(#filter-4)" transform="translate(37.025316, 46.000000)">
<path d="M24.196055,2.68359965 C24.8899407,1.8177448 26.0064693,1.82521173 26.6836283,2.69225814 L40.872699,20.8601973 C41.5526621,21.7308341 41.2103747,22.4366247 40.1012607,22.4366247 L31.1853858,22.4366247 L31.1853858,38.5236322 L19.6604889,38.5236322 L19.6604889,22.4366247 L10.3594066,22.4366247 C9.25859767,22.4366247 8.92499102,21.7393653 9.62260704,20.8688558 L24.196055,2.68359965 Z" id="Polygon" fill="#1F7FFF"></path>
<g id="noun_47280_cc" fill-rule="nonzero" fill="#165CE4">
<path d="M31.2533248,39.1323944 L19.5010415,39.1323944 C19.1136036,39.1323944 18.8553116,38.8732394 18.8553116,38.484507 L18.8553116,23.0647887 L8.45906105,23.0647887 C8.20076911,23.0647887 8.00705015,22.9352113 7.87790418,22.6760563 C7.74875821,22.4816901 7.8133312,22.1577465 7.94247717,21.9633803 L24.8605993,0.777464789 C25.1188912,0.453521127 25.6354751,0.453521127 25.893767,0.777464789 L42.7473161,21.8985915 C42.8764621,22.028169 42.9410351,22.2225352 42.9410351,22.4169014 C42.9410351,22.8056338 42.6827432,23.0647887 42.2953052,23.0647887 L42.2953052,23.0647887 L31.8990546,23.0647887 L31.8990546,38.484507 C31.8990546,38.8732394 31.5761897,39.1323944 31.2533248,39.1323944 Z M20.1467714,37.8366197 L30.543022,37.8366197 L30.543022,22.4169014 C30.543022,22.028169 30.8013139,21.7690141 31.1887518,21.7690141 L40.9392726,21.7690141 L25.3771831,2.26760563 L9.81509373,21.7690141 L19.5656145,21.7690141 C19.9530524,21.7690141 20.2113443,22.028169 20.2113443,22.4169014 L20.2113443,37.8366197 L20.1467714,37.8366197 Z" id="Shape"></path>
<path d="M49.9794905,45.8056338 L1.22688672,45.8056338 C0.839448806,45.8056338 0.581156866,45.5464789 0.581156866,45.1577465 L0.581156866,36.8647887 C0.581156866,36.4760563 0.839448806,36.2169014 1.22688672,36.2169014 C1.61432463,36.2169014 1.87261657,36.4760563 1.87261657,36.8647887 L1.87261657,44.5098592 L49.3337606,44.5098592 L49.3337606,36.8647887 C49.3337606,36.4760563 49.5920526,36.2169014 49.9794905,36.2169014 C50.3669284,36.2169014 50.6252203,36.4760563 50.6252203,36.8647887 L50.6252203,45.1577465 C50.6252203,45.4816901 50.3023554,45.8056338 49.9794905,45.8056338 Z" id="Shape"></path>
</g>
</g>
</g>
</g>
</svg>
<svg width="57" height="57" viewBox="0 0 57 57" xmlns="http://www.w3.org/2000/svg"><title>upload</title><g transform="translate(1 1)" stroke-width="2" stroke="#7FC9FD" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path d="M18 24l10-9 10 9M28 39.545V15"/><circle cx="27.5" cy="27.5" r="27.5"/></g></svg>

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 336 B

2
public/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow: /download/

View File

@@ -14,7 +14,7 @@ const filename = path.join(__dirname, '..', 'public', 'version.json');
const filedata = {
commit,
source: pkg.homepage,
version: pkg.version
version: process.env.CIRCLE_TAG || pkg.version
};
fs.writeFileSync(filename, JSON.stringify(filedata, null, 2) + '\n');

View File

@@ -4,12 +4,12 @@ const conf = convict({
s3_bucket: {
format: String,
default: '',
env: 'P2P_S3_BUCKET'
env: 'S3_BUCKET'
},
redis_host: {
format: String,
default: 'localhost',
env: 'P2P_REDIS_HOST'
env: 'REDIS_HOST'
},
listen_port: {
format: 'port',
@@ -25,17 +25,32 @@ const conf = convict({
sentry_id: {
format: String,
default: '',
env: 'P2P_SENTRY_CLIENT'
env: 'SENTRY_CLIENT'
},
sentry_dsn: {
format: String,
default: '',
env: 'P2P_SENTRY_DSN'
env: 'SENTRY_DSN'
},
env: {
format: ['production', 'development', 'test'],
default: 'development',
env: 'NODE_ENV'
},
max_file_size: {
format: Number,
default: 1024 * 1024 * 1024 * 2,
env: 'MAX_FILE_SIZE'
},
expire_seconds: {
format: Number,
default: 86400,
env: 'EXPIRE_SECONDS'
},
l10n_dev: {
format: Boolean,
default: false,
env: 'L10N_DEV'
}
});

View File

@@ -1,9 +1,9 @@
const conf = require('./config.js');
const isProduction = conf.env === 'production'
const isProduction = conf.env === 'production';
const mozlog = require('mozlog')({
app: 'FirefoxFileshare',
app: 'FirefoxSend',
level: isProduction ? 'INFO' : 'verbose',
fmt: isProduction ? 'heka' : 'pretty',
debug: !isProduction

View File

@@ -1,175 +0,0 @@
const express = require('express');
const exphbs = require('express-handlebars');
const busboy = require('connect-busboy');
const path = require('path');
const bodyParser = require('body-parser');
const helmet = require('helmet');
const bytes = require('bytes');
const conf = require('./config.js');
const storage = require('./storage.js');
const Raven = require('raven');
if (conf.sentry_dsn) {
Raven.config(conf.sentry_dsn).install();
}
const mozlog = require('./log.js');
const log = mozlog('portal.server');
const STATIC_PATH = path.join(__dirname, '../public');
const app = express();
app.engine(
'handlebars',
exphbs({
defaultLayout: 'main',
partialsDir: 'views/partials/'
})
);
app.set('view engine', 'handlebars');
app.use(helmet());
app.use(busboy());
app.use(bodyParser.json());
app.use(express.static(STATIC_PATH));
app.get('/', (req, res) => {
res.render('index', {
trackerId: conf.analytics_id,
dsn: conf.sentry_id
});
});
app.get('/exists/:id', (req, res) => {
const id = req.params.id;
storage
.exists(id)
.then(() => {
res.sendStatus(200);
})
.catch(err => res.sendStatus(404));
});
app.get('/download/:id', (req, res) => {
const id = req.params.id;
storage.filename(id).then(filename => {
storage
.length(id)
.then(contentLength => {
res.render('download', {
filename: filename,
filesize: bytes(contentLength),
trackerId: conf.analytics_id,
dsn: conf.sentry_id
});
})
.catch(() => {
res.render('download');
});
});
});
app.get('/assets/download/:id', (req, res) => {
const id = req.params.id;
if (!validateID(id)) {
res.sendStatus(404);
return;
}
storage
.filename(id)
.then(reply => {
storage.length(id).then(contentLength => {
res.writeHead(200, {
'Content-Disposition': 'attachment; filename=' + reply,
'Content-Type': 'application/octet-stream',
'Content-Length': contentLength
});
const file_stream = storage.get(id);
file_stream.on('end', () => {
storage
.forceDelete(id)
.then(err => {
if (!err) {
log.info('Deleted:', id);
}
})
.catch(err => {
log.info('DeleteError:', id);
});
});
file_stream.pipe(res);
});
})
.catch(err => {
res.sendStatus(404);
});
});
app.post('/delete/:id', (req, res) => {
const id = req.params.id;
if (!validateID(id)) {
res.sendStatus(404);
return;
}
const delete_token = req.body.delete_token;
if (!delete_token) {
res.sendStatus(404);
}
storage
.delete(id, delete_token)
.then(err => {
if (!err) {
log.info('Deleted:', id);
res.sendStatus(200);
}
})
.catch(err => res.sendStatus(404));
});
app.post('/upload/:id', (req, res, next) => {
if (!validateID(req.params.id)) {
res.sendStatus(404);
return;
}
req.pipe(req.busboy);
req.busboy.on('file', (fieldname, file, filename) => {
log.info('Uploading:', req.params.id);
const protocol = conf.env === 'production' ? 'https' : req.protocol;
const url = `${protocol}://${req.get('host')}/download/${req.params.id}/`;
storage.set(req.params.id, file, filename, url).then(linkAndID => {
res.json(linkAndID);
});
});
});
app.get('/__lbheartbeat__', (req, res) => {
res.sendStatus(200);
});
app.get('/__heartbeat__', (req, res) => {
storage.ping().then(() => res.sendStatus(200), () => res.sendStatus(500));
});
app.get('/__version__', (req, res) => {
res.sendFile(path.join(STATIC_PATH, 'version.json'));
});
app.listen(conf.listen_port, () => {
log.info('startServer:', `Portal app listening on port ${conf.listen_port}!`);
});
const validateID = route_id => {
return route_id.match(/^[0-9a-fA-F]{32}$/) !== null;
};

318
server/server.js Normal file
View File

@@ -0,0 +1,318 @@
const express = require('express');
const exphbs = require('express-handlebars');
const busboy = require('connect-busboy');
const path = require('path');
const bodyParser = require('body-parser');
const helmet = require('helmet');
const bytes = require('bytes');
const conf = require('./config.js');
const storage = require('./storage.js');
const Raven = require('raven');
const crypto = require('crypto');
const fs = require('fs');
if (conf.sentry_dsn) {
Raven.config(conf.sentry_dsn).install();
}
const mozlog = require('./log.js');
const log = mozlog('send.server');
const STATIC_PATH = path.join(__dirname, '../public');
const app = express();
function allLangs() {
return fs
.readdirSync(path.join(STATIC_PATH, 'locales'))
.map(function(f) {
return f.split('.')[0];
})
.join(',');
}
function prodLangs() {
return require(path.join(
__dirname,
'..',
'package.json'
)).availableLanguages.join(',');
}
const availableLanguages = conf.l10n_dev ? allLangs() : prodLangs();
app.engine(
'handlebars',
exphbs({
defaultLayout: 'main',
partialsDir: 'views/partials/',
helpers: {
availableLanguages,
l10nDev: conf.l10n_dev
}
})
);
app.set('view engine', 'handlebars');
app.use(helmet());
app.use(
helmet.hsts({
maxAge: 31536000,
force: conf.env === 'production'
})
);
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
connectSrc: [
"'self'",
'https://sentry.prod.mozaws.net',
'https://www.google-analytics.com'
],
imgSrc: ["'self'", 'https://www.google-analytics.com'],
scriptSrc: ["'self'"],
styleSrc: ["'self'", 'https://code.cdn.mozilla.net'],
fontSrc: ["'self'", 'https://code.cdn.mozilla.net'],
formAction: ["'none'"],
frameAncestors: ["'none'"],
objectSrc: ["'none'"],
reportUri: '/__cspreport__'
}
})
);
app.use(
busboy({
limits: {
fileSize: conf.max_file_size
}
})
);
app.use(bodyParser.json());
app.use(express.static(STATIC_PATH));
app.get('/', (req, res) => {
res.render('index');
});
app.get('/unsupported', (req, res) => {
res.render('unsupported');
});
app.get('/legal', (req, res) => {
res.render('legal');
});
app.get('/jsconfig.js', (req, res) => {
res.set('Content-Type', 'application/javascript');
res.render('jsconfig', {
trackerId: conf.analytics_id,
dsn: conf.sentry_id,
maxFileSize: conf.max_file_size,
expireSeconds: conf.expire_seconds,
layout: false
});
});
app.get('/exists/:id', (req, res) => {
const id = req.params.id;
if (!validateID(id)) {
res.sendStatus(404);
return;
}
storage
.exists(id)
.then(() => {
res.sendStatus(200);
})
.catch(err => res.sendStatus(404));
});
app.get('/download/:id', (req, res) => {
const id = req.params.id;
if (!validateID(id)) {
res.sendStatus(404);
return;
}
storage
.filename(id)
.then(filename => {
return storage.length(id).then(contentLength => {
storage.ttl(id).then(timeToExpiry => {
res.render('download', {
filename: decodeURIComponent(filename),
filesize: bytes(contentLength),
sizeInBytes: contentLength,
timeToExpiry: timeToExpiry
});
});
});
})
.catch(() => {
res.status(404).render('notfound');
});
});
app.get('/assets/download/:id', (req, res) => {
const id = req.params.id;
if (!validateID(id)) {
res.sendStatus(404);
return;
}
storage
.metadata(id)
.then(meta => {
storage
.length(id)
.then(contentLength => {
res.writeHead(200, {
'Content-Disposition': 'attachment; filename=' + meta.filename,
'Content-Type': 'application/octet-stream',
'Content-Length': contentLength,
'X-File-Metadata': JSON.stringify(meta)
});
const file_stream = storage.get(id);
file_stream.on('end', () => {
storage
.forceDelete(id)
.then(err => {
if (!err) {
log.info('Deleted:', id);
}
})
.catch(err => {
log.info('DeleteError:', id);
});
});
file_stream.pipe(res);
})
.catch(err => {
res.sendStatus(404);
});
})
.catch(err => {
res.sendStatus(404);
});
});
app.post('/delete/:id', (req, res) => {
const id = req.params.id;
if (!validateID(id)) {
res.sendStatus(404);
return;
}
const delete_token = req.body.delete_token;
if (!delete_token) {
res.sendStatus(404);
return;
}
storage
.delete(id, delete_token)
.then(err => {
if (!err) {
log.info('Deleted:', id);
res.sendStatus(200);
}
})
.catch(err => res.sendStatus(404));
});
app.post('/upload', (req, res, next) => {
const newId = crypto.randomBytes(5).toString('hex');
let meta;
try {
meta = JSON.parse(req.header('X-File-Metadata'));
} catch (err) {
res.sendStatus(400);
return;
}
if (
!meta.hasOwnProperty('aad') ||
!meta.hasOwnProperty('id') ||
!meta.hasOwnProperty('filename') ||
!validateIV(meta.id)
) {
res.sendStatus(404);
return;
}
meta.delete = crypto.randomBytes(10).toString('hex');
log.info('meta', meta);
req.pipe(req.busboy);
req.busboy.on('file', (fieldname, file, filename) => {
log.info('Uploading:', newId);
storage.set(newId, file, filename, meta).then(
() => {
const protocol = conf.env === 'production' ? 'https' : req.protocol;
const url = `${protocol}://${req.get('host')}/download/${newId}/`;
res.json({
url,
delete: meta.delete,
id: newId
});
},
err => {
if (err.message === 'limit') {
return res.sendStatus(413);
}
res.sendStatus(500);
}
);
});
req.on('close', err => {
storage
.forceDelete(newId)
.then(err => {
if (!err) {
log.info('Deleted:', newId);
}
})
.catch(err => {
log.info('DeleteError:', newId);
});
});
});
app.get('/__lbheartbeat__', (req, res) => {
res.sendStatus(200);
});
app.get('/__heartbeat__', (req, res) => {
storage.ping().then(() => res.sendStatus(200), () => res.sendStatus(500));
});
app.get('/__version__', (req, res) => {
res.sendFile(path.join(STATIC_PATH, 'version.json'));
});
const server = app.listen(conf.listen_port, () => {
log.info('startServer:', `Send app listening on port ${conf.listen_port}!`);
});
const validateID = route_id => {
return route_id.match(/^[0-9a-fA-F]{10}$/) !== null;
};
const validateIV = route_id => {
return route_id.match(/^[0-9a-fA-F]{24}$/) !== null;
};
module.exports = {
server: server,
storage: storage
};

View File

@@ -4,11 +4,10 @@ const s3 = new AWS.S3();
const conf = require('./config.js');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const mozlog = require('./log.js');
const log = mozlog('portal.storage');
const log = mozlog('send.storage');
const redis = require('redis');
const redis_client = redis.createClient({
@@ -24,34 +23,73 @@ if (conf.s3_bucket) {
module.exports = {
filename: filename,
exists: exists,
ttl: ttl,
length: awsLength,
get: awsGet,
set: awsSet,
setField: setField,
delete: awsDelete,
forceDelete: awsForceDelete,
ping: awsPing
ping: awsPing,
flushall: flushall,
quit: quit,
metadata
};
} else {
module.exports = {
filename: filename,
exists: exists,
ttl: ttl,
length: localLength,
get: localGet,
set: localSet,
setField: setField,
delete: localDelete,
forceDelete: localForceDelete,
ping: localPing
ping: localPing,
flushall: flushall,
quit: quit,
metadata
};
}
function flushall() {
redis_client.flushdb();
}
function quit() {
redis_client.quit();
}
function metadata(id) {
return new Promise((resolve, reject) => {
redis_client.hgetall(id, (err, reply) => {
if (err || !reply) {
return reject(err);
}
resolve(reply);
});
});
}
function ttl(id) {
return new Promise((resolve, reject) => {
redis_client.ttl(id, (err, reply) => {
if (err || !reply) {
return reject(err);
}
resolve(reply * 1000);
});
});
}
function filename(id) {
return new Promise((resolve, reject) => {
redis_client.hget(id, 'filename', (err, reply) => {
if (!err) {
resolve(reply);
} else {
reject(err);
if (err || !reply) {
return reject();
}
resolve(reply);
});
});
}
@@ -68,6 +106,10 @@ function exists(id) {
});
}
function setField(id, key, value) {
redis_client.hset(id, key, value);
}
function localLength(id) {
return new Promise((resolve, reject) => {
try {
@@ -82,25 +124,26 @@ function localGet(id) {
return fs.createReadStream(path.join(__dirname, '../static', id));
}
function localSet(id, file, filename, url) {
function localSet(newId, file, filename, meta) {
return new Promise((resolve, reject) => {
const fstream = fs.createWriteStream(path.join(__dirname, '../static', id));
const filepath = path.join(__dirname, '../static', newId);
const fstream = fs.createWriteStream(filepath);
file.pipe(fstream);
fstream.on('close', () => {
const uuid = crypto.randomBytes(10).toString('hex');
redis_client.hmset([id, 'filename', filename, 'delete', uuid]);
redis_client.expire(id, 86400000);
log.info('localSet:', 'Upload Finished of ' + id);
resolve({
uuid: uuid,
url: url
});
file.on('limit', () => {
file.unpipe(fstream);
fstream.destroy(new Error('limit'));
});
fstream.on('finish', () => {
redis_client.hmset(newId, meta);
redis_client.expire(newId, conf.expire_seconds);
log.info('localSet:', 'Upload Finished of ' + newId);
resolve(meta.delete);
});
fstream.on('error', () => {
log.error('localSet:', 'Failed upload of ' + id);
reject();
fstream.on('error', err => {
log.error('localSet:', 'Failed upload of ' + newId);
fs.unlinkSync(filepath);
reject(err);
});
});
}
@@ -163,32 +206,32 @@ function awsGet(id) {
}
}
function awsSet(id, file, filename, url) {
function awsSet(newId, file, filename, meta) {
const params = {
Bucket: conf.s3_bucket,
Key: id,
Key: newId,
Body: file
};
return new Promise((resolve, reject) => {
s3.upload(params, function(err, _data) {
if (err) {
log.info('awsUploadError:', err.stack); // an error occurred
reject();
} else {
const uuid = crypto.randomBytes(10).toString('hex');
redis_client.hmset([id, 'filename', filename, 'delete', uuid]);
redis_client.expire(id, 86400000);
log.info('awsUploadFinish', 'Upload Finished of ' + filename);
resolve({
uuid: uuid,
url: url
});
}
});
let hitLimit = false;
const upload = s3.upload(params);
file.on('limit', () => {
hitLimit = true;
upload.abort();
});
return upload.promise().then(
() => {
redis_client.hmset(newId, meta);
redis_client.expire(newId, conf.expire_seconds);
log.info('awsUploadFinish', 'Upload Finished of ' + filename);
},
err => {
if (hitLimit) {
throw new Error('limit');
} else {
throw err;
}
}
);
}
function awsDelete(id, delete_token) {
@@ -197,13 +240,13 @@ function awsDelete(id, delete_token) {
if (!reply || delete_token !== reply) {
reject();
} else {
redis_client.del(id);
const params = {
Bucket: conf.s3_bucket,
Key: id
};
s3.deleteObject(params, function(err, _data) {
redis_client.del(id);
err ? reject(err) : resolve(err);
});
}
@@ -213,13 +256,13 @@ function awsDelete(id, delete_token) {
function awsForceDelete(id) {
return new Promise((resolve, reject) => {
redis_client.del(id);
const params = {
Bucket: conf.s3_bucket,
Key: id
};
s3.deleteObject(params, function(err, _data) {
redis_client.del(id);
err ? reject(err) : resolve(err);
});
});

View File

@@ -19,3 +19,5 @@ rules:
mocha/no-pending-tests: error
mocha/no-return-and-callback: warn
mocha/no-skipped-tests: error
no-console: off # ¯\_(ツ)_/¯

View File

@@ -0,0 +1,2 @@
env:
browser: true

22
test/frontend/driver.js Normal file
View File

@@ -0,0 +1,22 @@
const webdriver = require('selenium-webdriver');
const path = require('path');
const until = webdriver.until;
const driver = new webdriver.Builder()
.forBrowser('firefox')
.build();
driver.get(path.join('file:///', __dirname, '/frontend.test.html'));
driver.wait(until.titleIs('Mocha Tests'));
driver.wait(until.titleMatches(/^[0-1]$/));
driver.getTitle().then(title => {
driver.quit().then(() => {
if (title === '0') {
console.log('Frontend tests have passed.');
} else {
throw new Error('Frontend tests are failing. ' +
'Please open the frontend.test.html file in a browser.');
}
})
})

View File

@@ -0,0 +1,22 @@
class FakeFile extends Blob {
constructor(name, data, opt) {
super(data, opt);
this.name = name;
}
}
window.Raven = {
captureException: function(err) {
console.error(err, err.stack);
}
}
window.FakeFile = FakeFile;
window.FileSender = require('../../frontend/src/fileSender');
window.FileReceiver = require('../../frontend/src/fileReceiver');
window.sinon = require('sinon');
window.server = window.sinon.fakeServer.create();
window.assert = require('assert');
const utils = require('../../frontend/src/utils');
window.hexToArray = utils.hexToArray;
window.arrayToHex = utils.arrayToHex;

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>Mocha Tests</title>
<link rel="stylesheet" href="../../node_modules/mocha/mocha.css">
<script src="bundle.js"></script>
<meta charset="utf-8"/>
</head>
<body>
<div id="mocha"></div>
<script src="../../node_modules/mocha/mocha.js"></script>
<script>mocha.setup('bdd')</script>
<script src="frontend.test.js"></script>
<script>
mocha.checkLeaks();
mocha.globals(['jQuery']);
mocha.run(function(err) {
document.title = err;
});
</script>
</body>
</html>

View File

@@ -0,0 +1,360 @@
const FileSender = window.FileSender;
const FileReceiver = window.FileReceiver;
const FakeFile = window.FakeFile;
const assert = window.assert;
const server = window.server;
const hexToArray = window.hexToArray;
const arrayToHex = window.arrayToHex;
const sinon = window.sinon;
let file;
let encryptedIV;
let fileHash;
let secretKey;
let originalBlob;
describe('File Sender', function() {
before(function() {
server.respondImmediately = true;
server.respondWith(
'POST',
'/upload',
function(request) {
const reader = new FileReader();
reader.readAsArrayBuffer(request.requestBody.get('data'));
reader.onload = function(event) {
file = this.result;
}
const responseObj = JSON.parse(request.requestHeaders['X-File-Metadata']);
request.respond(
200,
{'Content-Type': 'application/json'},
JSON.stringify({url: 'some url',
id: responseObj.id,
delete: responseObj.delete})
)
}
)
})
it('Should get a loading event emission', function() {
const file = new FakeFile('hello_world.txt', ['This is some data.'])
const fs = new FileSender(file);
let testLoading = true;
fs.on('loading', isStillLoading => {
assert(!(!testLoading && isStillLoading));
testLoading = isStillLoading;
})
return fs.upload()
.then(info => {
assert(info);
assert(!testLoading);
})
.catch(err => {
console.log(err, err.stack);
assert.fail();
});
})
it('Should get a hashing event emission', function() {
const file = new FakeFile('hello_world.txt', ['This is some data.'])
const fs = new FileSender(file);
let testHashing = true;
fs.on('hashing', isStillHashing => {
assert(!(!testHashing && isStillHashing));
testHashing = isStillHashing;
})
return fs.upload()
.then(info => {
assert(info);
assert(!testHashing);
})
.catch(err => {
console.log(err, err.stack);
assert.fail();
});
})
it('Should get a encrypting event emission', function() {
const file = new FakeFile('hello_world.txt', ['This is some data.'])
const fs = new FileSender(file);
let testEncrypting = true;
fs.on('encrypting', isStillEncrypting => {
assert(!(!testEncrypting && isStillEncrypting));
testEncrypting = isStillEncrypting;
})
return fs.upload()
.then(info => {
assert(info);
assert(!testEncrypting);
})
.catch(err => {
console.log(err, err.stack);
assert.fail();
});
})
it('Should encrypt a file properly', function(done) {
const newFile = new FakeFile('hello_world.txt', ['This is some data.'])
const fs = new FileSender(newFile);
fs.upload().then(info => {
const key = info.secretKey;
secretKey = info.secretKey;
const IV = info.fileId;
encryptedIV = info.fileId;
const readRaw = new FileReader;
readRaw.onload = function(event) {
const rawArray = new Uint8Array(this.result);
originalBlob = rawArray;
window.crypto.subtle.digest('SHA-256', rawArray).then(hash => {
fileHash = hash;
window.crypto.subtle.importKey(
'jwk',
{
kty: 'oct',
k: key,
alg: 'A128GCM',
ext: true,
},
{
name: 'AES-GCM'
},
true,
['encrypt', 'decrypt']
)
.then(cryptoKey => {
window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: hexToArray(IV),
additionalData: hash,
tagLength: 128
},
cryptoKey,
rawArray
)
.then(encrypted => {
assert(new Uint8Array(encrypted).toString() ===
new Uint8Array(file).toString());
done();
})
})
})
}
readRaw.readAsArrayBuffer(newFile);
})
})
});
describe('File Receiver', function() {
class FakeXHR {
constructor() {
this.response = file;
this.status = 200;
}
static setup() {
FakeXHR.prototype.open = sinon.spy();
FakeXHR.prototype.send = function () {
this.onload();
}
FakeXHR.prototype.originalXHR = window.XMLHttpRequest;
FakeXHR.prototype.getResponseHeader = function () {
return JSON.stringify({
aad: arrayToHex(new Uint8Array(fileHash)),
filename: 'hello_world.txt',
id: encryptedIV
})
}
window.XMLHttpRequest = FakeXHR;
}
static restore() {
// originalXHR is a sinon FakeXMLHttpRequest, since
// fakeServer.create() is called in frontend.bundle.js
window.XMLHttpRequest.prototype.originalXHR.restore();
}
}
const cb = function(done) {
if (file === undefined ||
encryptedIV === undefined ||
fileHash === undefined ||
secretKey === undefined) {
assert.fail('Please run file sending tests before trying to receive the files.');
done();
}
FakeXHR.setup();
done();
}
before(cb)
after(function() {
FakeXHR.restore();
})
it('Should decrypt properly', function() {
const fr = new FileReceiver();
location.hash = secretKey;
return fr.download().then(([decrypted, name]) => {
assert(name);
assert(new Uint8Array(decrypted).toString() ===
new Uint8Array(originalBlob).toString())
}).catch(err => {
console.log(err, err.stack);
assert.fail();
})
})
it('Should emit decrypting events', function() {
const fr = new FileReceiver();
location.hash = secretKey;
let testDecrypting = true;
fr.on('decrypting', isStillDecrypting => {
assert(!(!testDecrypting && isStillDecrypting));
testDecrypting = isStillDecrypting;
});
fr.on('safe', isSafe => {
assert(isSafe);
})
return fr.download().then(([decrypted, name]) => {
assert(decrypted);
assert(name);
assert(!testDecrypting);
}).catch(err => {
console.log(err, err.stack);
assert.fail();
})
})
it('Should emit hashing events', function() {
const fr = new FileReceiver();
location.hash = secretKey;
let testHashing = true;
fr.on('hashing', isStillHashing => {
assert(!(!testHashing && isStillHashing));
testHashing = isStillHashing;
});
fr.on('safe', isSafe => {
assert(isSafe);
})
return fr.download().then(([decrypted, name]) => {
assert(decrypted);
assert(name);
assert(!testHashing);
}).catch(err => {
assert.fail();
})
})
it('Should catch fraudulent checksums', function(done) {
// Use the secret key and file hash of the previous file to encrypt,
// which has a different hash than this one (different strings).
const newFile = new FakeFile('hello_world.txt',
['This is some data, with a changed hash.'])
const readRaw = new FileReader();
readRaw.onload = function(event) {
const plaintext = new Uint8Array(this.result);
window.crypto.subtle.importKey(
'jwk',
{
kty: 'oct',
k: secretKey,
alg: 'A128GCM',
ext: true
},
{
name: 'AES-GCM'
},
true,
['encrypt', 'decrypt']
)
.then(key => {
// The file hash used here is the hash of the fake
// file from the previous test; it's a phony checksum.
return window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv: hexToArray(encryptedIV),
additionalData: fileHash,
tagLength: 128
},
key,
plaintext
)
})
.then(encrypted => {
file = encrypted;
const fr = new FileReceiver();
location.hash = secretKey;
fr.on('unsafe', isUnsafe => {
assert(isUnsafe)
})
fr.on('safe', () => {
// This event should not be emitted.
assert.fail();
})
fr.download().then(() => {
assert.fail();
done();
}).catch(err => {
assert(1);
done();
})
})
}
readRaw.readAsArrayBuffer(newFile);
})
it('Should not decrypt with an incorrect checksum', function() {
FakeXHR.prototype.getResponseHeader = function () {
return JSON.stringify({
aad: 'some_bad_hashz',
filename: 'hello_world.txt',
id: encryptedIV
})
}
const fr = new FileReceiver();
location.hash = secretKey;
return fr.download().then(([decrypted, name]) => {
assert(decrypted);
assert(name);
assert.fail();
}).catch(err => {
assert(1);
})
})
})

170
test/server/server.test.js Normal file
View File

@@ -0,0 +1,170 @@
const assert = require('assert');
const sinon = require('sinon');
const proxyquire = require('proxyquire');
const request = require('supertest');
const fs = require('fs');
const logStub = {};
logStub.info = sinon.stub();
logStub.error = sinon.stub();
const storage = proxyquire('../../server/storage', {
'./log.js': function() {
return logStub;
}
});
storage.flushall();
describe('Server integration tests', function() {
let server;
let storage;
let uuid;
let fileId;
before(function() {
const app = proxyquire('../../server/server', {
'./log.js': function() {
return logStub;
}
});
server = app.server;
storage = app.storage;
});
after(function() {
storage.flushall();
storage.quit();
server.close();
})
function upload() {
return request(server).post('/upload')
.field('fname', 'test_upload.txt')
.set('X-File-Metadata', JSON.stringify({
aad: '11111',
id: '111111111111111111111111',
filename: 'test_upload.txt'
}))
.attach('file', './test/test_upload.txt')
}
it('Responds with a 200 when the service is up', function() {
return request(server).get('/').expect(200);
});
it('Rejects with a 404 when a file id is not valid', function() {
return request(server).post('/upload/123')
.field('fname', 'test_upload.txt')
.set('X-File-Metadata', JSON.stringify({
'silly': 'text'
}))
.attach('file', './test/test_upload.txt')
.expect(404)
})
it('Accepts a file and stores it when properly uploaded', function(done) {
upload().then(res => {
assert(res.body.hasOwnProperty('delete'));
uuid = res.body.delete;
assert(res.body.hasOwnProperty('url'));
assert(res.body.hasOwnProperty('id'));
fileId = res.body.id;
fs.access('./static/' + fileId, fs.constants.F_OK, err => {
if (err) {
done(new Error('The file does not exist'));
} else {
done();
}
})
})
})
it('Responds with a 200 if a file exists', function() {
return request(server).get('/exists/' + fileId)
.expect(200)
})
it('Exists in the redis server', function() {
return storage.exists(fileId)
.then(() => assert(1))
.catch(err => assert.fail())
})
it('Fails delete if the delete token does not match', function() {
return request(server).post('/delete/' + fileId)
.send({ delete_token: 11 })
.expect(404);
})
it('Fails delete if the id is invalid', function() {
return request(server).post('/delete/1')
.expect(404);
})
it('Successfully deletes if the id is valid and the delete token matches', function(done) {
request(server).post('/delete/' + fileId)
.send({ delete_token: uuid })
.expect(200)
.then(() => {
fs.access('./static/' + fileId, fs.constants.F_OK, err => {
if (err) {
done();
} else {
done(new Error('The file does not exist'));
}
})
})
})
it('Responds with a 404 if a file does not exist', function() {
return request(server).get('/exists/notfound')
.expect(404)
})
it('Uploads properly after a delete', function(done) {
upload().then(res => {
assert(res.body.hasOwnProperty('delete'));
uuid = res.body.delete;
assert(res.body.hasOwnProperty('url'));
assert(res.body.hasOwnProperty('id'));
fileId = res.body.id;
fs.access('./static/' + fileId, fs.constants.F_OK, err => {
if (err) {
done(new Error('The file does not exist'));
} else {
done();
}
})
})
})
it('Responds with a 200 for the download page', function() {
return request(server).get('/download/' + fileId)
.expect(200);
})
it('Downloads a file properly', function() {
return request(server).get('/assets/download/' + fileId)
.then(res => {
assert(res.header.hasOwnProperty('content-disposition'));
assert(res.header.hasOwnProperty('content-type'))
assert(res.header.hasOwnProperty('content-length'))
assert(res.header.hasOwnProperty('x-file-metadata'))
assert.equal(res.header['content-disposition'], 'attachment; filename=test_upload.txt')
assert.equal(res.header['content-type'], 'application/octet-stream')
})
})
it('The file is deleted after one download', function() {
assert(!fs.existsSync('./static/' + fileId));
})
it('No longer exists in the redis server', function() {
return storage.exists(fileId)
.then(() => assert.fail())
.catch(err => assert(1))
})
});

1
test/test_upload.txt Normal file
View File

@@ -0,0 +1 @@
This is a test.

View File

@@ -43,7 +43,7 @@ const awsStub = {
}
};
const storage = proxyquire('../server/storage', {
const storage = proxyquire('../../server/storage', {
redis: redisStub,
fs: fsStub,
'./log.js': function() {
@@ -110,23 +110,20 @@ describe('Testing Set using aws', function() {
it('Should pass when the file is successfully uploaded', function() {
const buf = Buffer.alloc(10);
sinon.stub(crypto, 'randomBytes').returns(buf);
s3Stub.upload.callsArgWith(1, null, {});
s3Stub.upload.returns({promise: () => Promise.resolve()});
return storage
.set('123', {}, 'Filename.moz', 'url.com')
.then(reply => {
assert.equal(reply.uuid, buf.toString('hex'));
assert.equal(reply.url, 'url.com');
assert.notEqual(reply.uuid, null);
.set('123', {on: sinon.stub()}, 'Filename.moz', {})
.then(() => {
assert(expire.calledOnce);
assert(expire.calledWith('123', 86400000));
assert(expire.calledWith('123', 86400));
})
.catch(err => assert.fail());
});
it('Should fail if there was an error during uploading', function() {
s3Stub.upload.callsArgWith(1, new Error(), null);
s3Stub.upload.returns({promise: () => Promise.reject()});
return storage
.set('123', {}, 'Filename.moz', 'url.com')
.set('123', {on: sinon.stub()}, 'Filename.moz', 'url.com')
.then(_reply => assert.fail())
.catch(err => assert(1));
});

View File

@@ -32,7 +32,7 @@ const logStub = {};
logStub.info = sinon.stub();
logStub.error = sinon.stub();
const storage = proxyquire('../server/storage', {
const storage = proxyquire('../../server/storage', {
redis: redisStub,
fs: fsStub,
'./log.js': function() {
@@ -117,15 +117,14 @@ describe('Testing Get from local filesystem', function() {
describe('Testing Set to local filesystem', function() {
it('Successfully writes the file to the local filesystem', function() {
const stub = sinon.stub();
stub.withArgs('close', sinon.match.any).callsArgWithAsync(1);
stub.withArgs('finish', sinon.match.any).callsArgWithAsync(1);
stub.withArgs('error', sinon.match.any).returns(1);
fsStub.createWriteStream.returns({ on: stub });
return storage
.set('test', { pipe: sinon.stub() }, 'Filename.moz', 'moz.la')
.then(reply => {
assert(reply.uuid);
assert.equal(reply.url, 'moz.la');
.set('test', { pipe: sinon.stub(), on: sinon.stub() }, 'Filename.moz', {})
.then(() => {
assert(1);
})
.catch(err => assert.fail());
});

View File

@@ -1,65 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<title>Download your file</title>
{{#if dsn}}
{{> sentry dsn=dsn}}
{{/if}}
<script src="/bundle.js"></script>
<link rel="stylesheet" href="https://code.cdn.mozilla.net/fonts/fira.css" />
<link rel="stylesheet" type="text/css" href="/main.css" />
{{#if trackerId}}
{{> analytics trackerId=trackerId}}
{{/if}}
</head>
<body>
<div class="main-window">
<div id="download">
{{#if filename}}
<div class="title">
Your friend is sending you a file: <br />
{{{filename}}} ({{{filesize}}})
</div>
<div id="download-page-one">
<div>
<button id="download-btn" onclick="download()">Download File</button>
</div>
</div>
<div id="download-progress">
<div id="download-text">
Downloading File...
</div>
<div class="upload">
<!-- progress bar here -->
<div id="progress-bar"></div>
<div id="progress-text"></div>
</div>
</div>
<div class="send-new" id="send-file">
Send your own files
</div>
{{else}}
<div class="title">
This link has expired or never existed in the first place.
</div>
<div class="share-window">
<img src="/resources/link_expired.png" alt="Link expired" />
</div>
<div class="send-new" id="send-file">
Send your own files
</div>
{{/if}}
<div id="download">
<script src="/download.js"></script>
<div id="download-page-one">
<div class="title">
<span id="dl-filename"
data-l10n-id="downloadFileName"
data-l10n-args='{"filename": "{{filename}}"}'></span>
<span data-l10n-id="downloadFileSize"
data-l10n-args='{"size": "{{filesize}}"}'></span>
<span id="dl-bytelength" hidden="true">{{sizeInBytes}}</span>
<span id="dl-ttl" hidden="true">{{timeToExpiry}}</span>
</div>
<div class="description" data-l10n-id="downloadMessage"></div>
<img src="/resources/illustration_download.svg" id="download-img" data-l10n-id="downloadAltText"/>
<div>
<button id="download-btn" data-l10n-id="downloadButtonLabel"></button>
</div>
</div>
<!-- <ul id="downloaded_files">
</ul> -->
<div id="download-progress" hidden="true">
<div class="title"
data-l10n-id="downloadingPageProgress"
data-l10n-args='{"filename": "{{filename}}", "size": "{{filesize}}"}'>
</div>
<div class="description" data-l10n-id="downloadingPageMessage"></div>
<!-- progress bar here -->
<div class="progress-bar" id="dl-progress">
<div class="percentage">
<span class="percent-number"></span>
<span class="percent-sign">%</span>
</div>
</div>
<div class="upload">
<div class="progress-text">{{filename}}</div>
</div>
</div>
</body>
</html>
<a class="send-new" data-l10n-id="sendYourFilesLink"></a>
</div>

View File

@@ -1,96 +1,73 @@
<!DOCTYPE html>
<html>
<head>
<title>Firefox Fileshare</title>
{{#if dsn}}
{{> sentry dsn=dsn}}
{{/if}}
<script src="/bundle.js"></script>
<link rel="stylesheet" href="https://code.cdn.mozilla.net/fonts/fira.css" />
<link rel="stylesheet" type="text/css" href="/main.css" />
{{#if trackerId}}
{{> analytics trackerId=trackerId}}
{{/if}}
</head>
<body>
<div class="main-window">
<div id="page-one">
<div class="title">
Share your files quickly, privately and securely.
</div>
<div class="upload-window" ondrop="onUpload(event)" ondragover="allowDrop(event)">
<div id="upload-img"><img src="/resources/upload.svg" alt="Upload"/></div>
<div>
DRAG &amp; DROP
</div>
<div class="upload">
<div id="browse-text">
your file/folder here or
</div>
<div id="browse">
<form method="post" action="upload" enctype="multipart/form-data">
<label for="file-upload" class="file-upload">browse</label>
<input id="file-upload" type="file" onchange="onUpload(event)" name="fileUploaded" />
</form>
</div>
</div>
</div>
</div>
<div id="file-list">
<table id="uploaded-files">
<thead>
<tr>
<!-- htmllint attr-bans="false" -->
<th width="30%">File</th>
<th width="45%">Copy URL</th>
<th width="18%">Expires in</th>
<th width="7%">Delete</th>
<!-- htmllint tag-bans="$previous" -->
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div id="upload-progress">
<div class="title" id="upload-filename">
Uploading
</div>
<div class="upload-window">
<div id="upload-img"><img src="/resources/upload.svg" alt="Upload" /></div>
<div class="upload">
<!-- progress bar here -->
<div id="progress-bar"></div>
<div id="progress-text"></div>
</div>
</div>
</div>
<div id="share-link">
<div class="title">
Copy the link below to share your file!
</div>
<div class="share-window">
<img src="/resources/share.png" alt="Share" />
<div id="share-window-r">
<div id="copy">
<input id="link" type="url" value="" readonly/>
<button id="copy-btn">Copy</button>
</div>
<div>
This link expires after one download
</div>
</div>
</div>
<div class="send-new">
Send another file
</div>
</div>
<div id="page-one">
<script src="/upload.js"></script>
<div class="title" data-l10n-id="uploadPageHeader"></div>
<div class="description">
<div data-l10n-id="uploadPageExplainer"></div>
<a href="https://testpilot.firefox.com/experiments/send" class="link" data-l10n-id="uploadPageLearnMore"></a>
</div>
<div class="upload-window" >
<div id="upload-img"><img data-l10n-id="uploadSvgAlt" src="/resources/upload.svg"/></div>
<div id="upload-text" data-l10n-id="uploadPageDropMessage"></div>
<span id="file-size-msg"><em data-l10n-id="uploadPageSizeMessage"></em></span>
<form method="post" action="upload" enctype="multipart/form-data">
<label for="file-upload" id="browse"
data-l10n-id="uploadPageBrowseButton"></label>
<input id="file-upload" type="file" name="fileUploaded" />
</form>
</div>
</body>
</html>
<div id="file-list">
<table id="uploaded-files">
<thead>
<tr>
<!-- htmllint attr-bans="false" -->
<th width="35%" data-l10n-id="uploadedFile"></th>
<th width="25%" data-l10n-id="copyFileList"></th>
<th width="21%" data-l10n-id="expiryFileList"></th>
<th width="12%" data-l10n-id="deleteFileList"></th>
<!-- htmllint tag-bans="$previous" -->
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div id="upload-progress" hidden="true">
<div class="title" id="upload-filename" data-l10n-id="uploadingPageHeader"></div>
<div class="description"></div>
<!-- progress bar here -->
<div class="progress-bar" id="ul-progress">
<div class="percentage">
<span class="percent-number">0</span>
<span class="percent-sign">%</span>
</div>
</div>
<div class="upload">
<div class="progress-text"></div>
<div id="cancel-upload"
data-l10n-id="uploadingPageCancel"></div>
</div>
</div>
<div id="share-link" hidden="true">
<div class="title" data-l10n-id="uploadSuccessTimingHeader"></div>
<div id="share-window">
<div id="copy-text"></div>
<div id="copy">
<input id="link" type="url" value="" readonly/>
<button id="copy-btn" data-l10n-id="copyUrlFormButton"></button>
</div>
<button id="delete-file" data-l10n-id="deleteFileButton"></button>
<a class="send-new" id="send-new-completed" data-l10n-id="sendAnotherFileLink"></a>
</div>
</div>
<div id="upload-error" hidden="true">
<div class="title" data-l10n-id="errorPageHeader"></div>
<div class="expired-description" data-l10n-id="errorPageMessage"></div>
<img id="upload-error-img" data-l10n-id="errorAltText" src="/resources/illustration_error.svg"/>
<a class="send-new" id="send-new-error" data-l10n-id="sendAnotherFileLink"></a>
</div>

View File

@@ -0,0 +1,8 @@
{{#if dsn}}
window.dsn = '{{{dsn}}}';
{{/if}}
{{#if trackerId}}
window.trackerId = '{{{trackerId}}}';
{{/if}}
const MAXFILESIZE = {{{maxFileSize}}};
const EXPIRE_SECONDS = {{{expireSeconds}}};

View File

@@ -1 +1,48 @@
{{{body}}}
<!DOCTYPE html>
<html>
<head>
<title>Firefox Send</title>
<script src="/jsconfig.js"></script>
<link rel="stylesheet" type="text/css" href="/main.css" />
<link rel="stylesheet" href="https://code.cdn.mozilla.net/fonts/fira.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="defaultLanguage" content="en-US">
<meta name="availableLanguages" content="{{availableLanguages}}">
<link rel="icon" type="image/png" href="/resources/favicon-32x32.png" sizes="32x32" />
<link rel="localization" href="/locales/{locale}/send.ftl">
<script defer src="/l20n.min.js"></script>
</head>
<body>
<header class="header">
<div class="send-logo">
<a href="/">
<img src="/resources/send_logo.svg" alt="Send"/><h1 class="site-title">Send</h1>
</a>
<div class="site-subtitle">
<a href="https://testpilot.firefox.com" target="_blank">Firefox Test Pilot</a>
<div data-l10n-id="siteSubtitle">web experiment</div>
</div>
</div>
<a href="https://qsurvey.mozilla.com/s3/txp-firefox-send" rel="noreferrer noopener" class="feedback" target="_blank" data-l10n-id="siteFeedback">Feedback</a>
</header>
<div class="all">
{{{body}}}
</div>
<div class="footer">
<div class="legal-links">
<a href="https://www.mozilla.org"><img class="mozilla-logo" src="/resources/mozilla-logo.svg"/></a>
<a href="https://www.mozilla.org/about/legal" data-l10n-id="footerLinkLegal"></a>
<a href="https://testpilot.firefox.com/about" data-l10n-id="footerLinkAbout"></a>
<a href="/legal" data-l10n-id="footerLinkPrivacy"></a>
<a href="/legal" data-l10n-id="footerLinkTerms"></a>
<a href="https://www.mozilla.org/en-US/privacy/websites/#cookies" data-l10n-id="footerLinkCookies"></a>
</div>
<div class="social-links">
<a href="https://github.com/mozilla/send" target="_blank"><img class="github" src="/resources/github-icon.svg"/></a>
<a href="https://twitter.com/FxTestPilot" target="_blank"><img class="twitter" src="/resources/twitter-icon.svg"/></a>
</div>
</div>
</body>
</html>

12
views/legal.handlebars Normal file
View File

@@ -0,0 +1,12 @@
<div id="legal">
<div class="title" data-l10n-id="legalHeader"></div>
<div class="description" data-l10n-id="legalNoticeTestPilot">
<a href="https://testpilot.firefox.com/terms"></a>
<a href="https://testpilot.firefox.com/privacy"></a>
<a href="https://testpilot.firefox.com/experiments/send"></a>
</div>
<div class="description" data-l10n-id="legalNoticeMozilla">
<a href="https://www.mozilla.org/privacy/websites/"></a>
<a href="https://www.mozilla.org/about/legal/terms/mozilla/"></a>
</div>
</div>

View File

@@ -0,0 +1,8 @@
<div id="download">
<div class="title" data-l10n-id="expiredPageHeader"></div>
<div class="share-window">
<img src="/resources/illustration_expired.svg" id="expired-img" data-l10n-id="linkExpiredAlt"/>
</div>
<div class="expired-description" data-l10n-id="uploadPageExplainer"></div>
<a class="send-new" href="/" id="expired-send-new" data-l10n-id="sendYourFilesLink"></a>
</div>

View File

@@ -1,17 +0,0 @@
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', '{{{trackerId}}}', 'auto');
if (window.URL && document.referrer) {
ga("set", "referrer", (new URL(document.referrer)).origin);
} else {
ga("set", "referrer", "");
}
ga('set', 'anonymizeIp', true);
ga('send', 'pageview');
</script>

View File

@@ -1,3 +0,0 @@
<script>
window.dsn = '{{{dsn}}}';
</script>

View File

@@ -0,0 +1,11 @@
<div id="unsupported-browser">
<div class="title" data-l10n-id="notSupportedHeader"></div>
<div class="description" data-l10n-id="notSupportedDetail"></div>
<a id="dl-firefox" href="https://www.mozilla.org/firefox/new/?scene=2" target="_blank">
<img src="/resources/firefox_logo-only.svg" id="firefox-logo" alt="Firefox"/>
<div id="dl-firefox-text">Firefox<br>
<span data-l10n-id="downloadFirefoxButtonSub"></span>
</div>
</a>
<div class="unsupported-description" data-l10n-id="uploadPageExplainer"></div>
</div>