PHP: Get file extension not working on uploads to S3

房东的猫 提交于 2019-12-17 16:46:12

问题


I am using the Amazon S3 API to upload files and I am changing the name of the file each time I upload.

So for example:

Dog.png > 3Sf5f.png

Now I got the random part working as such:

function rand_string( $length ) {
            $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";  

            $size = strlen( $chars );
            for( $i = 0; $i < $length; $i++ ) {
                $str .= $chars[ rand( 0, $size - 1 ) ];
            }

            return $str;
        }   

So I set the random_string to the name parameter as such:

$params->key = rand_string(5);

Now my problem is that this wont show any extension. So the file will upload as 3Sf5f instead of 3Sf5f.png.

The variable $filename gives me the full name of the file with its extension.

If I use $params->key = rand_string(5).'${filename}'; I get:

3Sf5fDog.png

So I tried to retrieve the $filename extension and apply it. I tried more than 30 methods without any positive one.

For example I tried the $path_info(), I tried substr(strrchr($file_name,'.'),1); any many more. All of them give me either 3Sf5fDog.png or just 3Sf5f.

An example of what I tried:

// As @jcinacio pointed out. Change this to: 
//
//   $file_name = "${filename}";
//
$file_name = '${filename}'  // Is is wrong, since '..' does not evaluate 

$params->key = rand_string(5).$file_name;
=
3Sf5fDog.png

.

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

$params->key = rand_string(5).$file_name;
=
3Sf5f

.

$filename = "example.png"   // If I declare my own the filename it works.
$file_name = substr(strrchr('${filename}', '.'), 1);

$params->key = rand_string(5).$file_name;
=
3Sf5f.png

The entire class file: http://pastebin.com/QAwJphmW (there are no other files for the entire script).

What I'm I doing wrong? This is really frustrating.


回答1:


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.



回答2:


$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;



回答3:


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;
}



回答4:


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;



回答5:


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;



回答6:


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);  



回答7:


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




回答8:


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




回答9:


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.




回答10:


Untested, but simple enough to work:

$ext = pathinfo($filename, PATHINFO_EXTENSION);

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




回答11:


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();



回答12:


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


来源:https://stackoverflow.com/questions/10574614/php-get-file-extension-not-working-on-uploads-to-s3

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!