3 * Jirafeau, your web file repository
4 * Copyright (C) 2015 Jerome Jutteau <jerome@jutteau.fr>
5 * Copyright (C) 2015 Nicola Spanti (RyDroid) <dev@nicola-spanti.info>
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 header('Content-Type: text/javascript');
22 define('JIRAFEAU_ROOT', dirname(__FILE__
) . '/../');
24 require(JIRAFEAU_ROOT
. 'lib/settings.php');
25 require(JIRAFEAU_ROOT
. 'lib/functions.php');
26 require(JIRAFEAU_ROOT
. 'lib/lang.php');
28 // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
29 var web_root
= "<?php echo $cfg['web_root']; ?>";
31 var lang_array
= <?php
echo json_lang_generator(null); ?
>;
32 var lang_array_fallback
= <?php
echo json_lang_generator("en"); ?
>;
34 function translate (expr
) {
35 if (lang_array
.hasOwnProperty(expr
)) {
36 var e
= lang_array
[expr
];
40 if (lang_array_fallback
.hasOwnProperty(expr
)) {
41 var e
= lang_array_fallback
[expr
];
45 return "FIXME: " + expr
;
48 function isEmpty(str
) {
49 return (!str ||
0 === str
.length
);
52 // Extend date object with format method
53 Date
.prototype
.format
= function(format
) {
54 format
= format ||
'YYYY-MM-DD hh:mm';
56 var zeropad
= function(number
, length
) {
57 number
= number
.toString();
59 while(number
.length
< length
)
60 number
= '0' + number
;
64 YYYY
: this
.getFullYear(),
65 MM
: zeropad(this
.getMonth() +
1),
66 DD
: zeropad(this
.getDate()),
67 hh
: zeropad(this
.getHours()),
68 mm
: zeropad(this
.getMinutes()),
71 sign
= (localDate
.getTimezoneOffset() > 0) ?
'-' : '+';
72 offset
= Math
.abs(localDate
.getTimezoneOffset());
73 hours
= zeropad(Math
.floor(offset
/ 60));
74 minutes
= zeropad(offset %
60);
75 return sign + hours +
":" + minutes
;
78 pattern
= '(' +
Object.keys(formats
).join(')|(') +
')';
80 return format
.replace(new RegExp(pattern
, 'g'), function(match
) {
81 return formats
[match
];
85 function dateFromUtcString(datestring
) {
86 // matches »YYYY-MM-DD hh:mm«
87 var m
= datestring
.match(/(\d+
)-(\d+
)-(\d+
)\s+
(\d+
):(\d+
)/);
88 return new Date(Date
.UTC(+m
[1], +m
[2] - 1, +m
[3], +m
[4], +m
[5], 0));
91 function dateFromUtcTimestamp(datetimestamp
) {
92 return new Date(parseInt(datetimestamp
) * 1000)
95 function dateToUtcString(datelocal
) {
97 datelocal
.getUTCFullYear(),
98 datelocal
.getUTCMonth(),
99 datelocal
.getUTCDate(),
100 datelocal
.getUTCHours(),
101 datelocal
.getUTCMinutes(),
102 datelocal
.getUTCSeconds()
106 function dateToUtcTimestamp(datelocal
) {
108 datelocal
.getUTCFullYear(),
109 datelocal
.getUTCMonth(),
110 datelocal
.getUTCDate(),
111 datelocal
.getUTCHours(),
112 datelocal
.getUTCMinutes(),
113 datelocal
.getUTCSeconds()
117 function convertAllDatetimeFields() {
118 datefields
= document
.getElementsByClassName('datetime')
119 for(var i
=0; i
<datefields
.length
; i++
) {
120 dateUTC
= datefields
[i
].getAttribute('data-datetime');
121 datefields
[i
].setAttribute('title', dateUTC +
' (GMT)');
122 datefields
[i
].innerHTML
= dateFromUtcString(dateUTC
).format('YYYY-MM-DD hh:mm (GMT O)');
126 function show_link (reference
, delete_code
, crypt_key
, date
)
129 document
.getElementById('uploading').style
.display
= 'none';
130 document
.getElementById('upload').style
.display
= 'none';
131 document
.getElementById('upload_finished').style
.display
= '';
132 document
.title
= "100% - <?php echo empty($cfg['title']) ? 'Jirafeau' : $cfg['title']; ?>";
135 var download_link_href
= 'f.php?h=' + reference
;
136 if (crypt_key
.length
> 0)
138 download_link_href +
= '&k=' + crypt_key
;
140 if (!!document
.getElementById('upload_finished_download_page'))
142 document
.getElementById('upload_link').href
= download_link_href
;
143 document
.getElementById('upload_link_text').innerHTML
= web_root + download_link_href
;
147 var filename
= document
.getElementById('file_select').files
[0].name
;
148 var b
= encodeURIComponent("<?php echo t("DL
"); ?> \"" + filename +
"\":") +
"%0D" +
"%0A";
149 b +
= encodeURIComponent(web_root + download_link_href
) +
"%0D" +
"%0A";
150 if (false == isEmpty(date
))
152 b +
= "%0D" +
"%0A" +
encodeURIComponent("<?php echo t("VALID_UNTIL
"); ?>: " + date
.format('YYYY-MM-DD hh:mm (GMT O)')) +
"%0D" +
"%0A";
153 document
.getElementById('upload_link_email').href
= "mailto:?body=" + b +
"&subject=" +
encodeURIComponent(filename
);
157 var delete_link_href
= 'f.php?h=' + reference +
'&d=' + delete_code
;
158 document
.getElementById('delete_link').href
= delete_link_href
;
159 document
.getElementById('delete_link_text').innerHTML
= web_root + delete_link_href
;
164 document
.getElementById('date').style
.display
= 'none';
167 document
.getElementById('date').innerHTML
= '<span class="datetime" title="'
168 +
dateToUtcString(date
) +
' (GMT)">'
169 + date
.format('YYYY-MM-DD hh:mm (GMT O)')
171 document
.getElementById('date').style
.display
= '';
174 // Preview link (if allowed)
175 if (!!document
.getElementById('preview_link'))
177 document
.getElementById('upload_finished_preview').style
.display
= 'none';
178 var preview_link_href
= 'f.php?h=' + reference +
'&p=1';
179 if (crypt_key
.length
> 0)
181 preview_link_href +
= '&k=' + crypt_key
;
184 // Test if content can be previewed
185 type
= document
.getElementById('file_select').files
[0].type
;
186 if ((type
.startsWith('image/')
187 || type
.startsWith('audio')
188 || type
.startsWith('text/plain')
189 || type
.startsWith('video/'))
190 && !type
.includes('image/svg+xml'))
192 document
.getElementById('preview_link').href
= preview_link_href
;
193 document
.getElementById('preview_link_text').innerHTML
= web_root + preview_link_href
;
194 document
.getElementById('upload_finished_preview').style
.display
= '';
198 // Direct download link
199 var direct_download_link_href
= 'f.php?h=' + reference +
'&d=1';
200 if (crypt_key
.length
> 0)
202 direct_download_link_href +
= '&k=' + crypt_key
;
204 document
.getElementById('direct_link').href
= direct_download_link_href
;
205 document
.getElementById('direct_link_text').innerHTML
= web_root + direct_download_link_href
;
207 // Hide preview and direct download link if password is set
208 if (document
.getElementById('input_key').value
.length
> 0)
210 if (!!document
.getElementById('preview_link'))
211 document
.getElementById('upload_finished_preview').style
.display
= 'none';
212 document
.getElementById('upload_direct_download').style
.display
= 'none';
216 function show_upload_progression (percentage
, speed
, time_left
)
218 document
.getElementById('uploaded_percentage').innerHTML
= percentage
;
219 document
.getElementById('uploaded_speed').innerHTML
= speed
;
220 document
.getElementById('uploaded_time').innerHTML
= time_left
;
221 document
.title
= percentage +
" - <?php echo empty($cfg['title']) ? 'Jirafeau' : $cfg['title']; ?>";
224 function hide_upload_progression ()
226 document
.getElementById('uploaded_percentage').style
.display
= 'none';
227 document
.getElementById('uploaded_speed').style
.display
= 'none';
228 document
.getElementById('uploaded_time').style
.display
= 'none';
229 document
.title
= "<?php echo empty($cfg['title']) ? 'Jirafeau' : $cfg['title']; ?>";
232 function upload_progress (e
)
234 if (e
== undefined || e
== null ||
!e
.lengthComputable
)
237 // Init time estimation if needed
238 if (upload_time_estimation_total_size
== 0)
239 upload_time_estimation_total_size
= e
.total
;
241 // Compute percentage
242 var p
= Math
.round (e
.loaded
* 100 / e
.total
);
245 p_str
= p
.toString() +
'%';
246 // Update estimation speed
247 upload_time_estimation_add(e
.loaded
);
249 var speed_str
= upload_time_estimation_speed_string();
250 speed_str
= upload_speed_refresh_limiter(speed_str
);
252 var time_str
= chrono_update(upload_time_estimation_time());
254 show_upload_progression (p_str
, speed_str
, time_str
);
257 function control_selected_file_size(max_size
, error_str
)
259 f_size
= document
.getElementById('file_select').files
[0].size
;
260 if (max_size
> 0 && f_size
> max_size
* 1024 * 1024)
262 pop_failure(error_str
);
263 document
.getElementById('send').style
.display
= 'none';
267 // add class to restyle upload form in next step
268 document
.getElementById('upload').setAttribute('class', 'file-selected');
270 document
.getElementById('options').style
.display
= 'block';
271 document
.getElementById('send').style
.display
= 'block';
272 document
.getElementById('error_pop').style
.display
= 'none';
273 document
.getElementById('send').focus();
277 function XHRErrorHandler(e
)
279 var text
= "${e.type}: ${e.loaded} bytes transferred"
283 function pop_failure (e
)
285 var text
= "<p>An error occured";
286 if (typeof e
!== 'undefined')
289 document
.getElementById('error_pop').innerHTML
= e
;
291 document
.getElementById('uploading').style
.display
= 'none';
292 document
.getElementById('error_pop').style
.display
= '';
293 document
.getElementById('upload').style
.display
= '';
294 document
.getElementById('send').style
.display
= '';
297 function add_time_string_to_date(d
, time
)
299 if(typeof(d
) != 'object' ||
!(d
instanceof Date
))
304 if (time
== 'minute')
306 d
.setSeconds (d
.getSeconds() +
60);
311 d
.setSeconds (d
.getSeconds() +
3600);
316 d
.setSeconds (d
.getSeconds() +
86400);
321 d
.setSeconds (d
.getSeconds() +
604800);
326 d
.setSeconds (d
.getSeconds() +
2592000);
329 if (time
== 'quarter')
331 d
.setSeconds (d
.getSeconds() +
7776000);
336 d
.setSeconds (d
.getSeconds() +
31536000);
342 function classic_upload (file
, time
, password
, one_time
, upload_password
)
344 // Delay time estimation init as we can't have file size
345 upload_time_estimation_init(0);
347 var req
= new XMLHttpRequest ();
348 req
.upload
.addEventListener ("progress", upload_progress
, false);
349 req
.addEventListener ("error", XHRErrorHandler
, false);
350 req
.addEventListener ("abort", XHRErrorHandler
, false);
351 req
.onreadystatechange
= function ()
353 if (req
.readyState
== 4 && req
.status
== 200)
355 var res
= req
.responseText
;
357 // if response starts with "Error" then show a failure
358 if (/^Error
/.test(res
))
364 res
= res
.split ("\n");
368 // convert time (local time + selected expiry date)
369 var localDatetime
= new Date();
370 if(!add_time_string_to_date(localDatetime
, time
))
372 pop_failure ('Error: Date can not be parsed');
375 expiryDate
= localDatetime
;
378 show_link (res
[0], res
[1], res
[2], expiryDate
);
382 pop_failure ("<?php echo t("ERR_OCC
"); ?>");
385 req
.open ("POST", 'script.php' , true);
387 var form
= new FormData();
388 form
.append ("file", file
);
390 form
.append ("time", time
);
392 form
.append ("key", password
);
394 form
.append ("one_time_download", '1');
395 if (upload_password
.length
> 0)
396 form
.append ("upload_password", upload_password
);
401 function check_html5_file_api ()
403 return window
.File
&& window
.FileReader
&& window
.FileList
&& window
.Blob
;
406 var async_global_transfered
= 0;
407 var async_global_file
;
408 var async_global_ref
= '';
409 var async_global_max_size
= 0;
410 var async_global_time
;
411 var async_global_transfering
= 0;
412 var async_global_last_code
;
414 function async_upload_start (max_size
, file
, time
, password
, one_time
, upload_password
)
416 async_global_transfered
= 0;
417 async_global_file
= file
;
418 async_global_max_size
= max_size
;
419 async_global_time
= time
;
421 var req
= new XMLHttpRequest ();
422 req
.addEventListener ("error", XHRErrorHandler
, false);
423 req
.addEventListener ("abort", XHRErrorHandler
, false);
424 req
.onreadystatechange
= function ()
426 if (req
.readyState
== 4 && req
.status
== 200)
428 var res
= req
.responseText
;
430 if (/^Error
/.test(res
))
436 res
= res
.split ("\n");
437 async_global_ref
= res
[0];
439 async_upload_push (code
);
442 req
.open ("POST", 'script.php?init_async' , true);
444 var form
= new FormData();
445 form
.append ("filename", async_global_file
.name
);
446 form
.append ("type", async_global_file
.type
);
448 form
.append ("time", time
);
450 form
.append ("key", password
);
452 form
.append ("one_time_download", '1');
453 if (upload_password
.length
> 0)
454 form
.append ("upload_password", upload_password
);
456 // Start time estimation
457 upload_time_estimation_init(async_global_file
.size
);
462 function async_upload_progress (e
)
464 if (e
== undefined || e
== null ||
!e
.lengthComputable
&& async_global_file
.size
!= 0)
467 // Compute percentage
468 var p
= Math
.round ((e
.loaded + async_global_transfered
) * 100 / (async_global_file
.size
));
471 p_str
= p
.toString() +
'%';
472 // Update estimation speed
473 upload_time_estimation_add(e
.loaded + async_global_transfered
);
475 var speed_str
= upload_time_estimation_speed_string();
476 speed_str
= upload_speed_refresh_limiter(speed_str
);
478 var time_str
= chrono_update(upload_time_estimation_time());
480 show_upload_progression (p_str
, speed_str
, time_str
);
483 function async_upload_push (code
)
485 async_global_last_code
= code
;
486 if (async_global_transfered
== async_global_file
.size
)
488 hide_upload_progression ();
489 async_upload_end (code
);
492 var req
= new XMLHttpRequest ();
493 req
.upload
.addEventListener ("progress", async_upload_progress
, false);
494 req
.addEventListener ("error", XHRErrorHandler
, false);
495 req
.addEventListener ("abort", XHRErrorHandler
, false);
496 req
.onreadystatechange
= function ()
498 if (req
.readyState
== 4)
500 if (req
.status
== 200)
502 var res
= req
.responseText
;
504 // This error may be triggered when Jirafeau does not receive any file in POST.
505 // This may be due to bad php configuration where post_max_size is too low
506 // comparing to upload_max_filesize. Let's retry with lower file size.
507 if (res
=== "Error 23")
509 async_global_max_size
= Math
.max(1, async_global_max_size
- 500);
510 async_upload_push (async_global_last_code
);
513 else if (/^Error
/.test(res
))
519 res
= res
.split ("\n");
521 async_global_transfered
= async_global_transfering
;
522 async_upload_push (code
);
527 if (req
.status
== 413) // Request Entity Too Large
529 // lower async_global_max_size and retry
530 async_global_max_size
= Math
.max(1, parseInt (async_global_max_size
* 0.8));
532 async_upload_push (async_global_last_code
);
537 req
.open ("POST", 'script.php?push_async' , true);
539 var start
= async_global_transfered
;
540 var end
= start + async_global_max_size
;
541 if (end
>= async_global_file
.size
)
542 end
= async_global_file
.size
;
543 var blob
= async_global_file
.slice (start
, end
);
544 async_global_transfering
= end
;
546 var form
= new FormData();
547 form
.append ("ref", async_global_ref
);
548 form
.append ("data", blob
);
549 form
.append ("code", code
);
553 function async_upload_end (code
)
555 var req
= new XMLHttpRequest ();
556 req
.addEventListener ("error", XHRErrorHandler
, false);
557 req
.addEventListener ("abort", XHRErrorHandler
, false);
558 req
.onreadystatechange
= function ()
560 if (req
.readyState
== 4 && req
.status
== 200)
562 var res
= req
.responseText
;
564 if (/^Error
/.test(res
))
570 res
= res
.split ("\n");
572 if (async_global_time
!= 'none')
574 // convert time (local time + selected expiry date)
575 var localDatetime
= new Date();
576 if(!add_time_string_to_date(localDatetime
, async_global_time
)) {
577 pop_failure ('Error: Date can not be parsed');
580 expiryDate
= localDatetime
;
583 show_link (res
[0], res
[1], res
[2], expiryDate
);
586 req
.open ("POST", 'script.php?end_async' , true);
588 var form
= new FormData();
589 form
.append ("ref", async_global_ref
);
590 form
.append ("code", code
);
594 function upload (max_size
)
596 var one_time_checkbox
= document
.getElementById('one_time_download');
597 var one_time
= one_time_checkbox
!== null ? one_time_checkbox
.checked
: false;
598 if (check_html5_file_api ())
602 document
.getElementById('file_select').files
[0],
603 document
.getElementById('select_time').value
,
604 document
.getElementById('input_key').value
,
606 document
.getElementById('upload_password').value
612 document
.getElementById('file_select').files
[0],
613 document
.getElementById('select_time').value
,
614 document
.getElementById('input_key').value
,
616 document
.getElementById('upload_password').value
621 var upload_time_estimation_total_size
= 42;
622 var upload_time_estimation_transfered_size
= 42;
623 var upload_time_estimation_transfered_date
= 42;
624 var upload_time_estimation_moving_average_speed
= 42;
626 function upload_time_estimation_init(total_size
)
628 upload_time_estimation_total_size
= total_size
;
629 upload_time_estimation_transfered_size
= 0;
630 upload_time_estimation_moving_average_speed
= 0;
632 upload_time_estimation_transfered_date
= d
.getTime();
635 function upload_time_estimation_add(total_transfered_size
)
637 // Let's compute the current speed
639 var speed
= upload_time_estimation_moving_average_speed
;
640 if (d
.getTime() - upload_time_estimation_transfered_date
!= 0)
641 speed
= (total_transfered_size
- upload_time_estimation_transfered_size
)
642 / (d
.getTime() - upload_time_estimation_transfered_date
);
643 // Let's compute moving average speed on 30 values
644 var m
= (upload_time_estimation_moving_average_speed
* 29 + speed
) / 30;
645 // Update global values
646 upload_time_estimation_transfered_size
= total_transfered_size
;
647 upload_time_estimation_transfered_date
= d
.getTime();
648 upload_time_estimation_moving_average_speed
= m
;
651 function upload_time_estimation_speed_string()
654 var s
= upload_time_estimation_moving_average_speed
* 1000;
662 else if (s
< 1000000)
664 res
= Math
.floor(s
/100) / 10;
669 res
= Math
.floor(s
/100000) / 10;
674 return res
.toString() +
' ' + scale
;
677 function milliseconds_to_time_string (milliseconds
)
679 function numberEnding (number
) {
680 return (number
> 1) ?
translate ('PLURAL_ENDING') : '';
683 var temp
= Math
.floor(milliseconds
/ 1000);
684 var years
= Math
.floor(temp
/ 31536000);
686 return years +
' ' +
translate ('YEAR') +
numberEnding(years
);
688 var days
= Math
.floor((temp %
= 31536000) / 86400);
690 return days +
' ' +
translate ('DAY') +
numberEnding(days
);
692 var hours
= Math
.floor((temp %
= 86400) / 3600);
694 return hours +
' ' +
translate ('HOUR') +
numberEnding(hours
);
696 var minutes
= Math
.floor((temp %
= 3600) / 60);
698 return minutes +
' ' +
translate ('MINUTE') +
numberEnding(minutes
);
700 var seconds
= temp %
60;
702 return seconds +
' ' +
translate ('SECOND') +
numberEnding(seconds
);
704 return translate ('LESS_1_SEC');
707 function upload_time_estimation_time()
709 // Estimate remaining time
710 if (upload_time_estimation_moving_average_speed
== 0)
712 return (upload_time_estimation_total_size
- upload_time_estimation_transfered_size
)
713 / upload_time_estimation_moving_average_speed
;
716 var chrono_last_update
= 0;
717 var chrono_time_ms
= 0;
718 var chrono_time_ms_last_update
= 0;
719 function chrono_update(time_ms
)
723 // Don't update too often
724 if (d
.getTime() - chrono_last_update
< 3000 &&
725 chrono_time_ms_last_update
> 0)
726 chrono
= chrono_time_ms
;
729 chrono_last_update
= d
.getTime();
730 chrono_time_ms
= time_ms
;
732 chrono_time_ms_last_update
= d
.getTime();
735 // Adjust chrono for smooth estimation
736 chrono
= chrono
- (d
.getTime() - chrono_time_ms_last_update
);
738 // Let's update chronometer
741 time_str
= milliseconds_to_time_string (chrono
);
745 var upload_speed_refresh_limiter_last_update
= 0;
746 var upload_speed_refresh_limiter_last_value
= '';
747 function upload_speed_refresh_limiter(speed_str
)
750 if (d
.getTime() - upload_speed_refresh_limiter_last_update
> 1500)
752 upload_speed_refresh_limiter_last_value
= speed_str
;
753 upload_speed_refresh_limiter_last_update
= d
.getTime();
755 return upload_speed_refresh_limiter_last_value
;
759 document
.addEventListener('DOMContentLoaded', function(event
) {
760 // Search for all datetime fields and convert the time to local timezone
761 convertAllDatetimeFields();
764 // Add copy event listeners
765 function copyLinkToClipboard(link_id
) {
766 var focus
= document
.activeElement
;
767 var e
= document
.getElementById(link_id
);
769 var tmp
= document
.createElement("textarea");
770 document
.body
.appendChild(tmp
);
771 tmp
.textContent
= e
.href
;
773 tmp
.setSelectionRange(0, tmp
.value
.length
);
774 document
.execCommand("copy");
775 document
.body
.removeChild(tmp
);
780 function addCopyListener(button_id
, link_id
) {
781 if(document
.getElementById(button_id
)){
782 document
.getElementById(button_id
)
783 .addEventListener("click", function() {
784 copyLinkToClipboard(link_id
);});