3

I'm working on a script that allows users to download files stored online. It always seems to work with image files, but with PDF's the browser sometimes downloads .PHP files instead of .PDF's.

I haven't tested the script with any other file types, so I don't know if the problem is limited to PDF's or if it would happen for other types as well.

Here's my code:

$mimeTypes = array(
    /* images */
    'png' => 'image/png', 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'bmp' => 'image/bmp',
    /* spreadsheets */
    'xl' => 'application/vnd.ms-excel', 'xls' => 'application/vnd.ms-excel', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'csv' => 'text/csv', 'sdc' => 'application/vnd.stardivision.calc', 'odc' => 'application/vnd.oasis.opendocument.chart',
    /* text files */
    'doc' => 'application/msword', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'odt' => 'application/vnd.oasis.opendocument.text', 'rtf' => 'application/rtf', 'rtx' => 'text/richtext',
    /* PDF */
    'pdf' => 'application/pdf'
);

$file = __DIR__ . '/files/' . $row['disk_name'];

header('Content-Description: File Transfer');
header('Content-Type: ' . $mimeTypes[$row['ext']]);
header('Content-Disposition: attachment; filename="'. $row['name'] .'"');
header('Content-Transfer-Encoding: binary');
header('Connection: Keep-Alive');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
readfile($file);
die();

The .PHP files that are downloaded are 1KB larger than the corresponding PDF files. In notepad, the extra content in a PDF file downloaded as .PHP looks like this:

