tag:blogger.com,1999:blog-283500222009-07-07T10:14:24.485-05:00Rich's RantsWherein I occasionally rant on various topics including, but not limited to, PHP, Music, and whatever other Topics I find interesting at the moment.Richard Lynchhttp://www.blogger.com/profile/03615701995633150243noreply@blogger.comBlogger16125tag:blogger.com,1999:blog-28350022.post-29340140898259007682009-06-03T11:53:00.003-05:002009-07-07T10:06:32.329-05:00Facebook PerformanceI've been logging calls to Facebook API servers in my DEV environment for some weeks now.<br /><br />I'm pretty disappointed by the performance, overall:<br /><a href="http://l-i-e.com/fbapi_stats.htm">Facebook API Call Performance Stats</a><br /><br />I realize Facebook has scalability issues beyond my ken, but they also have resources beyond my ken as well...<br /><br />Facebook doesn't wait over 4 seconds for my server; Surely I shouldn't have to wait 30 seconds for theirs! :-)<br /><br />In fact, it's kind of pointless in the context of a canvas page for their servers to bother to respond at all after 4 seconds...<br /><br />User --A--> Facebook server request canvas page --B--> Request to my server --C--> API Call to Facebook server.<br /><br />If C can detect that we are in the context of a canvas page, and Facebook already <span style="font-weight:bold;">knows</span> that B will not wait more than X seconds, they might as well issue an HTTP 408 Request Timeout or something similar after X seconds, because there's no real point in them giving me the info they aren't going to wait for...<br /><br />I went ahead and created a Feature Request in Facebook's Bugzilla. You can vote for it if you think it will help you.<br /><a href="http://bugs.developers.facebook.com/show_bug.cgi?id=5528">#5528</a><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28350022-2934014089825900768?l=richardlynch.blogspot.com'/></div>Richard Lynchhttp://www.blogger.com/profile/03615701995633150243noreply@blogger.com0tag:blogger.com,1999:blog-28350022.post-55320005167059558372009-05-14T13:05:00.002-05:002009-05-14T13:07:52.917-05:00svn diff in vimIn the category of "How did I live without this?!" tools, I'm late to the party with this vim plugin from Brian Shire of Facebook / APC / PHP fame:<br /><br /><a href="http://github.com/ghewgill/vim-scmdiff/tree/master"></a><br /><br />If you use svn, git, cvs or similar, this plugin shows you a "diff" in vim.<br /><br />I don't know how often I quit vi or open another shell just to do a diff.<br /><br />Now, I don't have to!<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28350022-5532000516705955837?l=richardlynch.blogspot.com'/></div>Richard Lynchhttp://www.blogger.com/profile/03615701995633150243noreply@blogger.com1tag:blogger.com,1999:blog-28350022.post-28925544920680527902009-02-14T17:44:00.003-06:002009-02-14T17:51:56.023-06:00Lumni I Likovski, Cook County Assessor's Office<p>ADVERTISEMENT: <a href="http://l-i-e.com/resume.htm">Hire Me</a></p><br /><br />So the other day, I had to go down and file for a home owner's exemption on my house.<br /><br />I got my little queue ticket #96. They were calling out #32, with 5 or 6 workstations.<br /><br />I had a half hour, max, to get this done, before I had to be elsewhere.<br /><br />This did not look good...<br /><br />Thank God for Lumni I. Likovski, Manager, Taxpayer Exemptions.<br /><br />He was out in the waiting area, checking for people who didn't need a full-blown computer workstation (such as myself) and doing "triage" to get us the forms we needed, get our questions answered, and get us out of there.<br /><br />He even cheerfully (and I stress cheerfully) photocopied my Driver's License for me, a requirement to hand in the form.<br /><br />It's not often in this day and age that you get excellent service/help like this, most especially from government staff.<br /><br />So I'm dedicating this blog post to LUMNI I LIKOVSKI down that at Cook County. I owe you a beer!<br /><br />PS<br />I tried to just send in a comment to his boss using their online form, but it blows up like this, every time I try to submit it:<br /><br /><blockquote>Database Results Error<br />Description: The conversion of char data type to smalldatetime data type resulted in an out-of-range smalldatetime value.<br />Number: -2147217913 (0x80040E07)<br />Source: Microsoft OLE DB Provider for SQL Server</blockquote><br /><br />Maybe they should hire me to re-do their website? :-)<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28350022-2892554492068052790?l=richardlynch.blogspot.com'/></div>Richard Lynchhttp://www.blogger.com/profile/03615701995633150243noreply@blogger.com0tag:blogger.com,1999:blog-28350022.post-13999605098032869012008-07-21T21:28:00.002-05:002008-07-21T21:32:31.990-05:00Webgrind ROCKSOkay, so I'm a Linux guy through and through, and I'd love nothing more at my day job than to wipe the Doze desktop and throw Gentoo or Debian or FreeBSD or even Fedora on it...<br /><br />Shoot, I'd even be happy to dual boot the thing.<br /><br />But my current day job is the kinda job where I can't even surf to my own blog at work, because it's blocked.<br /><br />So, being stuck with Doze [shudder], I was Oh. My. God. happy to find this gem:<br /><br /><a href="http://code.google.com/p/webgrind/">Webgrind</a><br /><br />It's kind of like kCacheGrind, only a lot less features and through a browser and dog-slow.<br /><br />But so what?!<br /><br />The POINT is, I can actually successfully profile my code with <a href="http://xdebug.org/">Xdebug</a> for the first time ever on Windows without jumping backwards through flaming hoops!<br /><br />Now if I could just get JIT debugger client to happen...<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28350022-1399960509803286901?l=richardlynch.blogspot.com'/></div>Richard Lynchhttp://www.blogger.com/profile/03615701995633150243noreply@blogger.com0tag:blogger.com,1999:blog-28350022.post-42422049536944022392008-05-08T08:41:00.003-05:002008-05-08T09:10:12.309-05:00G is smart!<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_BmA524o-X8w/SCMDdrbNEFI/AAAAAAAAAAM/IJvTWKnxbnc/s1600-h/g.jpg"><img style="display:inline; margin:0 0px 0px 0;cursor:pointer; cursor:hand; vertical-align: text-bottom;" src="http://bp0.blogger.com/_BmA524o-X8w/SCMDdrbNEFI/AAAAAAAAAAM/IJvTWKnxbnc/s320/g.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5198002203323600978" /></a> is smart. I mean, <a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_BmA524o-X8w/SCMDdrbNEFI/AAAAAAAAAAM/IJvTWKnxbnc/s1600-h/g.jpg"><img style="display:inline; margin:0 0px 0px 0;cursor:pointer; cursor:hand; vertical-align: text-bottom;" src="http://bp0.blogger.com/_BmA524o-X8w/SCMDdrbNEFI/AAAAAAAAAAM/IJvTWKnxbnc/s320/g.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5198002203323600978" /></a> is really really smart.<br />I ask <a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_BmA524o-X8w/SCMDdrbNEFI/AAAAAAAAAAM/IJvTWKnxbnc/s1600-h/g.jpg"><img style="display:inline; margin:0 0px 0px 0;cursor:pointer; cursor:hand; vertical-align: text-bottom;" src="http://bp0.blogger.com/_BmA524o-X8w/SCMDdrbNEFI/AAAAAAAAAAM/IJvTWKnxbnc/s320/g.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5198002203323600978" /></a> a lot of questions, and <a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_BmA524o-X8w/SCMDdrbNEFI/AAAAAAAAAAM/IJvTWKnxbnc/s1600-h/g.jpg"><img style="display:inline; margin:0 0px 0px 0;cursor:pointer; cursor:hand; vertical-align: text-bottom;" src="http://bp0.blogger.com/_BmA524o-X8w/SCMDdrbNEFI/AAAAAAAAAAM/IJvTWKnxbnc/s320/g.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5198002203323600978" /></a> often knows the answer.<br />Of course, sometimes <a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_BmA524o-X8w/SCMDdrbNEFI/AAAAAAAAAAM/IJvTWKnxbnc/s1600-h/g.jpg"><img style="display:inline; margin:0 0px 0px 0;cursor:pointer; cursor:hand; vertical-align: text-bottom;" src="http://bp0.blogger.com/_BmA524o-X8w/SCMDdrbNEFI/AAAAAAAAAAM/IJvTWKnxbnc/s320/g.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5198002203323600978" /></a> doesn't. Though that often turns out later to be that I just didn't ask the question the right way.<br />Sometimes <a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_BmA524o-X8w/SCMDdrbNEFI/AAAAAAAAAAM/IJvTWKnxbnc/s1600-h/g.jpg"><img style="display:inline; margin:0 0px 0px 0;cursor:pointer; cursor:hand; vertical-align: text-bottom;" src="http://bp0.blogger.com/_BmA524o-X8w/SCMDdrbNEFI/AAAAAAAAAAM/IJvTWKnxbnc/s320/g.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5198002203323600978" /></a> gives me a lot of extra information. But that's okay. Sometimes <a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_BmA524o-X8w/SCMDdrbNEFI/AAAAAAAAAAM/IJvTWKnxbnc/s1600-h/g.jpg"><img style="display:inline; margin:0 0px 0px 0;cursor:pointer; cursor:hand; vertical-align: text-bottom;" src="http://bp0.blogger.com/_BmA524o-X8w/SCMDdrbNEFI/AAAAAAAAAAM/IJvTWKnxbnc/s320/g.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5198002203323600978" /></a>'s extra information teaches me something. And sometimes it's strictly for entertainment value, which is also okay.<br />And <a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_BmA524o-X8w/SCMDdrbNEFI/AAAAAAAAAAM/IJvTWKnxbnc/s1600-h/g.jpg"><img style="display:inline; margin:0 0px 0px 0;cursor:pointer; cursor:hand; vertical-align: text-bottom;" src="http://bp0.blogger.com/_BmA524o-X8w/SCMDdrbNEFI/AAAAAAAAAAM/IJvTWKnxbnc/s320/g.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5198002203323600978" /></a> likes multi-color rainbow-y things, which is cool.<br /><br />Now, I've got just one question for you: Am I talking about Google, or my girlfriend?<br /><br />Is Google an Artificial Intelligence?<br /><br />I'm serious here.<br /><br />Is Google's spidering, indexing, search, and response algorithms, in toto, an Artificial Intelligence?<br /><br />It doesn't know what I'm supposed to be doing Saturday night, for example.<br /><blockquote>[Girlfriend interjects] Mother's Day dinner at Indie Cafe</blockquote><br /><br />Hmmmm. Now that she's added that, Google <strong>does</strong> know what I'm supposed to be doing Saturday night!<br /><br />So clearly Google learns, and learns quickly...<br /><br />I dunno.<br /><br />I still think my girlfriend is smarter than Google... But I'm not so sure Google isn't pretty smart.<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28350022-4242204953694402239?l=richardlynch.blogspot.com'/></div>Richard Lynchhttp://www.blogger.com/profile/03615701995633150243noreply@blogger.com0tag:blogger.com,1999:blog-28350022.post-59242716732116162652008-04-17T10:36:00.003-05:002008-04-17T10:43:43.571-05:00Solaris vi input mode arrow keys fixThis blog post is not for you to read.<br /><br />It's for me.<br /><br />You see, every once in a awhile, I work for a client/employer who uses Solaris, OpenSolaris, Unix, or some other traditional Unix-like OS.<br /><br />They have their reasons, and they are good ones, and I'm fine with that.<br /><br />Alas, I then usually waste about an hour of their time (and they pay me for it) to Google for the hack that makes vi (my editor of choice) actually usable from a terminal program (putty, usually).<br /><br />To wit, the arrow keys in vi do not work in input mode under these OSes.<br /><br />This blog post is a reminder for ME to know what the heck to do next time this happens.<br /><br />So here is what I do:<br />Open up ~/.exrc and type these things:<br />set t_ku=[control-v][up-arrow]<br />set t_kd=[control-v][down-arrow]<br />set t_kr=[control-v][right-arrow]<br />set t_kl=[control-v][left-arrow]<br /><br />Then make a symlink from .exrc to .vimrc<br />ln -s ~/.exrc ~/.vimrc<br /><br />Finally, alias vi to vim, since this only works for vim:<br />alias vi=vim<br /><br />Bonus Tip:<br />To get vi/vim to use more than ONE LINE when you start up, use:<br />TERM=putty screen<br /><br />I believe "screen" is magical pixie dust that gives you a whole screen of line instead of one line, and the TERM stuff obviously tells screen that you need that screen to go to putty.<br /><br />If "man screen" had been installed, perhaps I would have a better understanding of the magical pixie dust, but so it goes.<br /><br />:-)<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28350022-5924271673211616265?l=richardlynch.blogspot.com'/></div>Richard Lynchhttp://www.blogger.com/profile/03615701995633150243noreply@blogger.com2tag:blogger.com,1999:blog-28350022.post-27349120439848001262007-09-13T00:26:00.000-05:002007-09-13T00:37:25.975-05:00Adobe SpamsFor the record:<br /><br />I <span style="font-weight:bold;">loathe</span> Flash.<br /><br />I mean, even on Windows where it kind of sort of works, it sucks up WAY too much RAM, and WAY too much CPU, and crashes far too often.<br /><br />Not that anything really works well on Windows (other than their Marketing, Legal, and Bullying Departments) but Flash works particularly badly.<br /><br />On Linux? Forget it.<br /><br />I gave up installing the damn thing, even on Windows, about a year ago, and you know what?<br /><br />I have found that there is not one site with any content worth getting that I am missing out, that I can't find elsewhere.<br /><br />Maybe your experience is different; Maybe you just can't live without uTube.<br /><br />Fine.<br /><br />But for me, no more Flash, ever again.<br /><br />Oh yeah, the <span style="font-weight:bold;">real</span> reason I hate Adobe is that they are spammers.<br /><br />They added me to their mailing list, unsolicited, and sent me commercial junk mail.<br /><br />So unless you want to support spammers, stop using their products.<br /><br />It's that simple.<br /><br />Thank you.<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28350022-2734912043984800126?l=richardlynch.blogspot.com'/></div>Richard Lynchhttp://www.blogger.com/profile/03615701995633150243noreply@blogger.com1tag:blogger.com,1999:blog-28350022.post-32939922595938138982007-07-17T18:24:00.000-05:002007-07-17T18:27:33.115-05:00File Upload Progress MeterEvery week or two, somebody asks how to do a File Upload Progress Meter on the PHP-General mailing list...<br /><br />Why the BROWSER doesn't provide this feature everybody wants is beyond<br />my ken.<br /><br />Surely the browser has some clue how many bytes are uploaded and how<br />big the file was.<br /><br />How tricky could it be for Firefox/IE to poke those values into a<br />couple variables somewhere?<br /><br />Instead we have a zillion JS hacks by developers generating tons of<br />traffic back-n-forth to the server to ask it how many bytes it has<br />received so far...<br /><br />In fact, why don't the browsers provide a NICE file upload progress meter in the first place, so web designers don't feel the need to re-invent the wheel?<br /><br />Or even (gasp) some nice hooks involving CSS and Web 2.0 or whatever so web designers can just do this without even needing to know any Javascript?<br /><br />This certainly would be way more useful than half the crap the browser wars have introduced in the past half decade that we've been needing this feature!<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28350022-3293992259593813898?l=richardlynch.blogspot.com'/></div>Richard Lynchhttp://www.blogger.com/profile/03615701995633150243noreply@blogger.com1tag:blogger.com,1999:blog-28350022.post-86561131823448996042007-07-12T23:18:00.001-05:002007-07-12T23:46:15.526-05:00Too Many "Friends"Bear with me here, there's a fairly long prologue...<br /><br />So, here's the thing:<br /><br />In one of my alter egos, I'm a Talent Buyer at a music venue.<br /><br />Now, you may not know much about the internal workings of music venues, but, basically, a music venue has about 1000 bands pounding on the doors wanting to play for every available position.<br /><br />There's nothing we'd love more than for there to be way more fans out there, mind you, so we could actually accommodate about 100 of those artists, instead of just he 1.<br /><br />Do note that I said 100 of the 1000, not all of them...<br /><br />90% of everything is still crap.<br /><br />Anyway, at the email address we've set up specifically for artists to submit a demo, we tend to get a whole lot of friend-link requests to basically all the big social networking sites.<br /><br />Now, this is going to sound petty, but it's getting pretty dang annoying.<br /><br />Most of these friend link requests are coming from artists we don't work with.<br /><br />Many of them are coming from artists we simply can't work with, as they're not the 1 in a thousand.<br /><br />I daresay a lot of them are coming from the 900 we wouldn't want to work with, even if the the supply/demand went the other way, with way more fans than artists...<br /><br />Our website pretty clearly asks bands not to add us to their promotional mailing lists. Do we really need to spell it out "and don't send us be-my-friend emails either"?<br /><br />Anyway, I'm thinking that all these social networking sites should just stop sending out emails on behalf of their users to non-users to invite them to join.<br /><br />If I want to join your social-networking site, and buy into the whole thing, then fine, I've agreed to get your emails.<br /><br />But I haven't!<br /><br />So, really, all these social-networking sites are just thinly-disguised spammers, when you get down to it.<br /><br />Or maybe there should be some kind of industry-standard minimum proof that the recipient might actually want these dang things, or that the sender actually has an existing relationship.<br /><br />Knowing an email address is not an existing relationship!<br /><br />Because it's gotten to the point where a new entrant in the social networking market, to me, just means yet another flurry of invites that I don't really want.<br /><br />Then I have to spend 20 minutes digging through their site for a place to contact them to say not only to ban that one user from sending me emails, but to ban ALL the users from ever sending me emails.<br /><br />I don't even want the dang things at my personal address anymore, really, from friends I actually know. It's gotten that bad.<br /><br />I definitely don't want them from strangers to an email address that was set up for a very specific business purpose.<br /><br />Am I being too petty?<br /><br />I don't think so.<br /><br />We came up with guidelines for the robots, and that seems to have (mostly) worked out.<br /><br />Can't we come up with guidelines for these social networking / stay in touch sites?<br /><br />Here are some suggested starting points for guidelines:<br /><br />If I'm not a member of your site, don't email me more than one invite ever, period.<br /><br />If I didn't want to join when Lee invited me, I still don't want to join when Fran invites me, okay?<br /><br />The invite email should provide links including:<br />&nbsp; &nbsp; ban this user from ever emailing me again<br />&nbsp; &nbsp; ban all users from ever emailing me<br />&nbsp; &nbsp; accept invitation<br />&nbsp; &nbsp; decline invitation, but join site<br /><br />Perhaps there should also be a "do not social-network-invite" shared database maintained by the larger existing social networking sites, which other social networking sites could pay a reasonable fee on a per email basis to check against, and a person could register with that one place to not get any invites from any social networking site.<br /><br />Note that the fee should be large enough to make it prohibitive for spammers to just pay up to garner emails, that it should not actually hand out email addresses but return a YES/NO for a submitted address from the social networking site, but be cheap enough that any serious new social networking site would buy in as a matter of course. Maybe there is no such price-point, but at least give it some thought.<br /><br />I sure don't want to keep contacting every johnny-come-lately social networking site to ask them to put a ban on their users sending me invites. There are too many of them springing up like weeds.<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28350022-8656113182344899604?l=richardlynch.blogspot.com'/></div>Richard Lynchhttp://www.blogger.com/profile/03615701995633150243noreply@blogger.com6tag:blogger.com,1999:blog-28350022.post-74554205109313275402007-07-11T16:24:00.000-05:002007-07-11T16:31:14.505-05:00PHP in HTMLHow come this doesn't work:<br />&lt;button action='&lt;?php mysqli_query($connect,$query)?&gt;'&gt;Click&lt;/button&gt;<br /><br />[sportscaster voice-over]<br /><br />Joe: Hey Bob, let's look at a slow-motion instant-replay of this common PHP newbie fallacy scenario.<br />Bob: Sure Joe!<br />Joe: Okay, so here goes:<br />&nbsp;&nbsp;The user requests a HTTP URL document.<br />&nbsp;&nbsp;The webserver fires up.<br />&nbsp;&nbsp;The webserver finds that it needs PHP to generate the document.<br />&nbsp;&nbsp;PHP fires up.<br />Bob: Wow, look at PHP go! That's fast!<br />Joe: Yeah, it is fast.<br />&nbsp;&nbsp;PHP has generated the document, and spits it out.<br />Bob: Boy, it's already finished. Hey, it's quit!<br />Joe: That's right, Bob.<br />&nbsp;&nbsp;PHP has FINISHED EXECUTION, and has exited.<br />&nbsp;&nbsp;Now watch this!<br />Bob: Oh boy, I see it coming now...<br />Joe: Yep, there it is.<br />&nbsp;&nbsp;There's some HTML in the browser, trying to execute some PHP code...<br />Bob: But you can see, PHP has LONG FINISHED and is OUTTA HERE!!!<br />Joe: That's right, Bob, PHP is simply not around to execute that code.<br />Bob: So what can you do, Joe?<br />Joe: Well, if you can live with the browser going back-n-forth to the web-server, with a significant "lag" time...<br />Bob: Oooh, well, I can see how that might be useful sometimes...<br />Joe: In those cases, you can use Ajax.<br />Bob: Anything else?<br />Joe: Not really. Until you get back to the webserver and PHP, there's just no PHP available. Unless your user is in the extreme minority of uber-PHP-geeks that has installed this EXPERIMENTAL PHP browser plug-in thingie: http://pecl.php.net/package/PHPScript<br />Bob: Whoa, Joe, I don't think I've ever even heard of anybody who's ever installed that.<br />Joe: Me neither, though I met Wez Furlong who wrote it, so I have to assume HE has installed it at least once...<br /><br />[cue to cool Guinness commercial]<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28350022-7455420510931327540?l=richardlynch.blogspot.com'/></div>Richard Lynchhttp://www.blogger.com/profile/03615701995633150243noreply@blogger.com0tag:blogger.com,1999:blog-28350022.post-64245339603001937702007-07-07T00:14:00.000-05:002007-07-07T00:40:05.435-05:00PHP Microsoft Excel Reader and Serial Killer DatesSo, my sister's ex-company of about 6 years that has shut down operations, but still has outstanding loan accounts to collect on...<br /><br />They have this arcane accounting system and it generates fixed-width field .TXT files, with the wrong dates (with 2-digit year, of course).<br /><br />The correct dates are in some Excel spreadsheets.<br /><br />So they wanted a quick hack script to change columns 254 through 259 in some .TXT files based on a simple lookup in some Excel files.<br /><br />Some developer told them it would take two weeks, and my sister didn't believe that, and they weren't real keen on spending that much $$$.<br /><br />I told her it was more like a 2 hour job, which might turn into 2 days, when all is said and done.<br /><br />It actually only took about 8 hours, all told, and most of that was spent in attempting to use various Excel reader PHP scripts that just plain didn't work.<br /><br />The one up on PHPClasses ends up using parse_url to try to figure out where a local file is, and bombs out. It's a really nifty stream-filter OOP thingie, if it only actually worked...<br /><br />And what's with all the annoying ads? Maybe less ads would get more traffic would get more revenue... [shrug]<br /><br />There were several Excel Reader commercial options in the $100 to $200 range, which didn't appeal at all.<br /><br />There's an Excel Writer in the <a href="http://pear.php.net/">PEAR Repository</a>, but I couldn't see how to make it read Excel files.<br /><br />There was some other Excel Reader script, but I forget now why I rejected it. Oh well.<br /><br />Finally, I found <a href="http://sourceforge.net/projects/phpexcelreader/">an Excel Reader PEAR package</a>, only it's on Sourceforge instead of in PEAR.<br /><br />Go figure.<br /><br />It also had a bug, where it was doing an include of 'OLERead.php' but the file was actually named 'OLERead.inc'<br /><br />Not quite sure how that passed by a QA process, <span style="font-weight:bold;">any QA process</span> but it's trivial to fix.<br /><br />I did submit a bug report, so hopefully it will get fixed. That is the nice thing about OpenSource.<br /><br />The example to read a whole sheet in as an array was pretty much all I needed after that quick '.php' -> '.inc' hack.<br /><br />I didn't even try the reading as a stream thing, since there are only a few thousand accounts.<br /><br />So I had a nice Excel Reader to do the account number to date lookup.<br /><br />Yippee.<br /><br />Then came the joy of Microsoft Excel internal date format...<br /><br />It's basically a "count" of days from January 1 1900 for the integer part, and a count of seconds for the fractional/decimal part.<br /><br />Of course, Microsoft aped Lotus 123 and knowingly left in the bug of 1900 being a leap year (it's not)!<br /><br />So from 0 to 60, the date is "off" by one, and at 60, the bogus date of February 29, 1900 is output. Everything is great from 61 up to a zillion or so where you get to December 31, 9999.<br /><br />Now, granted, none of these loans date back to the first couple months of 1900, but it's still pretty irksome...<br /><br />And you'd want a conversion function to be correct and re-usable, rather than something that only works for a limited input set. (Y2K anybody?)<br /><br />I found a conversion function in C, and ported it to PHP:<br /><a href="http://l-i-e.com/excel_date.phps">http://l-i-e.com/excel_date.phps</a><br /><br />I'm not claiming it's the best code ever, and I don't even know what it does, really, as I just changed the variables to have $ in front, and swapped int() function into floor() function.<br /><br />Slapped in an sprintf instead of returning the individual month/day/year as pass-by-access args, since it seemed easier.<br /><br />That pretty much sums up the past couple evenings for me.<br /><br />I occasionally run into a Microsoft devotee who wonders why I hate Microsoft so much.<br /><br />Really, if this rant doesn't make it clear why I hate Microsoft, I simply cannot hold a rational conversation with you...<br /><br />It <span style="font-weight:bold;">would</span> have been a 2-hour job if MS wasn't so stupid, but it was an 8-hour job because Microsoft is, well, stupid.<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28350022-6424533960300193770?l=richardlynch.blogspot.com'/></div>Richard Lynchhttp://www.blogger.com/profile/03615701995633150243noreply@blogger.com14tag:blogger.com,1999:blog-28350022.post-34774768033631810292007-07-05T22:00:00.000-05:002007-07-05T22:06:58.386-05:00Windows PHP Free DevelopmentZoe Slattery of IBM has documented exactly how a Windows user would go about hacking PHP source with all <span style="font-weight:bold;">free</span> development tools.<br /><br /><a href="http://www-03.ibm.com/developerworks/blogs/page/phpblog">http://www-03.ibm.com/developerworks/blogs/page/phpblog</a><br /><br />I'm not sure if I should leap with joy or cringe in fear, but now any PHP Windows user could manage to write their own custom extension.<br /><br />I managed to stumble through it once, thanks to Sara Goleman's fantastic articles up on Zend.com:<br /><a href="http://devzone.zend.com/node/view/id/1021">Extension Writing Part I: Introduction to PHP and Zend</a><br /><br />I wonder how long it will be before Microsoft accidentally on purpose breaks things so you can't do this?<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28350022-3477476803363181029?l=richardlynch.blogspot.com'/></div>Richard Lynchhttp://www.blogger.com/profile/03615701995633150243noreply@blogger.com0tag:blogger.com,1999:blog-28350022.post-61870473604397095522007-06-14T15:57:00.000-05:002007-06-14T16:19:46.975-05:00Refund PolicySo I was setting up a new domain today for a website, and for the first time noticed <a href="http://hostbaby.com">Hostbaby's</a> Refund Policy at the bottom of their contact page:<br /><br />REFUND POLICY: Anyone who wants a refund can get a refund for any reason.<br /><br />Made me laugh out loud.<br /><br />Now, the thing is, I'm sure some readers are saying "Yeah, right" sarcastically.<br /><br />But I've worked with these guys long enough to know, that if that's what they say their policy is, that's actually their policy.<br /><br />REFUND POLICY: Anyone who wants a refund can get a refund for any reason.<br /><br />You can tell them you want to shut down your site and get a refund because your dog ate your homework, and they'll do it.<br /><br />Plus they don't even charge you until a full month, so you get your site up before the billing starts.<br /><br />I love this webhost.<br /><br />PS They also run <a href="http://cdbaby.com/">CDBaby.com</a> where you can buy a zillion Indie CDs, but no major label junk. :-)<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28350022-6187047360439709552?l=richardlynch.blogspot.com'/></div>Richard Lynchhttp://www.blogger.com/profile/03615701995633150243noreply@blogger.com0tag:blogger.com,1999:blog-28350022.post-65839456405418004722007-06-13T11:38:00.000-05:002007-06-13T14:39:02.723-05:00PHP header location redirect refreshI haven't posted much, but at php|tek 2007, Chris Shiflett actually said he <span style="font-weight:bold;">liked</span> my rant, and that I should post more.<br /><br />Well, hey, if Chris Shiflett says I oughta do something, I listen.<br /><br />So, today's rant is about PHP header Location hacks.<br /><br />First, let's be sure everybody understands what it is:<br /><br />header("Location: http://example.com");<br /><br />will re-direct the browser to the URL example.com<br /><br />Specifically, the PHP tells Apache to issue to the browser a 301 Redirect header to that URL, and then the browser gets that 301 Rediret header and automatically tries to visit that URL.<br /><br />EDIT<br />As pointed out on PHP-General, PHP actually sends a 302 Temporarily Moved rather than a 301 Permanently moved. The rest of the rant still applies, as I simply mis-typed 301 for 302 anyway.<br />/EDIT<br /><br />Now this seems really cool at first, perhaps because it <span style="font-weight:bold;">is</span> really cool, when used for an appropriate problem.<br /><br />Unfortunately, many PHP scripters are using this as an Idiom or as a Programming Construct with things like:<br /><br />if (!logged_in()){<br /> header("Location: login.php");<br />}<br /><br />Now, this does "work" but there are several problems with it.<br /><br />First and foremost, the HTTP Specs require a <span style="font-weight:bold;">complete</span> URI for the location. And while this fragment of a URI might "work" on most browsers, it will, sooner or later, totally mess you up when the browser mis-interprets this.<br /><br />Specifically, some versions of IE will do the redirect, but won't POST the original data as a redirect should, with this URI fragment. Use the full URL, and IE actually does the right thing, and redirects with all the POST data intact.<br /><br />You shouldn't use an incomplete URI just because "it works" any more than you should use non-compliant HTML just because it works.<br /><br />But let's look at this header("Location: ") in a slow-motion instant replay:<br />User requests page X<br />Browser uses dog-slow Internet to contact web server.<br />Web server receives request, hands it off to PHP<br />PHP responds over dog-slow Internet with 301 Redirect to login.php.<br />Browser interprets 301 Redirect, hopefully correctly.<br />Browser uses dog-slow Internet to contact web server.<br />Web server receives request for login.php, hands it off to PHP<br />PHP finally spits out the login form.<br /><br />Whew.<br /><br />Maybe you should consider just doing this instead:<br /><br />if (!logged_in()){<br /> require 'login.php';<br /> exit;<br />}<br /><br />And look, ma, no extra round trip through the dog-slow Internet.<br /><br />Plus, you're not <span style="font-weight:bold;">wasting</span> an HTTP connection, which, on a busy server, is a precious resource. Presumably you'd like your website to be popular enough to be busy someday, right?<br /><br />You're going to end up reading the login.php file in the end anyway. Why would you spend all that time going back and forth to the browser to do it?<br /><br />And include 'login.php' is no more difficult to read/understand/maintain than the header().<br /><br />Reserve a header("Location: xxx") for when you <span style="font-weight:bold;">really</span> need it: When a document has actually moved and the URL is being retired.<br /><br />Don't use header("Location: xxx") as a Programming Construct in place of simple if/else and include logic.<br /><br />PS This same rant applies to the header("Refresh: ") usage as well. Though why you'd want to use that instead of Location: is beyond me, and probably the subject of another rant...<div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28350022-6583945640541800472?l=richardlynch.blogspot.com'/></div>Richard Lynchhttp://www.blogger.com/profile/03615701995633150243noreply@blogger.com11tag:blogger.com,1999:blog-28350022.post-1151006043049985712006-06-22T14:08:00.001-05:002009-07-07T10:01:43.782-05:00PHP Downloads, Content-Disposition, Content-type, and other arcana.Every damn day, some other poor PHP newbie goes off and finds "advice" in Google about how to force browsers to download things, and how to get the filename they want in the "Save As..." prompt box.<br /><br />Invariably, these newbies are suckered in by Bad Advice<sup><font size="-2">tm</font></sup> from people who <span style="font-weight:bold;">clearly</span> do not read the HTTP specs.<br /><br />Now I'm not claiming to have read <span style="font-weight:bold;">all</span> the HTTP specs, much less memorized them, but did fight through this battle in the days of version 3 and 4 browsers, and it <span style="font-style:bold;">still</span> seems to be tripping people up, while my solution has been working for me since nineteen-ninety-mumble.<br /><br />So I'm gonna let you in on a few little <span style="font-style:italic;">secrets</span>.<br /><br />Okay, one of the secrets is widely published in the RFCs, and the other is hard-won experience about how the 1995 johnny-come-lately Microsoft made-up Content-disposition header isn't really widely-supported very well, and how to make 100% certain that your downloads always end up prompting the user for the right filename with a crude but effective hack that leaves the brower with no other reasonable choice.<br /><br />First, if you Google for this topic, you're going to find a lot of people suggesting a lot of different MIME-types to use for the "Content-type" header:<br /><code><br />application/octet-stream<br />application/download<br />application/force-download<br />.<br />.<br />.<br /></code><br /><br />Note that this is to force a download -- If you actually want a browser to display (or attempt to display) some content, the Content-type: should always be the correct Content-type for that type of document. E.g. text/html for HTML, image/gif for a GIF, image/jpeg for a JPEG (image/jpg will not work on some browsers), image/png for a PNG, application/pdf for a PDF, and so on.<br /><br />If you are ever in doubt about the correct MIME-type for browser display, find a static URL that works, preferably on the corporate site driving that technology, and find out what Content-type: is sent by their site for their sample content.<br /><br />For a download, <span style="font-style:italic;">application/octet-stream</span> works because it's a part of the HTTP specification, and has been part of the HTTP spec, from the very beginning of HTTP specifications.<br /><br />The others "work" only because they are <span style="font-weight:bold;">made-up</span> content-types, and the browser currently has <span style="font-weight:bold;">no idea</span> what to do with them.<br /><br />Some equally valid Content-type would be:<code><br />asdf/asdf<br />abc/abc<br />you-can-put/anything-you-want-here<br />microsoft/sucks<br />asdfswetrkhkhkvnknsdbknilghwerthiehl/wilerywnfaksvnklgndiglkghadlgha<br /></code><br /><br />Only problem is, <span style="font-weight:bold;">tomorrow</span> Microsoft can choose, at their discretion, that "application/download", or any of the other made-up MIME-types means "Put it in the <span style="font-style:italic;">My documents</span> directory", because MS knows much better than you that that is what all their users really want, and your download doesn't do what you want any more.<br /><br />But they <span style="font-weight:bold;">cannot</span> change the meaning of <span style="font-style:italic;">application/octet-stream</span>, because that is specifically reserved, in the HTTP specification, for <span style="font-style:italic;">force a download</span><br /><br />So, all of the above boils down to the following question:<br /><br />Do you want to use a MIME type that happens, by sheer coincidence, to not be "taken" yet and will work today, but tomorrow might be re-defined?<br /><br />Or would you rather use the <span style="font-weight:bold;">documented feature</span> that <span style="font-style:italic;">appliction/octet-stream</span> will always work?<br /><br />For anybody not well-versed in <span style="font-style:italic;">Tech-Talk</span> the correct answer is the latter, and definitely not the former.<br /><br />So to force the browser to download a document, the only correct solution is:<code><br />Content-type: application/octet-stream<br /></code><br /><br />Anything else is a game of Russian Roulette.<br /><br />With Microsoft pulling the trigger.<br /><br /><br />Now we come to the somewhat more complicated issue of getting the "Save As..." window to provide the filename you like as a default.<br /><br />Let's assume, for our purposes, that you want this filename:<code><br />iwant.xyz<br /></code><br /><br />In an ideal world, the "Content-disposition: ... ;filename=iwant.xyz" would work perfectly for this.<br /><br />... represents some sort of MIME-type, which is largely irrelevant, for the purposes of this article about forced downloads.<br /><br />Unfortunately, with some versions of some browsers, this Content-disposition: simply will not work.<br /><br />In fact, no matter what combinations of headers you try to use, there is <span style="font-weight:bold;">some</span> minute version, such as x.y.z.37, that <span style="font-style:italic;">doesn't work</span> even though x.y.z.36 and x.y.z.38 <span style="font-style:italic;">do work</span><br /><br />This is complicated by the presence or absence, and/or changes to the Content-type: and the "..." part of Content-disposition: that we're ignoring.<br /><br />To document exactly which versions of which browsers do/don't work for which combination of headers, mime-types and URLs is well beyond the scope of a blog post. Perhaps a Ph.D. Thesis would be more appropriate, if somebody is desparate for a Thesis Topic and has a lot of time to spare.<br /><br />And for the love of <span style="font-style:italic;">insert diety of choice here</span> do not ask me to tell you which browser/version won't work with your particular <span style="font-style:italic;">solution</span>. I have neither the time nor the inclination to do your browser-testing QA for you. I don't even like doing my own QA, much less yours.<br /><br />I can only guarantee you that if you test on <span style="font-weight:bold;">every</span> minor version of <span style="font-weight:bold;">every</span> browser ever released, you <span style="font-weight:bold;">will</span> find one that does not work.<br /><br />But I <span style="font-weight:bold;">do</span> have a solution for you:<br />Provide a URL which the browser cannot <span style="font-weight:bold;">possibly</span> mess up.<br /><br />For example, this URL <span style="font-weight:bold;">will</span> mess up some browsers:<br />http://example.com/download.php?filename=iwant.xyz<br /><br />This URL, however, the browser, cannot possibly mess up:<br />http://example.com/download/iwant.xyz<br />because it's too damn simple to mess up.<br />K.I.S.S. priniciple is the watchword here.<br /><br />Don't give the browser any opportunity to screw up. Because if you do, some browser somewhere <span style="font-weight:bold;">will</span> screw up.<br /><br /><span style="font-style:italic;">"But wait!"</span>, you say, "<span style="font-style:italic;">I can't do that! I need my PHP script to do the download work</span>"<br /><br />Yes, you can.<br /><br />Follow the bouncing ball:<br /><br />First, 'download' above may <span style="font-weight:bold;">look</span> like a directory, but it's not. It's a PHP script. It just doesn't happen to have <span style="font-style:italic;">.php</span> on the end of it.<br /><br />And <span style="font-style:italic;">iwant.xyz</span> doesn't have to be in any particular location just because it looks like a boring static URL component.<br /><br />There are a variety of ways to make this work. Apache's <a href="http://httpd.apache.org/docs/1.3/mod/mod_rewrite.html">mod_rewrite</a> springs to mind for Apache experts, and there are many articles online telling you how to do PHP mod_rewrite.<br /><br />But, truth to tell, mod_rewrite is a real PITA to mess with. And Apache/PHP has a <span style="font-weight:bold;">much easier</span> technique available which I'll detail below.<br /><br />So, there are two tasks here for this URL:<br />http://example.com/download/iwant.xyz<br /><br />The first is to somehow get 'download' to be a PHP script, even without '.php' on the end.<br />And the second is to somehow make 'iwant.xyz' from the URL available to PHP.<br /><br />Fortunately, the mechanics of these are very easy tasks.<br /><br />It seems to be difficult for newbies to wrap their brains around the concepts, but the actual mechanics are trivial, and I'm hoping this How-To will ameliorate the difficulty of the concepts.<br /><br />Let us begin by assuming your not quite working download.php PHP script looks something like this:<code><pre><br />&lt;?php<br /> //register_globals is off, of course<br /> $filename = $_GET['filename'];<br /> <br /> //Crude cleansing to avoid ../../etc/passwd hacks<br /> $filename = basename($filename);<br /> <br /> //In an ideal world, you would have a specific range of legal values for $filename<br /> //And your cleansing would test positive only for valid input<br /> //The following line is far too restrictive in anything but this sample application<br /> //But it's definitely the Right Way (tm) for THIS sample application<br /> //Security can't be bought off-the-rack. It's a custom job like this<br /> if ($filename != 'iwant.xyz') die("Did you really think I wouldn't add filename validation here?");<br /> <br /> //I can virtually guarantee that the next line is not correct. Fix it.<br /> //I personally would recommend that it NOT be in your webtree,<br /> //so Bad Guys (tm) cannot bypass your application and just get it direct.<br /> //If your webhost does not provide a non-web-tree directory, find a new host.<br /> //This should be the complete full path to the "real" iwant.xyz file.<br /> $basename = '/some/path/to/the/real/files/';<br /> <br /> //Compose the actual full file path:<br /> //If you didn't put / at the end of $basename, add it here<br /> //Or do some fancy footwork to be sure you have the proper number of '/'s you need<br /> //Or not, as Un*x systems ignore bogus extra '/' in a pathname anyway.<br /> $fullname = "$basename$filename";<br /> <br /> //For larger files, a decent browser will provide a progress meter, if you do this:<br /> $filesize = filesize($fullname);<br /> header("Content-length: $filesize");<br /> <br /> //As discussed above, the only Documented Feature,<br /> //sure-fire guaranteed way to force a download every time is:<br /> header("Content-type: application/octet-stream");<br /> <br /> //Now just read the file and spit it out:<br /> readfile($fullname);<br /> //For large files, an fopen/fread loop using feof may be more appropriate<br />?&gt;<br /></pre></code><br /><br />Now, instead of the usual 'download.php' you might expect, name this script 'download' without the '.php'<br /><br />In order to convince Apache that this script really <span style="font-weight:bold;">is</span> a PHP script, even without the .php on it, create a file named '.htaccess' in the same directory as 'download' (or a 'higher' directory) and put this in it:<code><pre><br />&lt;Files download&gt;<br /> ForceType application/x-httpd-php<br />&lt;/Files&gt;<br /></pre></code><br /><br />The above three lines of magic force Apache to think of 'download' as a PHP script, even though .php is not part of the script name.<br /><br />This assumes that your webserver has been configured with .htaccess "on" in httpd.conf. If that's not the case, then you would probably want to put those three lines directly in httpd.conf, or in a file that httpd.conf Includes.<br /><br />If your Apache webserver host provides neither httpd.conf nor .htaccess to you, the I feel truly sorry for you, but cannot help you, other than to suggest finding a better host.<br /><br />Your URL would then look something like:<br />http://example.com/download?filename=iwant.xyz<br /><br />You should go ahead and build this example application now, and then we can move on to our second task of getting rid of the ?filename= part.<br /><br />Here is a sample application using the above code:<br /><a href="http://l-i-e.com/blogger/download.php?filename=iwant.xyz">http://l-i-e.com/blogger/download.php?filename=iwant.xyz</a><br /><br />Note that you will, depending on your browser make and model, probably be prompted to "Save As..." with the filename 'download.php'<br /><br />We'll be fixing that in our next task, so just change the name to 'iwant.xyz' by hand when prompted to download.<br /><br />But if you can find a browser that does <span style="font-weight:bold;">not</span> treat that file as a download, I'll send you a Cookie.<br /><br />Note that you can configure some browsers to just auto-save all downloads in some directory or, blech, on your desktop. That's a user-configuration choice which nothing in the world is going to "fix". Sorry. Educate the user, or live with their freedom of choice, whichever way you want to look at it.<br /><br />But the browser itself is still treating the output as a 'download' even if it has been [mis-]configured to just dump the file in some random directory.<br /><br />Now, on to the task of getting the URL to end in /iwant.xyz so that the browser is "fooled" into thinking it's a static URL and it <span style="font-weight:bold;">will</span> use 'iwant.xyz' as the default filename for the download window.<br /><br />As you can see, your script 'download' is going to have some <span style="font-style:italic;">extra</span> stuff at the end of it.<br /><br />Apache and PHP collaborate to mostly ignore anything <span style="font-style:italic;">extra</span> tacked onto the end of a URL, except for one crucial input they provide:<code><br />$_SERVER['PATH_INFO']<br /></code><br /><br />This variable is set by Apache/PHP to contain everything after your script name that is in the URL, no matter what is there.<br /><br />Now, because your real application might need more input than just 'iwant.xyz' I'm going to go above and beyond here, and provide an include file that will give a lot of flexibility.<br /><br />Here are some URLs the <span style="font-style:italic;">normal</span> way, and some done my <span style="font-style:italic;">recommended</span> way compared side-by-side:<br /><table border="1"><br /> <tr><th>Normal</th><th>Recommended</th></tr><br /> <tr><br /> <td>http://example.com/download.php?filename=iwant.xyz</td><br /> <td>http://example.com/download/iwant.xyz</td><br /> </tr><br /> <tr><br /> <td>http://example.com/download?filename=subdirectory/iwant.xyz</td><br /> <td>http://example.com/download/subdirectory/iwant.xyz</td><br /> </tr><br /> <tr><br /> <td>http://example.com/download?page=42&amp;line=20&amp;filename=iwant.xyz</td><br /> <td>http://example.com/download/page=42/line=20/iwant.xyz</td><br /> </tr><br /></table><br /><br />Keep in mind on that last one, that the browser <span style="font-weight:bold;">cannot</span> know that you don't have directories named 'page=42' and 'line=20' no matter how odd that may seem for directory names.<br /><br />Those are perfectly valid directory names, and the browser has to assume that's what you have.<br /><br />Only you and I will know that 'download' isn't a directory but a PHP script, and those 'extra' bits are really just inputs to this PHP include:<code><pre><br />&lt;?php<br /> //Consider a URL such as:<br /> // http://example.com/scriptname/var1=val1/var2=val2/path/to/filename.xyz<br /> //Transform it into:<br /> // $PATH = '/path/to/filename.xyz'<br /> // $PATH_VARS['var1'] = 'val1';<br /> // $PATH_VARS['var2'] = 'val2';<br /> $PATH = '';<br /> $parts = explode('/', $_SERVER['PATH_INFO']);<br /> foreach ($parts as $part){<br /> $pieces = explode('=', $part);<br /> switch(count($pieces)){<br /> case 1: /* tack it on as part of a pathname */<br /> //Also ignore the leading '/' of PATH_INFO which turns into an empty '' from explode()<br /> if ($pieces[0] !== '') $PATH .= "/" . $pieces[0];<br /> break;<br /> default: /* Set up something like $_GET only with $PATH_VARS */<br /> $var = $pieces[0];<br /> // value might have = within it...<br /> unset($pieces[0]);<br /> $val = implode('=', $pieces);<br /> $PATH_VARS[$var] = $val;<br /> break;<br /> }<br /> }<br />?&gt;<br /></pre></code><br /><br />I've commented the above script heavily, and all it does is transform the PATH_INFO that Apache and PHP provide into a couple convenient variables:<br />$PATH_VARS will contain any /var=val/ in the URL as $PATH_VARS['var'] = 'val';<br />$PATH will contain anything else in the path as '/subdir1/subdir2/filename.xyz';<br /><br />Save that script above as 'pathinfo.inc' and change the top of your 'download' script from $filename = $_GET['filename'] into this:<code><br />require 'pathinfo.inc';<br />$filename = $PATH_VARS['filename'];<br /></code><br /><br />Now, you can surf to a URL like this:<br /><a href="http://l-i-e.com/blogger/download/iwant.xyz">http://l-i-e.com/blogger/download/iwant.xyz</a><br />and get a download windows with the only reasonable choice for a default filename to "Save As..." that a browser could possibly infer from that static-looking URL: 'iwant.xyz'<br /><br />It would be nice if this was a <span style="font-weight:bold;">Documented Feature</span> or if something like Content-disposition actually worked in all the minor versions of all the browsers ever released.<br /><br />But, in this case, consider what else a browser could possibly <span style="font-weight:bold;">do</span> with the download window it <span style="font-weight:bold;">must</span> provide.<br /><br />If you really think about this, I believe you'll come to the same conclusion I did, many years ago: This is a hack, but a reasonably safe hack, because what else can a browser do with such a simple URL, given that it must prompt the user for a file download (or auto-save the download by user choice) to remain compliant with the HTTP Spec.<br /><br />Here is the final source for our download script:<br /><a href="http://l-i-e.com/blogger/download.phps">http://l-i-e.com/blogger/download.phps</a><br /><br />And the pathinfo.inc file:<br /><a href="http://l-i-e.com/blogger/pathinfo.phps">http://l-i-e.com/blogger/pathinfo.phps</a><br /><br />There are a few caveats worth mentioning here:<br /><br />Unlike $_GET, $PATH_VARS cannot be made into a "super-global" so you'll have to declare it global within your functions/methods.<br /><br />Actually, technically, you could use PHP's RunKit extension to force $PATH_VARS to be a "super-global" but if you've got RunKit installed on your server, you probably already know everything in this article anyway.<br /><br />If you don't know what RunKit is, you should just take my word for it that you don't want it installed, but if you need convincing, let me just point out that the purpose of RunKit is to be able to re-define something like:<blockquote><code><br />if ($whatever) echo $something;<br /></code></blockquote><br />so that the 'if' and the 'echo' don't do what you expect them to do anymore.<br /><br />I.e., RunKit lets you re-define the actual PHP language on-the-fly, for developing a new version of the PHP language.<br /><br />Let me also point out, in case it's not blatantly obvious, that savvy users can <span style="font-weight:bold;">still</span> put any damn thing they want into the URL in attempts to break your script, take over your server, and otherwise cause you much grief.<br /><br />This URL munging should not be considered primarily as a "Security Measure" though there may, or may not, be some relative increased security in that finding a ? in a URL and then trying variants is probably a very common Bad Guy technique, but cramming more things onto the end of a static URL generally doesn't do anything at all, so most Bad Guys probably don't do a lot of that.<br /><br />This falls into "Security through Obscurity" though, which are generally very weak security measures, and only useful, if at all, when layered in with other, more robust, Security Measures.<br /><br />Or, to make a long story short, you <span style="font-weight:bold;">must</span> still validate and cleanse any data coming from $PATH and $PATH_VARS, exactly as you would for $_GET.<br /><br />Required Reading:<br /><a href="http://phpsec.org/">http://phpsec.org/</a><br /><br /><br />I aleady know that at least one other PHP Developer thinks I'm daft to put /var=val/ into the URL, and that I should just use 'positional' elements.<br /><br />Unfortunately, I do not find that very convenient, as some of the elements in my scripts are optional, so the URL would end up needing too many '/////' in it and my eyes are too old and worn-out to attempt to count those correctly.<br /><br /><br />I can also safely predict some bloggers will insist that "Content-disposition:" works just fine in all browsers, or maybe they'll be smart and qualify it as "all modern browsers"<br /><br />My only possible responses to that are:<ol><br /><li>You haven't tested enough minor release versions</li><br /><li>I believe backwards-compatible legacy support for ancient browsers is important</li><br /></ol><br /><br />If you do not like this particular solution, just don't use it.<br /><br />I happen to believe, based on my experience fixing far too many bug reports from iconoclastic users of niche browsers you may have never even heard of, that it's the only correct solution to browser insanity, paricularly if you use PHP to output dynamic rich media such as Images (GIF, JPEG, PNG), PDF, FDF, Flash/Ming, and so on, as I have done.<br /><br />The pathinfo.inc file above works wonderfully for a URL such as:<br />http://example.com/thumbnail/max_width=100/photographer_id=7/artist_id=15/rockstar.jpg<br /><br />It also works for the PDF URL embedded in an FDF which tend to drive Netscape/IE crazy if you start adding dynamic elements.<br /><br />This rant was actually referenced by none other than PHP Security Expert Chris Shiflett in <a href="http://swik.net/PHP/Planet+PHP/The+Adobe+PDF+XSS+Vulnerability/tsjr"> The Adobe PDF XSS Vulnerability</a><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28350022-115100604304998571?l=richardlynch.blogspot.com'/></div>Richard Lynchhttp://www.blogger.com/profile/03615701995633150243noreply@blogger.com4tag:blogger.com,1999:blog-28350022.post-1147985282793778632006-05-18T15:47:00.000-05:002006-06-22T23:57:12.593-05:00Bogus Blog?Truthfully, this blog originally got created just so I could add a comment to:<br /><br /><a href="http://phpgirl.blogspot.com/2006/05/chicago-php-user-group-report.html">http://phpgirl.blogspot.com/2006/05/chicago-php-user-group-report.html</a><div class="blogger-post-footer"><img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/28350022-114798528279377863?l=richardlynch.blogspot.com'/></div>Richard Lynchhttp://www.blogger.com/profile/03615701995633150243noreply@blogger.com0