View Full Version : Path Magic
jscheuer1
07-18-2010, 06:05 AM
OK, so I'll go into all of the gory details if I must. But here's the concept:
I have two absolute paths - say:
http:/somedomain.com/gallery/
http:/somedomain.com/gallery/images/
Now, just about anyone knows that if I'm in the second one, the first one is:
../
and:
http:/somedomain.com/gallery/images/../
But I could be anywhere. If I know the absolute path of where I am (#2 in the example), and the absolute path of where I want to reference (#1 in the example), is there a PHP function (or can one be created) that will give me ../, or whatever it might be (given the possibility that #1 and #2 might be reversed or entirely different, but will always be on the same domain)?
In other words, can we do something like so:
function findRelativePath($absPathOne, $absPathTwo){
// work some magic here
return relativePath from $absPathTwo to $absPathOne;
}
There's a function called realpath() (http://us2.php.net/manual/en/function.realpath.php) that does the exact opposite. Look at the second user's example (posted by jpic) for a possible solution (I didn't try it out, though).
jscheuer1
07-18-2010, 09:20 PM
There's a function called realpath() (http://us2.php.net/manual/en/function.realpath.php) that does the exact opposite. Look at the second user's example (posted by jpic) for a possible solution (I didn't try it out, though).
Thanks, I stumbled upon that earlier today and have already implemented it. It works fine for my purposes. I had to add a trailing slash to the output though. I suppose that for some other purposes it could be fine 'as is', though I'm not sure what purposes those would be.
just out of curiosity, why do you want to use relative paths when you know the absolute paths?
jscheuer1
07-18-2010, 09:58 PM
I'm working on a fusion of PHP Photo Album:
http://www.dynamicdrive.com/dynamicindex4/php-photoalbum.htm
and image moderation.
This somewhat idle task was inspired by this thread:
http://www.dynamicdrive.com/forums/showthread.php?t=55981
where I believe I worked things out to the OP's satisfaction.
But it got me to thinking . . .
One of the requirements of the modifications in that thread is that one know the relative path back from the folder with the images in it to the folder with the page that views those images. It's either that or (I imagine) do a lot of rewriting of the ddphpalbum.js file, which I'm trying to keep intact as much as possible so that it can still be used on the same server for a conventional PHP Photo Album page. I suppose a more creative approach to the issue of how to display the desired 'deleted' image as a placeholder might also suffice.
I was thinking of copying it to the images folder as the filename that was deleted. That actually could work out well and cut down on a lot of code. But if the file type (.jpg, .png, etc.) doesn't agree with the deleted image, there will be other issues. And we begin to get a lot of files lying around that we really don't need. I could have one for each supported image type and do a cleanup routine, but this whole tack seems messy.
I''ve already added viewing the larger image before deciding on deletion, as well as a way to store the deleted image in a deleted folder (recycle bin).
Next are a ways to restore all, restore one, and to clean out the contents of the "recycle bin". These should all be fairly simple.
I would appreciate you having a look at my unlink.php:
<?php
$file = isset($_GET['del'])? $_GET['del'] : false;
if($file and file_exists($file)){
$file = basename($file, '.php'); // should limit deletion to the folder this page is in and skip the getalbumpics.php & this file - test!
}
$success = false;
if($file and file_exists($file)){
@$success = unlink($file);
}
if($success){
echo $file . ' successfully deleted.';
} else {
echo 'There was a problem deleting ' . $file;
}
?>
Do you think that the way I've used basename is sufficient to prevent the script from being used to delete other files outside the folder? Do you think it's vulnerable to any sort of injection via its query string that could make it do something bad?
jscheuer1
07-18-2010, 10:31 PM
Oh, and it occurred to me that I didn't really answer your question.
The relative path is needed to get from the typical array entry for an image:
"someimage.jpg"
Which will only be looked for in what is known as the baseurl in the script, an absolute path to the images folder generated by the getalbumpics.php script.
If I know that path and the path where I want my placeholder to be (for many reasons it's good not to have it in the same folder as the images), I can "get there from here" by changing the image array entry to the placeholder filename preceded by the relative path from the images folder to wherever the placeholder image is.
The javascript will resolve it as something like:
http://www.somedomain.com/gallery/images/../placeholder.gif
If I use the absolute path though, the javascript will resolve it as something like:
http://www.somedomain.com/gallery/images/http://www.somedomain.com/gallery/placeholder.gif
hmm... okay.
I haven't actually worked anything out on the php end, but it's interesting, and I'll try to do it later. It would look something like this:
<?php
$file = // filename from GET
$dir = // VALID image directory relative to site root to look for file in
// (setting this ourselves helps prevent hacking)
$file = basename($file); // just the filename, no path info
// getimagesize will return FLASE if the file isn't an image
if(getimagesize($dir.$file)){
// rename the file, or move it to a "recycle bin"
// actually deleting things can be REAL bad
}
?>I dunno. I'll work on it.
jscheuer1
07-19-2010, 12:04 AM
This file is in the folder that it will act upon. Isn't making the filename reduced to its basename() enough to restrict it to the folder the script is in?
The only files that are in the folder are image files and .php files. By using the parameter '.php' with basename(), it will strip that extension. So when the the script 'asks' for the second time:
if($file and file_exists($file))
My thinking is that the answer can only be yes (true) if the file is in the current folder and doesn't have the .php extension. But, PHP novice that I am, I could be wrong about that. However, it seems to satisfy logic in that regard. If it really does, no need to rework that part unless it can be done more efficiently.
My real concerns are:
There may be directives that allow any script to look outside its current folder for files along certain paths, even if only the filename.ext is given. Do these have any effect if not invoked within the script itself?
Something, some PHP code perhaps, could be injected as the value for 'del' ($_GET['del']) in the query string that could somehow change how the rest of the script works.
If it's all in the same folder, then using basename() should be sufficient, yes, but I think you're misunderstanding the [suffix] argument: it won't exclude .php files, it will just exclude the extension (e.g., basename("path/file.php", ".php") will return "file" instead of "file.php").
getimagesize() is a great function, which, ironically, I rarely end up using as a method to get an image's size. You'll only get a result if the file is an image, otherwise, it will return false. So, it's a good way to make sure a particular image is an image. If you want to make better use of the results, index [2] of the returned array will be an IMAGETYPE constant, and index [mime] will give the mime type.
here's a list of the values that go with each IMAGETYPE constant (which seems to be really obscure information, but important if you expect to use the return value - I guess they might vary from server to server, so doublecheck).
[IMAGETYPE_GIF] => 1
[IMAGETYPE_JPEG] => 2
[IMAGETYPE_PNG] => 3
[IMAGETYPE_SWF] => 4
[IMAGETYPE_PSD] => 5
[IMAGETYPE_BMP] => 6
[IMAGETYPE_TIFF_II] => 7
[IMAGETYPE_TIFF_MM] => 8
[IMAGETYPE_JPC] => 9
[IMAGETYPE_JP2] => 10
[IMAGETYPE_JPX] => 11
[IMAGETYPE_JB2] => 12
[IMAGETYPE_SWC] => 13
[IMAGETYPE_IFF] => 14
[IMAGETYPE_WBMP] => 15
[IMAGETYPE_JPEG2000] => 9
[IMAGETYPE_XBM] => 16
[IMAGETYPE_ICO] => 17
djr33
07-19-2010, 01:42 AM
I'm still not sure I understand the reason you want to approach it like this.
Can you not generate an absolute link using the location of the image and the image's name, regardless of where the script is? Or a link beginning with "/" because that will eliminate the need for subdomain complexity.
[...]
The javascript will resolve it as something like:
http://www.somedomain.com/gallery/images/../placeholder.gif
If I use the absolute path though, the javascript will resolve it as something like:
http://www.somedomain.com/gallery/images/http://www.somedomain.com/gallery/placeholder.gif
basically, you're trying to avoid messing with the original script, right? I "get it," but it might end up being easier (and cleaner) to just rewrite everything... :o
jscheuer1
07-19-2010, 02:57 AM
Well, I am just learning PHP. And there are at least two issues here, one only involving PHP, the other only slightly involving PHP. I could probably parse out the relative path on the client side.
I figure in the unlink.php file that - First I determine if the parameter passed is a file. If so, I strip it to its basename and determine if it is still a file in the current folder. I figure stripping it to its basname before I determine that it is a file might cause an error if it's not a string that could be considered a file. I might be being too careful there.
Once I strip it to its basename, if it was a file, with the extension argument (.php in this case), it will no longer be a file, so will fail the second file_exists test. That much proves true in testing.
I suppose I could do something like (after stripping):
if(file_exists(dirname($_SERVER['SCRIPT_FILENAME']) . '/' . $file))
which works. But I'm just not convinced that's necessary at that point.
I'm still concerned about some directive somewhere on the server allowing my unlink.php to also look in some designated path for the file. But it's my understanding that I have to invoke that in my script for that to be an issue.
And I'm concerned about some PHP code being injected via the query string, as explained before. I don't really think that is an issue, but if that hypothetical injected code has a chance of executing, it could be an issue.
As far as I can tell, neither of you has addressed either of those issues.
As for rewriting the javascript to deal with the path issue, certainly not easier, except if I have to do a lot of stuff with that part of the code, which I don't anticipate. Even if I do, I've made this relative path a part of the master javascript object for that instance of the Photo Album. So it won't need to be recalculated each time it's needed. The only problem there would be if I needed to do other similar things involving a third or fourth, etc. folder. Which, as I say, I don't really anticipate. Even there though, once I can get to the page's folder with my relative path, I can pretty much go anywhere from there.
djr33
07-19-2010, 03:57 AM
1. basename() and odd input:
basename() never generates errors, or at least it doesn't mentioned it on php.net. I believe it just splits at the last slash and returns it. Very simple. It could, due to complex character sets or something unexpected, give you an unexpected filename, or maybe blank (I'd confirm the result isn't an empty string, and also probably doesn't start with '.'), but in these cases it wouldn't be a security issue as much as that randomly there might be two or more ways to delete one file: submit the real name, or submit some extremely complex invalid name that might parse as the same. But in either case, the user would have permission to delete the file in question and we just have to hope they know better than submitted random strings. In any even remotely logical situation, I'm sure it will be fine.
By the way, see this:
http://www.php.net/manual/en/function.basename.php#86972
And perhaps one of the first comments on the page, related to Cyrillic characters. Apparently it is possible that there may be some confusion but even then it looks like it won't be an "error".
2. PHP only looks for files in the current location. I believe there may be a way to change this to some default but this is an intentional configuration and can't be triggered by the user.
3. PHP code can't be injected unless you use eval() or a related function. Since you're never executing the strings, it won't ever be a problem. This is a problem with MySQL, for example, because you are generating source code within your PHP then "eval"ing it.
I'm not sure about the specific issue. If this continues to be a problem, I can try to look closer at the script to figure it out, but honestly if I were doing this, I'd rather rewrite it than work out parsing strings as paths (in any complex way).
While we're on this topic, here's a question. Is this a valid path:
myfolder1/../myfolder2/image.jpg
I've used that in PHP before, though never HTML (I haven't had the need to do so), but I'm not convinced it's technically correct. Is there anything wrong with this? I know that ../ is fine at the beginning, but can it go in the middle without any potential problems and is that "valid"?? It's always worked for me so I haven't investigated it much, though I've always been curious.
daniel - yes, it's valid. unnecessary, but valid. you can go up and down the path as much as you like.
john - I think you only need to verify that it's a file once, not at the beginning and then again later. basename() first, then (because you really only care that it's an image file) check that it's an image by using getimagesize() or finfo.
jscheuer1
07-19-2010, 04:10 PM
Thanks Daniel on clarifying the benign nature of basename, in that it throws no error, even (as I found in testing) if you feed it a boolean instead of a string.
I think I'm tending to be a bit overly cautious as I learn this language. There are so many errors one can get and that are listed with the functions that can give them. I just assume that even functions that are not noted for errors might also give one if they don't get what they're expecting. Some do.
This is what I have now:
<?php
$file = isset($_GET['del'])? $_GET['del'] : false;
$pattern = '/\.(jpg|jpeg|png|gif|bmp)$/i';
$success = false;
$file = basename($file);
if($file and preg_match($pattern, $file) and file_exists($file)){
@$success = copy($file, 'deleted/' . $file);
if($success){
@$success = unlink($file);
}
}
if($success){
echo $file . ' successfully deleted.';
} else {
echo 'There was a problem deleting ' . $file;
}
?>
Using getimagesize() struck me as a bit expensive (especially when I got to restoring whole folders of images by looping through the files individually) and a tad unreliable (see the notes on .gif format for that function). These images were originally selected using the same regex in the above. They are in a folder that is supposed to be reserved for image files of these types and the .php file(s) that are used to catalog/manipulate them.
I discarded using the '.php' argument with basename because testing showed that filename.php.php would become filename.php - exactly what I don't want to have happen.
Now I'm noticing that the filedates change when I make backups and restore. This appears to be part of the copy function. I'm thinking I may get the filemtime, make the copy, then touch the copy to the filetime. However, I'm not certain how crucial that is. Perhaps as an option because the most efficient way to use this tool would be to look at the most recent files first. If older files that are deleted and then restored come to the front of the list, that might make things confusing. But it also might make them more worthy of further attention/debate among the moderators. Remember, though it also has other uses, this was originally conceived as a tool for moderators to validate images via visual inspection.
Now I'm wondering about the backups. It could be someone's job to periodically go and empty those out. But, if one mod deletes one file, another deletes another, and then restores all, and the backups haven't been cleared out yet, the second mod will get both files back.
I could clear the backups on page unload, but that seems a bit drastic. A button with a confirm telling them that this time they cannot get the files back if they proceed might be best. The admin can set access to that button.
since you're secure in the knowledge that the files you're looking for are images, and since you're deleting them (not uploading them or serving them via an include(), for example), then checking the file extension should be sufficient. I use getimagesize() for uploads and when I'm using include() to display an image; times when I really need to be sure that it's an image and not something disguised as an image.
If you're creating backups, you could rename() the file to your backup directory rather than copying a backup and then deleting the original. Saves a step of code, anyway.
About cleaning out the backups: you could write a clean-up script that deletes all the backup files and set a cron job (if your server supports it) to run that script every day or so.
djr33
07-19-2010, 10:23 PM
In this case, a cron job is a bad idea because this script is going to be distributed (right?), and many servers don't support them. They're a good idea, but probably more than can be explained on one of the DD script pages.
Instead, I suggest just running this every time the page is loaded, deleting anything more than 24 hours old. I know that is extra work, but there's not really any way around it. I suppose you could base it on a random number or perhaps store a "last deleted" value in a database or text file, then delete if it's more than 24 hours old, updating the "last deleted" date.
Powered by vBulletin® Version 4.2.2 Copyright © 2021 vBulletin Solutions, Inc. All rights reserved.