Û¼T"gS_åœx¨fꙸx0]Ï~0î…[#X7beú
Œ¾){(‹Ã ‰ždêò¤ž
^`ô}\ȯDdìè¬#¸A,.Û­àÇ3ðuúÑÅIôo GP‘pð.bWædÎVª8¨‹ÃÚï~“½1ÚùYGrƒ^º9YçãžÁa0]žÿ{VP\§•ÑúÄ›\²/Óa4§ R   Â×ïYGrøå3¨$Ë  Køpf%„   HÐá@~Tyw«ðŠfPý—@òwæà– Ác8rI:2ëÅ*P5„*‚øïÖ`»´æí£³Èx€–T$W³d´ HÏy»…G˜ÊÃ?Ž=TÐð+PÊÃc6’q
¥üQ˜Q¦GØšOQò‹¹t‡€öGz‚‘«mØÌ&Æ5kPnd Ôs˜ù7Ê¢ø=à¦Uïºd"œ¸ÐîÌóÏlnŒVšÊ¤P[¾ò¸ª‹„mÊO/
‹xï$   Ƶ¤%0´7M?ÒÙdaã1ÓKùaPÌZó†A¬møY¨¿D›K²Ji;w €9ö>*®h³³†‚wRy¶=s+U\ñd¤™ýØ·ÇΗú®Q–kÈ(˜´jñWQd©¢à¦MÌw½Ÿt°Z±Þa¾co{Ö`ȳŸô¯òje©ÒPióBL$“ÓP/&´ÆÞµš9h–)lŸk  )
À„uÚº'Ûˆ“Ô_4~‹}]?óâùg3 Ô¨?ËL¡ê*¢‰“çS)  M³‘*LâGG…®sœùw;¤N`Åx½cëu  'oJæeÆ_M½ËÒXÈ…³„{ÈŃ)õ³P´œ»SI™P,éH@¸’ÄÌ^ÓÙb§[id˜_^çî>zflø€aÞ‹€% G‘,=ùI¦ÕaˆVEÃvoûl•>`ÂÆJ^V›ƒ
:"ˆi„ÄÐ’@€Ð—ìRy¯ '¡šË¢›Øƒ
Æ[&ÉF›­’_+Õ¢$ÜÜMãºÏ€u},w†¼BœÄA`R¤dYm)Œ$¤<},<³Ó_¥æËÒP|&Tj; •ê¥U0h|&Õ}©¸t‹s *1žõŽ2„E'…ÛnäYjë— Tg¡’v•[  ¥ì¤îK¨ä–t$ üñ\؉¸X€R+e^ªx;£ÌË&ä´R®ðúpR_Åíf ^,vžÒJcAŽ•<O€LR1%ªP¡Ãê–x]º¬#Îs?¨üVŽ*jæ ¤Úbª£ê£«â7îm]û$ ÇUfGÄo£ÒÛþO#ƒÞ©?t±ª·PN¹¡8¨i°<ôyZ_lGÞJÕ\Y{®Hõ X~
¸Å‘"H; «™
a™Žl%BÅׄ]Œ‹ešµR…JÏeÕÒ(gH©,ÓÚaIR/¤¬Ÿ#? J  ¸’„“ã.žÚ#§j¥Š³çŠtâŰ^)¹Ï(Pm°¢¨–ÕÒ‹ˆk)ÂÚý£Ò5 $W¬WPa&/çG1ÈDaÕû²Žâ:¥§š™”2HŸˆUë«WŒRF°“²yݳYEÂÁoØŠ?@‘dŠ-HVÑîÝ?jÆr)¨º7¥Bd–•u$¼&Eê®eùµRÂf,UË Û«‚•jõYšU²±Ú!ò#¼–aÐÍU°ÚsVTö
­ìÄ^úª•
Tصr‡gPùÅ™0©S”ûË‘.œÅå•   ³‘„ôjÞ6ËÂOçÌAj?j÷,¬ÎpRŸx}Äd$ÌpÓxÇnÚŽ3yä/rž©öU™ö¢‘ú²ÇGÁ£È¡/L<賎Drþy·³Ú¨•*nîªS±µN+¶IÓª5ô®üÕ¶fâ•×|+ë­vØ×J·¶>ž²ÞÅ&4k>HTTP/1.1 200 OK
Date: Fri, 26 Jul 2013 03:24:16 GMT
Server: Apache/2.4.2 (Win64) PHP/5.4.3
X-Powered-By: PHP/5.4.3
Expires: 0
Cache-Control: must-revalidate, post-check=0, pre-check=0
Pragma: public
Content-Description: File Transfer
Content-Disposition: attachment; filename="bst-bmp180-ds000-08.pdf"
Content-Transfer-Encoding: binary
Connection: Keep-Alive, Keep-Alive
Content-Length: 752201
Keep-Alive: timeout=5, max=99
Content-Type: application/pdf

What am I doing wrong that is causing this to happen?

Nate
  • 26,164
  • 34
  • 130
  • 214
  • That's bizarre. It's almost as if there is a keepalive connection, and the last request is being saved as output. It's the only thing I can think of, since your headers are in the file, and at the end of it... two things that should never happen. – Brad Jul 26 '13 at 03:35
  • @Brad and OP. Aren't you supposed to be reading the file first? `readfile($file);` is at the end over `die();` – Funk Forty Niner Jul 26 '13 at 03:40
  • try adding a `\n` to the end of each header i have had this problem before. and with this use "" instead of '' to quote it. – cmorrissey Jul 26 '13 at 03:40
  • @Fred - the readfile example shows the header functions being called first: http://php.net/manual/en/function.readfile.php – Nate Jul 26 '13 at 03:43
  • 1
    @Nate Ah ok. And have you tried using `ob_clean(); flush();` in conjunction with it, as stated on that page? Or will it not make a difference? – Funk Forty Niner Jul 26 '13 at 03:44
  • @Fred, Absolutely not. Headers should always be first. Output buffering is a way to get around this, but isn't efficient, and certainly not ever advised. Christopher, Your comment is not accurate. There is no need to add a new line (doing so may actually cause problems), and certainly no reason to use double quotes vs. single quotes. Using double quotes is less efficient, as PHP has to look for variables and what not, and can potentially lead to broken code, should you use the name of a variable. – Brad Jul 26 '13 at 03:45
  • @Nate, This isn't a code problem, but a server problem. Also, if you can reproduce it, I'd be interested in looking at a packet capture. – Brad Jul 26 '13 at 03:47
  • @Brad / OP Maybe a corrupt database/table? I say this, because every once in a while when I try to send out a newsletter from a database-driven script, it will sometimes skip rows and output an error and keep executing. – Funk Forty Niner Jul 26 '13 at 03:50
  • @Brad adding new lines '\n' is not a problem, IE needs them for most of these calls, and they don't interfere with other browsers. double quotes enables the '\n' to be parsed correctly. Im not spewing this out, this is based on testing these really silly problems. – cmorrissey Jul 26 '13 at 03:53
  • 1
    Newlines shouldn't be needed in `header()`, each of them defines a separate response header. PHP automatically separates them with newlines, it wouldn't work with any browser if it didn't. – Barmar Jul 26 '13 at 03:58
  • I suspect the issue is with sending the `Content-length` header. Try leaving this, as well as the keepalive-related headers, and let the webserver deal with these issues itself. – Barmar Jul 26 '13 at 04:02
  • 1
    @Fred, adding `ob_clean(); flush();` before `readfile()` worked! I didn't notice I was missing those lines somehow.. Want to make that an answer? – Nate Jul 26 '13 at 13:20
  • @Nate I'm glad that it works for you now Nate! And sure, I would love to make it as an answer, which I'm sure that it could help others, should they be faced with the same or similar problem. cheers – Funk Forty Niner Jul 26 '13 at 14:47
  • @ChristopherMorrissey, You certainly do not need extra newlines in your headers, for any browser. This is a basic part of HTTP. The headers are separated by new lines farther down the stack than your application. Adding extra ones shouldn't do anything, but a broken version of PHP may leave them in there, giving you `\n\r\n`, which browsers will think your headers are done. Besides, even if you did think you had to manually add these line breaks, the standard says `\r\n`, not `\n`. I think your testing methods were flawed when you came to this conclusion. – Brad Jul 26 '13 at 18:55

1 Answers1

2

Adding ob_clean(); and flush(); functions before the readfile(); function, could be something worth using, as per what the PHP manual states on the subject.

readfile() http://php.net/manual/en/function.readfile.php

ob_clean() http://php.net/manual/en/function.ob-clean.php

flush() http://php.net/manual/en/function.ob-flush.php

These functions are not present in your posted code.

Funk Forty Niner
  • 74,450
  • 15
  • 68
  • 141