@@ -643,6 +643,13 @@ var elFinder = function(elm, opts, bootCallback) {
643643 */
644644 currentOpenCmd = null ,
645645
646+ /**
647+ * Current CSRF refresh request instance
648+ *
649+ * @type Object
650+ */
651+ currentCsrfRefresh = null ,
652+
646653 /**
647654 * Exec shortcut
648655 *
@@ -1410,6 +1417,97 @@ var elFinder = function(elm, opts, bootCallback) {
14101417 */
14111418 this . customHeaders = $ . isPlainObject ( this . options . customHeaders ) ? this . options . customHeaders : { } ;
14121419
1420+ /**
1421+ * CSRF header name for connector requests
1422+ *
1423+ * @type String
1424+ */
1425+ this . csrfHeaderName = 'X-elFinder-CSRF' ;
1426+
1427+ /**
1428+ * JSON response key for CSRF token
1429+ *
1430+ * @type String
1431+ */
1432+ this . csrfResponseKey = 'csrf' ;
1433+
1434+ /**
1435+ * JSON response key that marks a CSRF-triggered reload request
1436+ *
1437+ * @type String
1438+ */
1439+ this . csrfReloadKey = 'csrfReload' ;
1440+
1441+ /**
1442+ * Store or clear current CSRF token header
1443+ *
1444+ * @param {String } token
1445+ * @return void
1446+ */
1447+ this . setCsrfToken = function ( token ) {
1448+ if ( typeof token === 'string' && token ) {
1449+ self . customHeaders [ self . csrfHeaderName ] = token ;
1450+ } else {
1451+ delete self . customHeaders [ self . csrfHeaderName ] ;
1452+ }
1453+ } ;
1454+
1455+ /**
1456+ * Determine whether the response represents a CSRF refreshable failure
1457+ *
1458+ * @param {Object } xhr
1459+ * @param {Object } response
1460+ * @return {Boolean }
1461+ */
1462+ this . isCsrfReloadResponse = function ( xhr , response ) {
1463+ return ! ! ( xhr
1464+ && xhr . status === 403
1465+ && ! xhr . _csrfRefresh
1466+ && response
1467+ && response [ self . csrfReloadKey ] ) ;
1468+ } ;
1469+
1470+ /**
1471+ * Refresh CSRF token via open&init=1 without replaying the failed command
1472+ *
1473+ * @return {jQuery.Deferred }
1474+ */
1475+ this . refreshCsrfToken = function ( ) {
1476+ var cwdFile = self . cwd ( ) ,
1477+ target = ( cwdFile && cwdFile . hash ) || self . lastDir ( '' ) || self . startDir ( ) ;
1478+
1479+ if ( currentCsrfRefresh && currentCsrfRefresh . state ( ) === 'pending' ) {
1480+ return currentCsrfRefresh ;
1481+ }
1482+
1483+ currentCsrfRefresh = self . request ( {
1484+ data : { cmd : 'open' , target : target , init : 1 , tree : 1 } ,
1485+ preventDefault : true ,
1486+ preventFail : true ,
1487+ _csrfRefresh : true
1488+ } ) . always ( function ( ) {
1489+ currentCsrfRefresh = null ;
1490+ } ) ;
1491+
1492+ return currentCsrfRefresh ;
1493+ } ;
1494+
1495+ /**
1496+ * Start background CSRF refresh if current failure is refreshable
1497+ *
1498+ * @param {Object } xhr
1499+ * @param {Object } response
1500+ * @return {Boolean }
1501+ */
1502+ this . handleCsrfReload = function ( xhr , response ) {
1503+ if ( ! self . isCsrfReloadResponse ( xhr , response ) ) {
1504+ return false ;
1505+ }
1506+
1507+ self . refreshCsrfToken ( ) ;
1508+ return true ;
1509+ } ;
1510+
14131511 /**
14141512 * Any custom xhrFields to send across every ajax request
14151513 *
@@ -2443,6 +2541,7 @@ var elFinder = function(elm, opts, bootCallback) {
24432541 // check responseText, Is that JSON?
24442542 try {
24452543 data = JSON . parse ( xhr . responseText ) ;
2544+ xhr . _elfinderResponse = data ;
24462545 if ( data && data . error ) {
24472546 error = data . error ;
24482547 }
@@ -2496,7 +2595,13 @@ var elFinder = function(elm, opts, bootCallback) {
24962595 return dfrd . reject ( { error :[ 'errResponse' , 'errDataEmpty' ] } , xhr , response ) ;
24972596 } else if ( ! $ . isPlainObject ( response ) ) {
24982597 return dfrd . reject ( { error :[ 'errResponse' , 'errDataNotJSON' ] } , xhr , response ) ;
2499- } else if ( response . error ) {
2598+ }
2599+
2600+ if ( isOpen && ! ! data . init && Object . prototype . hasOwnProperty . call ( response , self . csrfResponseKey ) ) {
2601+ self . setCsrfToken ( response [ self . csrfResponseKey ] ) ;
2602+ }
2603+
2604+ if ( response . error ) {
25002605 if ( isOpen ) {
25012606 // check leafRoots
25022607 $ . each ( self . leafRoots , function ( phash , roots ) {
@@ -2735,6 +2840,12 @@ var elFinder = function(elm, opts, bootCallback) {
27352840 if ( error ) {
27362841 error . error = '' ;
27372842 }
2843+ } else if ( self . handleCsrfReload ( xhr , xhr . _elfinderResponse ) ) {
2844+ deffail = false ;
2845+ syncOnFail = false ;
2846+ if ( error ) {
2847+ error . error = '' ;
2848+ }
27382849 }
27392850 // abort xhr
27402851 xhrAbort ( ) ;
@@ -2812,6 +2923,7 @@ var elFinder = function(elm, opts, bootCallback) {
28122923 requestQueue . shift ( ) ( ) ;
28132924 }
28142925 } ) . fail ( error ) . done ( success ) ;
2926+ xhr . _csrfRefresh = ! ! opts . _csrfRefresh ;
28152927
28162928 if ( self . api >= 2.1029 ) {
28172929 xhr . _requestId = reqId ;
@@ -6846,6 +6958,8 @@ elFinder.prototype = {
68466958 self . trigger ( 'requestError' , errData ) ;
68476959 if ( errData . _getEvent && errData . _getEvent ( ) . isDefaultPrevented ( ) ) {
68486960 res . error = '' ;
6961+ } else if ( self . handleCsrfReload ( xhr , res ) ) {
6962+ res . error = '' ;
68496963 }
68506964 if ( res . _chunkfailure || res . _multiupload ) {
68516965 abort = true ;
0 commit comments