PHP: Get file extension not working on uploads to S3

一个人想着一个人 提交于 2019-11-28 01:03:32
Robbie

The variable $filename (and thus "${filename}") is NOT IN SCOPE at line 1053 of your code (line numbering based on raw paste from pastebin).

So, no matter what you do, you'll never find the extension of a variable that does not exist.


And I've finally worked out what you're doing. I presume this is an extension of PHP: Rename file before upload

Simple answer: you can't do it as you envisage.Why - the '$filename' is not parsed at the time that URL is created, but the variable is passed to Amazon S3 and handled there.

The solution

So, the only option I can think of is to have use the "successRedirect" parameter to point to another URL. That URL will receive the "bucket" and "key" as query parameters from Amazon (http://doc.s3.amazonaws.com/proposals/post.html#Dealing_with_Success). Point that to a PHP script that renames the file on Amazon S3 (copy + delete), then redirects the user to another success screen.

So,

in your code, line 34,

  1. add a fully qualified URL to a new php script file you're going to write.
  2. the php script wil get the bucket and key passed to it
  3. Create the new filename from the "key"
  4. use the function "public static function copyObject($srcBucket, $srcUri, $bucket, $uri)" to copy the uploaded file to the new name
  5. then delete the original (using deleteObject($bucket, $uri))
  6. then redirect the user to where you want to send them

That will do exactly what you want.


In response to your comments "Is this the only way - what about the costs as Amazon charge per request?"

Delete requests are free. No data transfer costs when moving on the same bucket (or even in the same region). So this solution (which is the only way without you transferring to an intermediate server, renaming and uploading) it doubles the cost of upload a from 1c per 1000 uploads to 2c per 1000 uploads. It's taken me 10 minutes @ $200/hour to find that out and respond = $33 = 1,666,666 uploads! Costs pale a bit when you do the maths :)

Compare with the other solution: do a post to an webserver, rename the file and then upload from the webserver: you move all the bandwidth from the clinet tdirectly to yourself - twice. And this also introduces risk and increased possible failure points.


In response to "Doesn't work. I you upload a file then the old one gets deleted"

I would assusme this is not a problem as you upload a file and then rename it within a second or two. But if you want ot gurantee each file gets uploaded, then you need to do a lot more than create a random filename anyway:

  1. have your "final" bucket
  2. for each upload, create a temporary bucket (that's 1c per 1000 buckets, if you're worried on costs)
  3. upload to temporary bucket
  4. create random name, check if does not exist in final bucket (that 1c per 1000 checks)
  5. copy file to final bucket (with new name)
  6. delete uploaded file as well as the bucket.
  7. periodically clean up buckets where the file uploads were not complete.
$fileSplit = explode('.',$filename);
$extension = '.'.$fileSplit[count($fileSplit) - 1];

This explode() divides up the file name into an array with periods as delimiters, and then grabs the last piece of the array (incase a file name is foo.bar.jpg), and puts a period in front of it. This should get you the desired extension to append it to the rand_string(5).

$params->key = rand_string(5).$extension;

I think something as simple as below should work to extract file extension from the file-name:

function getFileExtension($fileName)
{
    $ext = '';
    if(strpos($fileName, ".") !== false)
    {
        $ext = end(explode(".", $fileName));
    }
    return $ext;
}

if you're uploading images try this

$dim = getimagesize($file);
$type = $dim[2];
if( $type == IMAGETYPE_JPEG ) $ext=".jpg"; 
if( $type == IMAGETYPE_GIF ) $ext=".gif"; 
if( $type == IMAGETYPE_PNG ) $ext=".png";

$params->key = rand_string(5).$ext;

What happends if you:

$filename = "${filename}";
echo $filename;
die();

Do you get something like 'Dog.png'? If you don't there is something wrong in the way you are getting the filename. If you do get something like 'Dog.png', here is what I use to get the file extension.

$pieces = explode('.',trim($filename));
$extension = end($pieces);

Then you should be able to do this:

$params->key = rand_string(5).'.'.$extension;

You need to first find out what the original extension is and not rename the entire file. So keep the extension and rename de file name.

Assuming you have image name in $image_name:

$image_name = "image.png";
$random_string = "random";

list($filename,$fileext) = explode(".",$image_name);
$new_name = $random_string.'.'.$fileext;
rename($image_name,$new_name);  

ok here's another try that I used when I had trouble getting the extension on the server side. what I did was, I used javascript to extract the file extension and the send it via post.

<script type="text/javascript" >
function fileinfo() {
var file = document.form.file.value;
document.getElementById("ext").value = file.split('.').pop();
document.myform.submit();   
}
</script>
<form name="myform" enctype="multipart/form-data" onsubmit="fileinfo();">
<input type="file" name="file">
<input type="hidden" name="ext">
//rest of the form
</form>

in the next php file you can directly use $_POST['ext'] as extension. hope that helped. let me know if you have any trouble implementing this

i am using this in my websites (and works fine for years):

$file = 'yourOriginalfile.png';

//get the file extension
$fileExt = substr(strrchr($file, '.'), 1);

//create a random name and add the original extension
$fileUniqueName = md5(uniqid(mktime())) . '.' . $fileExt;

rename($file,$fileUniqueName); 

your function generates too short filenames (5 characters), this way creates longer filenames, avoiding to collide the file names.

example output: aff5a25e84311485d4eedea7e5f24a4f.png

It appears what's actually going on is rather than fully producing the filename right now, you're in effect passing a very small 'program' through the interface so it can then produce the filename later (when the variable $filename exists and is in scope). The other side of the interface eventually executes that 'program' you pass in, which produces the modified filename. (Of course passing a 'program' to something else to execute later doesn't tend to make debugging real easy:-)

(It's of course up to you whether you want to "make this work" or "do it a different way". "Different ways" typically involve renaming or copying the file yourself before you even try to invoke the upload interface, and are described in other answers.)

If you decide you want to "make it work", then the entire filename parameter needs to be a program, rather than just part of it. This somewhat uncommon functionality typically involves enclosing the entire string in single quotes. (You also need to do something about existing single quote marks inside the string so they don't terminate the quoted string too soon. One way is to quote each of them with a backslash. Another way that may look cleaner and usually works is to replace them with double quotes.) In other words, I believe the code below will work for you (I haven't got the right environment to test it, so I'm not sure).

$file_extension = 'substr(strrchr(${filename}, "."), 1)';

$params->key = rand_string(5).$file_extension;

(Once you get it working, you might want to revisit your naming scheme. Perhaps the name needs to be a little longer. Or perhaps it should include some identifiable information {like today's date, or the original name of the file}. You may hit on something like $file_base.rand_string(7).$file_extension.

Untested, but simple enough to work:

$ext = pathinfo($filename, PATHINFO_EXTENSION);

will return the extension part (without the '.')

A simple solution to re-name a file and compute the extension:

$fileName = 'myRandomFile.jpg';

// separate the '.'-separated parts of the file name
$parts = explode( '.', $fileName );

// Solution will not work, if no extension is present
assert( 1 < count( $parts ) );

// keep the extension and drop the last part
$extension = $parts[ count( $parts ) - 1 ];
unset( $parts[ count( $parts ) - 1 ] );

// finally, form the new file name
$newFileName = md5( 'someSeedHere' + implode( '.', $parts )) . '.' . $extension;

echo $extension             // outputs jpg
   . ' - ' 
   . $newFileName           // outputs cfcd208495d565ef66e7dff9f98764da.jpg
   ;

Note, that md5() is always 32 bytes long and non unique regarding the computed value. For for many practical instances, it's unique enough.

Addendum

Additionally, you may use this solution to trace variable changes:

abstract class CSTReportDelegate {

    abstract public function emitVariableChange( $variableName, $oldValue, $newValue );
    abstract public function emitVariableSetNew( $variableName, $newValue );

}

class CSTSimpleReportDelegate extends CSTReportDelegate {

    public function emitVariableChange( $variableName, $oldValue, $newValue ) {
        echo '<br />[global/change] '. $variableName . ' : ' . print_r( $oldValue, true ) . ' &rarr; ' . print_r( $newValue, true );
    }

    public function emitVariableSetNew( $variableName, $newValue ) {
        echo '<br />[global/init] '. $variableName . '   &rarr; ' . print_r( $newValue, TRUE );
    }

}


class CSysTracer {

    static protected 
        $reportDelegate;

    static private 
        $globalState = array();

    static private  
        $traceableGlobals = array();

    static private 
        $globalTraceEnabled = FALSE;

    const 
        DEFAULT_TICK_AMOUNT = 1;

    static public 
    function setReportDelegate( CSTReportDelegate $aDelegate ) {
        self::$reportDelegate = $aDelegate;
    }


    static public 
    function start( $tickAmount = self::DEFAULT_TICK_AMOUNT ) {

        register_tick_function ( array( 'CSysTracer', 'handleTick' ) );

    }


    static public 
    function stop() {

        unregister_tick_function( array( 'CSysTracer', 'handleTick' ) );

    }

    static public 
    function evalAndTrace( $someStatement ) {

        declare( ticks = 1 ); {
            self::start();
            eval( $someStatement );
            self::stop();
        }
    }

    static public 
    function addTraceableGlobal( $varName ) {

        if ( is_array( $varName )) {
            foreach( $varName as $singleName ) {
                self::addTraceableGlobal( $singleName ); 
            }
            return;
        }

        self::$traceableGlobals[ $varName ] = $varName;

    }

    static public 
    function removeTraceableGlobal( $varName ) {
        unset( self::$traceableGlobals[ $varName ] );   
    }

    /**
     * Main function called at each tick. Calls those functions, which
     * really perform the checks.
     * 
     */
    static public 
    function handleTick( ) {

        if ( TRUE === self::$globalTraceEnabled ) { 
            self::traceGlobalVariable();
        }

    }

    static public 
    function enableGlobalsTrace() {
        self::$globalTraceEnabled = TRUE;   
    }


    static public 
    function disableGlobalsTrace() {
        self::$globalTraceEnabled = FALSE;  
    }

    static public 
    function traceGlobalVariable( ) {

        foreach( self::$traceableGlobals as $aVarname ) {

            if ( ! isset( $GLOBALS[ $aVarname ] )) {
                continue;
            }

            if ( ! isset( self::$globalState[ $aVarname ] ) ) {

                self::$reportDelegate->emitVariableSetNew( $aVarname, $GLOBALS[ $aVarname ] );
                self::$globalState[ $aVarname ] = $GLOBALS[ $aVarname ];
                continue;
            }

           if ( self::$globalState[ $aVarname ] !== $GLOBALS[ $aVarname ]) {

             self::$reportDelegate->emitVariableChange( $aVarname, self::$globalState[ $aVarname ], $GLOBALS[ $aVarname ] );

           }

           self::$globalState[ $aVarname ] = $GLOBALS[ $aVarname ];

        }

    }

}

A sample use case:

ini_set("display_errors", TRUE);
error_reporting(E_ALL);

require_once( dirname( __FILE__ ) . '/CStatementTracer.inc.php' );

/* Ticks make it easy to have a function called for every line of PHP
 *  code. We can use this to track the state of a variable throughout
 * the execution of a script.
 */



CSysTracer::addTraceableGlobal( array( 'foo', 'bar' ));

CSysTracer::setReportDelegate( new CSTSimpleReportDelegate() ); 
CSysTracer::enableGlobalsTrace();

CSysTracer::start(); 
declare( ticks = 1 );

   //
   // At this point, tracing is enabled. 
   // Add your code or call your functions/methods here
   //

CSysTracer::stop();

How about this?

$temp = rand_string(5).'${filename}';         //should be 3Sf5fDog.png
$ext = pathinfo($temp, PATHINFO_EXTENSION); //should be .png
$temp2 = rand_string(5) . $ext;             //should be 4D47a.png
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!