Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS 10 won't send session ID when trying to deliver mp4 via X-Sendfile

I have a PHP file who's sole job is to check if a user is logged in + if a session variable is set, then delivers a file via nginx X-Sendfile. It works perfectly on any desktop browser, and previously on any mobile browser - but fails on any ios 10 browser with mp4s only. What makes little sense to me is that if I comment the die() line, it works as expected, but the php code should never even get into that line.

<?php
require_once('authConfig.php');
$userInfo = $auth0->getUser();
session_start();
include('cururl.php');
$murl = curPageURL();
parse_str($murl, $result);
$filename=$result['file'];
$folder=$result['folder'];
if (!$userInfo || !isset($_SESSION[$folder])) {
        header('Location:login.php');
        die();
}
$file="/var/www/uploads/" . $folder . "/" . $filename;
$aliasedFile = '/protected/' . $folder .'/' . $filename; 
$realFile = $file; 
header('Cache-Control: public, must-revalidate');
header('Pragma: no-cache');
header('Content-Type: ' . mime_content_type($file));
header('Content-Length: ' .(string)(filesize($realFile)) );
header('X-Accel-Redirect: ' . $aliasedFile);
exit;
?>

I can confirm that $_SESSION[$folder] is actually set by changing the code to:

if(isset($_SESSION[$folder])){
   echo "OK";
   die();
} 

UPDATE: Here's the access log from the iPhone. It should have access to the file.

10.0.0.1 forwarded for ..., 10.0.0.1 - - [16/Sep/2016:10:07:20 -0400]  "GET /xfiles.php?&file=001.mp4&folder=NNx6659rvB HTTP/1.0" 200 1944706 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/14A403 Safari/602.1"
10.0.0.1 forwarded for ..., 10.0.0.1 - - [16/Sep/2016:10:07:21 -0400]  "GET /xfiles.php?&file=001.mp4&folder=NNx6659rvB HTTP/1.0" 302 0 "https://www.sonoclipshare.com/xfiles.php?&file=001.mp4&folder=NNx6659rvB" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/14A403 Safari/602.1"
10.0.0.1 forwarded for ..., 10.0.0.1 - - [16/Sep/2016:10:07:21 -0400]  "GET /login.php HTTP/1.0" 200 4166 "https://www.sonoclipshare.com/xfiles.php?&file=001.mp4&folder=NNx6659rvB" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/14A403 Safari/602.1"

UPDATE2: If I change the php file to examine the variables more closely:

<?php
require_once('authConfig.php');
$userInfo = $auth0->getUser();
session_start();
include('cururl.php');
$murl = curPageURL();
parse_str($murl, $result);
$filename=$result['file'];
$folder=$result['folder'];
print("info: $userInfo <br>SESSION: $_SESSION[$folder] <br>");
if(!$userInfo || !isset($_SESSION[$folder])) {
   print("exiting");
}else {
   print("sending file");
}

I'm getting a result of:

info: Array
SESSION: NNc6659rvB
sending file

when logged in, and:

info:
SESSION:
exiting

when not logged in. It should be working as expected.

UPDATE3:
Adding var_dump:

if(!$userInfo || !isset($_SESSION[$folder])) {
        var_dump($userInfo);
        var_dump($_SESSION[$folder]);
        //header('Location:login.php');
        exit();
}
echo "sending file";

I get NULL NULL when not logged in and sending file when logged in. No echo from the loop. iOS acts like it's loading the mp4, shows the play button but no video unless I remove the exit() line.

UPDATE4: If I replace die() with exit(), same effect. Replacing it with a nonsense function to kill the script also have the same effect. For some reason it gets into the if statement when I request an mp4 only.

SOLUTION: I had to pass the session ID to the page as a query string and then set it manually. This works well for my use case, as this php page shouldn't ever link out to other pages, exposing the session ID as a referral link. Not 100% ideal, but it'll bridge me until Apple pushes an iOS fix. Oh, and I removed the authConfig code check as this was redundant. The session variable can't be set if the user isn't already logged in.

include('cururl.php');
$murl = curPageURL();
parse_str($murl, $result);
$filename=$result['file'];
$folder=$result['folder'];
$session_id=$result['session_id'];
session_id($session_id);
session_start();
if(empty($_SESSION[$folder])) {
        echo "redirecting";
        exit(header("Location: login.php"));
}
$file="/var/www/uploads/" . $folder . "/" . $filename;
$aliasedFile = '/protected/' . $folder .'/' . $filename; //this is the nginx alias of the file path
$realFile = $file; //this is the physical file path
header('Cache-Control: public, must-revalidate');
header('Pragma: no-cache');
header('Content-Type: ' . mime_content_type($file));
header('Content-Length: ' .(string)(filesize($realFile)) );
header('X-Accel-Redirect: ' . $aliasedFile);
exit;
like image 211
UltrasoundJelly Avatar asked Sep 16 '16 02:09

UltrasoundJelly


2 Answers

Ilmari has told you the situation: $_SESSION[$folder] is not being set when the mp4 is requested. Which means the iOS is not sending sessionID back.

Removing the die will "fix" the problem as the code simply continues, but it's a red herring. It's possibly to issue a "redirect header" but if you don't exit there will be other headers that will essentially nullify the redirect request. Always have an exit (or die) after issuing a redirect header.

What you need to check for is if $_SESSION is set for those requests. You won't find that in the Apache/NGNIX access logs, but will need to log it yourself. Simplest case is to add file_put_contents('\tmp\debug.log\, print_r($_SESSION, true)); into the code. (Will overwrite each run, use fopen if you want to append). Then chase back to see what cookies are being sent (or not sent) using the same logging. But something is not sending the sessionID.

As for a solution: that's tricky. Until it's fixed properly, perhaps you can use IP address? Not as reliable (in fact, very unreliable), but if a user is logged on using a particular IP, then allow any request from that IP? Or experiment using your own cookies / session system to see if they behave the same way. That's all I can think of for now.


Something from the Apple Forums: https://forums.developer.apple.com/thread/60688

Safari seems to be only adding those cookies whose path is different from the request url path. But this seesm to work on other browsers on IOS9 as these may be creating the cookies on the root of the domain.

So try changing the path of the request.

like image 140
Robbie Avatar answered Nov 20 '22 00:11

Robbie


Try it (without die/exit):

<?php

require_once 'authConfig.php'; // ok this *_once
$userInfo = $auth0->getUser();
session_start(); // why this line after get userinfo? probably this line must be place before including authConfig.php
if (!$userInfo || !isset($_SESSION[$folder])) {
    header('Location:login.php');
} else {
    include 'cururl.php'; // why this not *_once?
    $murl = curPageURL();
    parse_str($murl, $result);
    $filename    = $result['file'];
    $folder      = $result['folder'];
    $file        = '/var/www/uploads/' . $folder . '/' . $filename;
    $aliasedFile = '/protected/' . $folder .'/' . $filename; 
    $realFile    = $file; 
    header('Cache-Control: public, must-revalidate');
    header('Pragma: no-cache');
    header('Content-Type: ' . mime_content_type($file));
    header('Content-Length: ' . filesize($realFile)); // dont require cast to string because concatenation
    header('X-Accel-Redirect: ' . $aliasedFile);
}
like image 1
Deep Avatar answered Nov 19 '22 23:11

Deep