Sunday, July 21, 2019

The Great Smokey Mountains Adventure Day 2 -- The View From The Top

While there are countless attractions vying for your attention on Gatlinburg, TN's main street, we'd done our homework and despite the various pleas of D. and J., we focused on getting to our first adventure of the day: Ober Gatlinburg. This was no minor feat for me, as it required a 2 mile tram ride to the top of the surrounding mountain. While I have a fear of heights, enclosed structures like a tram car usually don't bother me. This one was awfully high up and did enough rocking to give me pause.

Still, before we knew it, we were at Ober Gatlinburg proper and the next stage of our adventure could begin. The kids immediately went on the largest water slide and every other attraction from there.

At the base of the amusement park area is the chairlift that takes visitors up to the top of Mount Harrison and serves as an observation point. The tram description promised it would be the best view of the Smokies without having to hike. My first thought when I saw the impossibly long and steep ascent of the chairs was a hard oh heck no. But as our time at Ober wore on, I started to relax a bit. Everyone on the chairlift was so chill. And if the chairlift hugged the mountain, it really wouldn't be that high off the ground.

So before I had time to change my mind, I found myself standing with J. waiting for a chairlift seat to scoop us up.

The chairlift ride was indeed relaxing. At times the ground dropped away and I had try awfully hard not to think about falling. I won't lie, I spent a good portion of the ride with my eyes closed. But thanks to sunglasses, I don't think J. knew this.

Turns out the Ober Gatlinburg scenic chairlift is the longest of its kind in the US. But we all made it to the top, enjoyed some Bluegrass music and took photos and then made it back down.

Whenever my fear of heights kicks in I try to tease out exactly what bothered me. In this case I decide it's the lack of some sort of safety belt. The clumsy-looking restraining bar feels more like an afterthought decoration than a serious safety measure. Give me a nice 5 point harness, and I'll be far more comfortable cruising up a mountain on a front-porch swing. Of course this is all in my head, Wikipedia promises me that even without the restraining bar the physics of the chairlift will keep me from falling out. Physics-shmisics, you best believe I had a death grip on that chairlift the entire way up and down the mountain.

After a fun time at Ober, we made our way to Ripley's Aquarium of the Smokies. I was far more in my element here, as plunging to my death was quite unlikely.

As aquariums go, this one was top notch. We saw amazing fish, got interesting views of sharks, pet horseshoe crabs and generally got to see all the things you'd expect to see at a top tier aquarium. If I had to level one complaint it would be that there wasn't anything particularly unique about this aquarium. We, of course, may just be spoiled. If you've never walked through a tunnel that's under the shark pool, scrambled by tunnel to the center of an fish tank, or seen a giant spider crab doing its thing, then you're in a for a real treat. But if you've been to the Baltimore Aquarium, then you probably won't see anything new here.

Still, the kids had a great time and I enjoyed snapping pics of the sharks and other creatures. And everybody enjoyed the air conditioning.

After a day of playing at very touristy Gatlinburg, we made our way back to our Airbnb and busted out the Legos for some building time. What a fun day!

Thursday, July 18, 2019

Heading to the Great Smoky Mountains

I was supposed to have the easiest path to starting our vacation. Shira had to fly to Boston to get D., and then the two of them had to fly through LaGuardia to get to Knoxville. J. had to fly unaccompanied through Atlanta. All I had to do was fly from DCA to Knoxville. How tricky could that be?

I did have one important task: I was responsible for shepherding our cooler, filled with dry ice and Kosher meat, to Knoxville. But I'd carefully read the TSA instructions on Dry Ice and even talked to someone at American Airlines ahead of time: as long as I followed the rules (used a breathable cooler, properly labeled and brought less than 5lbs of dry ice) I'd be fine. I confidently approached the baggage check-in area and handed the cooler and its precious contents to the ticket agent. She looked at the dry ice label and wordlessly walked over to a coworker. A few moments later she came back and checked me in. Well that was easy, I thought.

The short flight to Knoxville was leaving from the infamous Gate 35x, but even this didn't phase me. I'd be on the ground with our vacation starting in no time!

As the shuttle bus parked at our plane, I couldn't help but notice my cooler pulled aside from all the luggage. Neat, I thought, the cooler made it. As the doors opened an airport employee pointed to the cooler: will the owner of the cooler see me?

Uh oh.

The employee explained to me: the cooler wasn't weighed and was missing a hazmat label, and therefore couldn't fly. In a moment of panic I thought I forgot a step, but no, the weighing and labeling of the dry ice needed to be done by the airline not me. Why had they skipped this process? Why had TSA let the cooler make it as far as the plane? No answers were provided to these questions. The employee was adamant: this cooler can not be placed on this plane. The flight attended suggested that I carry the cooler on; no, that wouldn't do. The pilot herself suggested that she'd take ownership of the cooler and fly it as her own. Again, that was a no go.

By now, the tiny plane had been loaded and I was obviously holding us up.

Fine, I'll dump the dry ice and just take the cooler as is. No, that's not allowed either, I was informed. I had two choices: abandon the cooler or take a later flight. Neither option really worked.

Finally, someone managed to 'uncheck' the bag and allow me to dispose of the dry ice and fly with the cooler stuffed under the seat in front of me. Part way through the flight, the flight attendant came by and offered me a big chunk of ice to keep it cold.

Clearly everyone was trying their hardest, but rules were rules. So yeah, if you fly with dry ice, do make sure they weigh and tag it!

The rest of my trip to Knoxville was uneventful. I met J. at his flight and found him in good spirits. 20 minutes later Shira and D. landed and our vacation had officially begun!

Tomorrow we start our adventure in earnest! Gatlinburg, here we come!

Wednesday, July 17, 2019

Don't Fear the x, and other lessons from Shamir Secret Sharing

Consider these facts about this 3rd degree polynomial:

f(x) = 1822 + 3x + 19x2 + 2x3

Fact 1. This is considered a 3rd degree polynomial because the largest exponent value is 3. While looking at this equation gives me a burst of High School Math PTSD, it need not. Upon further reflection, it's just a bit of terse code. I could program the above as:

(define f (lambda (x)
            (+ 1822 
               (* 3 x)
               (* 19 (expt x 2))
               (* 2  (expt x 3)))))

Because all polynomials have the same shape, it's easy to make a function that generates polynomials:

(define (make-poly coeffs)
  (lambda (x)
    (let loop ((coeffs coeffs)
               (i 0)
               (sum 0))
      (cond ((null? coeffs) sum)
             (loop (cdr coeffs)
                   (+ i 1)
                   (+ sum (* (car coeffs) (^ x i)))))))))

With this 'make' function, I can replace the above code with the following:

(define f (make-poly '(1822 3 19 2)))

I can call f with as many values of x as I wish:

> (for-each (lambda (x) (show (cons x (f x)))) (range 0 10))
((0 . 1822))
((1 . 1846))
((2 . 1920))
((3 . 2056))
((4 . 2266))
((5 . 2562))
((6 . 2956))
((7 . 3460))
((8 . 4086))
((9 . 4846))
((10 . 5752))

Fact 2. Evaluating a polynomial at x = 0 always returns the value of the first constant term. We see this above, as 0 maps to 1822. This will continue to hold true no matter how ugly and complex the polynomial is.

Fact 3. Using Lagrange Interpolating polynomials we can use degree + 1 values of a polynomial to construct a function which will return values equivalent to the original polynomial. That is, if we feed any four values above into make-lagr-poly we end up with a new function fg which computes the same values as f.

(define fg (make-lagr-poly '((3 . 2056)
                            (6 . 2956)
                            (7 . 3460)
                            (10 . 5752))))

> (for-each (lambda (x) (show (cons x (fg x)))) (range 0 10))
((0 . 1822))
((1 . 1846))
((2 . 1920))
((3 . 2056))
((4 . 2266))
((5 . 2562))
((6 . 2956))
((7 . 3460))
((8 . 4086))
((9 . 4846))
((10 . 5752))

The how and why of Lagrange polynomials will have to wait for another blog post. But for now, trust me that this bit of math-magic works.

Cryptographer Adi Shamir combined the above facts to create something quite useful: a way to securely share a secret among a group.

Consider this problem:

You need to distribute a vault code to bank executives, however you'd like to avoid a number of pitfalls. For one, you want to make sure that a single lost or stolen code doesn't allow an attacker into the vault. You'd also like to insure that at least 4 executives need to agree on the requirement of opening the vault before it can be accessed. This reduces the chances that a small number of executives will coordinate to steal from the vault.

Shamir used the above math to create Shamir Secret Sharing. Here's how it works. First, note your secret, which in this case is the vault code. We'll use 12345 as our code. Then decide on your threshold, that is the number of executives that are required before the secret can be revealed. We'll use 4 as suggested above. Finally, decide on how many shares you want to give out. Let's assume there are 10 executives, so we'll generate 10 shares.

Step one is to form a polynomial of threshold - 1 degree, where the first term is the secret:

;; values 4, 19 and 103 are random numbers, and should be
;; generated in a secure and truly random way.
(define secret (make-poly '(12345 4 19 103))) 

Step two is to generate the 10 shares, one for each executive:

> (for-each (lambda (x) (show (cons x (secret x)))) (range 1 10))
((1 . 12471))
((2 . 13253))
((3 . 15309))
((4 . 19257))
((5 . 25715))
((6 . 35301))
((7 . 48633))
((8 . 66329))
((9 . 89007))
((10 . 117285))

Note how we start x at 1 and not 0. Calling this function with 0 reveals our secret.

We give each pair of numbers to an executive and instruct them to keep the pair private. If an attacker obtains less than 4 shares, then the secret remains safe and no clues about the it are revealed.

When the vault needs to be opened, 4 executives pool their shares to calculate a function equivalent to the original polynomial:

(define soln (make-lagr-poly '((1 . 12471)
                               (5 .  25715)
                               (9 . 89007)
                               (10 . 117285))))

Finally, to derive the secret we pass 0 to the solution function:

> (soln 0)

While the above implementation captures the essence of Shamir Secret Sharing, it's missing an important step to make it truly secure. I may cover that in a future post.

I had two takeaways from my experience implementing Shamir Secret Sharing. First, it's fascinating to see how mathematical properties can be combined to solve everyday problems

And Second, I found the math behind this scheme to be initially quite overwhelming. I struggled to both understand the mathematical notation, as well as how to convert it to code. And then it hit me: I've got a programming language that let's me directly implement polynomial and Lagrange interpolations functions; I don't need to think of them as purely symbolic expressions like so many of the examples on the web do. Putting this in terms of code and not math made all the difference for me.

What a joy it was to untangle what initially appeared to be so complex.

Find my implementation of Shamir Secret Sharing here. Find the original paper by Adi Shamir here. Enjoy!

Friday, July 05, 2019

A Low End Camera Shootout

We've got some travel with our nieces and nephew coming up, so it was time to dust off my collection of makePads and insure everything was ready for the kids to start creating.

The BLU Advance 4.0 has been working well as the makePad's hardware platform. The device is compact, cheap and functional. At $40 each, I can comfortably hand the phone to a kid and not worry how rough they'll play with it. One nagging limitation, however, is the quality of the camera. At 5 megapixels, the photos are fine at small sizes, but any degree of enlargement reveals how sub-par the pics are. I've wrestled with this pretty extensively: do the photos really need to be high quality? Could the lower quality be considered a creative constraint? How big a jump in megapixels would I need before I saw a notable difference?

Thankfully, Shira stepped in with a suggestion: swap the thought experiment with actual tests. That is, buy a bunch of BLU phones and do a photo shootout. And that's what I did.

The photos below are from a BLU Advance 4.0, BLU Advance 5.2 and a BLU R2 Plus. These have 5, 8 and 13 megapixel cameras respectively:

I found the results of this test to be insightful. At low resolutions, for example the thumbnails above, all the cameras look about the same. When enlarged, it's obvious at the A5 is better quality than the A4 and the R2 is leaps ahead of the A5. However, the jump from the 5 megapixel A4 to 8 megapixel A5 isn't as dramatic as I'd hoped. It's only when you hit 13 megapixels that you start to see some real improvement. So trading up to A5 from the A4 to gain 3 megapixels isn't worth it.

This is perhaps the best advertisement I've ever seen for a point and shoot digital camera. I'm tempted to try out this $40, 20 megapixel camera to see if it can deliver high quality pictures at a budget price tag. Though, a stand alone camera loses out to even the A4 in terms of versatility, so I'm not sure what that test would ultimately teach me.

Bottom line: unless I want to splurge on the $100/device, I'm just going to have to accept the cruddy image quality of the BLU A4.

Wednesday, July 03, 2019

One Wicked Bug: Fixing a PHP Upload Error #3

A few weeks back one of my customers started seeing a new issue with a recently updated Ionic app: uploading files resulted in a PHP Upload error #3, UPLOAD_ERR_PARTIAL. I'd come across the usual PHP upload errors before: needing to increase post_max_size, upload_max_filesize and max_execution_time. But these settings had never caused an UPLOAD_ERR_PARTIAL before.

One of the problems with the UPLOAD_ERR_PARTIAL error is how little information you're given. Apache and PHP report no errors, and there's no way to see the underlying web request that caused the problem. Using mod_security and mitmproxy gave me some interesting data, but nothing conclusive. mod_security reported a 408 Timeout and using mitmproxy actually corrected the problem altogether.

What had changed was replacing the standard Angular HTTP Client with a Cordova specific plugin. I'd found some evidence that iOS had timeout issues due to Keep-Alives, and thought that may be in play. Turning off Keep-Alive didn't help. I then swapped out the Ionic plugin, first for the FileTranser plugin, then for the native HTTP plugin. The problem persisted.

I then switched back to the Angular HTTP Client, and to my surprise, the issue showed up there as well.

I was just about to swap out Apache for Nginx, when it occurred to me that I hadn't investigated the issue from the Apache side of things. Attacking the problem from this angle yielded this hit on Google: Can't upload larger files to server. This individual ran into a similar issue and for once I got a new answer:

Found the problem. The Apache had a RequestReadTimeout header=20-40,MinRate=500 body=20-40,MinRate=500 setting which means the request's forced to timeout after max 40 sec... Another thing to watch out for.

I quick Google search of Apache RequestReadTimeout turned up a module I had no idea even existed, let alone one that was turned on by default. The mod_reqtimeout does what you think it does: if it takes too long to read a request, the Apache truncates the request. I enabled the logging the module suggested (LogLevel reqtimeout:info) and had my customer try again. Sure enough, the module reported that the request timed out.

Suddenly, the whole scenario made sense. My customer's slow connection was causing his uploads to be timed-out and truncated. From PHP's perspective the file was indeed a partial upload. Putting mitmproxy in between my client and Apache 'fixed' the problem because mitmproxy read the entire request slowly, reported it to me, and then delivered it all at once to Apache, which made Apache quite happy.

Not only am I pleased that I've fixed this bug, but I'm happy to have uncovered yet another critical Apache setting. Heck, just being reminded that you can turn up Apache's logging by setting LogLevel was an invaluable take away from all of this.

And here was I blaming Ionic, Cordova and iOS when the issue was fully due to my Apache ignorance.

Tuesday, July 02, 2019

More Columbia Pike Adjacent Fishing

Given the fishing success I had at Holmes Run, I was curious if a creek-like section of Four Mile Run would have similar results. So last night I made my way back down Columbia Pike and took advantage of the free parking at Arlington Mill Community Center.

I took the stairway down to the run and walked North. The water level was low, but I remained undeterred. Within 50 yards of entering the run I found what appeared to be a pool that was a couple of feet deep. I tossed in a small orange grubby plastic thing and bam! a sunfish snatched it up. I had my answer: there were definitely fish here!

For the next hour I made my way up the run, finding pools and pulling in sunfish. Obviously, they weren't huge, but for the size of the run they were actually pretty chubby.

At one point I came across a point where faster water was flowing between two rocks. I could see some larger fish--perhaps trout--hanging out in this section. I tried variety of baits, but they weren't biting. Still, this tells me that there's definitely interesting fish to be had in this section of the run.

Like Holmes Run, I was impressed at the solitude and nature factor. It was easy to forget that I was just a few hundred feet from homes and buildings. It was a relaxing time, with only the occasional runner, walker or biker passing by to break the illusion that I was fishing in a pristine wilderness.

If you're looking for an easy fishing option, look no further than Four Mile Run near the Arlington Mill Community Center. What you'll give up in terms of stalking monster fish, you'll make back in convenience and simplicity. And I'm so going back with more gear to try to land me some trout.

Monday, July 01, 2019

A Very DC Ultra Run

A few years back I enjoyed this read: I Walked 64 Miles Around the Beltway. What the Hell Was I Thinking? It's the tale of two friends tracing a path, as close as possible, to the DC beltway. The TL;DR version is this: they got way more than they bargained for. What seemed like a novel idea turned into a death march. An entertaining to read death march, but a death march none the less.

I enjoyed the story because it's a wonderful reminder that adventure doesn't require hopping a plane to a far off land. There are challenges and discoveries to be had in our own backyard. It's also a humble reminder that miles always seem small on paper. How gruelling can a 64 mile hike be? Turns out, very.

Why mention this story now? Over the weekend, Shira shared this tweet with me:

That's right, fellow Aringtonian Michael Wardian *ran* around the beltway. In one day. And he didn't cover 64 miles; his route took him almost 90. Even Mother-Nature didn't want to cooperate, and made the weather extra hot for his run.

You can read more details of Michael's run here and here. You can see the route he took here.

In short, what a superhuman effort.

Friday, June 28, 2019

Discovering the Goldmine that is the M5Stack Arduino Environment

When I last played with my M5Stack Fire device I had figured out a development environment and had my code for my first trivial app written. The app, if you can even call it that, accepted button input and made web requests to a particular URL with the button label appended. I was able to get both button detection and URL invocation working. Alas, when I combined these operations into one program the result was a system crash. Such a pain.

Out of frustration I put the device down planning to return it with fresh eyes.

This week I picked up the device and thought I'd try another approach to programming it. I'd had a recollection that the Arduino IDE had M5Stack support. A quick Google search turned up a YouTube video that promised I'd have this environment setup in less than 5 minutes.

Impressively, the video wasn't far off from its 5 minute promise. There were a bunch of steps, but everything fell into place and before I knew it, I'd configured the Arduino dev environment to work with my Fire.

But the part that blew me away was all the example code that's available:

For a good 20 minutes I was a kid in a candy store: opening up demos, running them, and ooh-and-ahhing as my M5 came to life. Bluetooth, WiFi, microphone support, LED support--there's code to demonstrate it all. If I can see the individual examples running, I should be able to mix and match the pieces into interesting projects. So. Cool.

Returning to my original challenge, I used the Display, Button and BasicHttpClient examples to work up my little button triggered URL app.

I ran into one gotcha during the process. At some point, attempts to flash new code to the device resulted in the cryptic message:

Arduino: 1.8.9 (Mac OS X), Board: "M5Stack-FIRE, Enabled, Default (2 x 6.5 MB app, 3.6 MB SPIFFS), 115200, None"

Sketch uses 344988 bytes (5%) of program storage space. Maximum is 6553600 bytes.
Global variables use 16604 bytes (0%) of dynamic memory, leaving 4505380 bytes for local variables. Maximum is 4521984 bytes. v2.6
Serial port /dev/cu.SLAB_USBtoUART
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
MAC: 84:0d:8e:25:aa:34
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Warning: Could not auto-detect Flash size (FlashID=0xffffff, SizeID=0xff), defaulting to 4MB
Compressed 8192 bytes to 47...

A fatal error occurred: Timed out waiting for packet content
A fatal error occurred: Timed out waiting for packet content

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.

I eventually arrived at a simple fix: hold down the power button on the device while Arduino is compiling and flashing the code. Easy peasy.

You might be wondering, what good is having a small device where a button press triggers a URL call? Thanks to Tasker and AutoRemote, it's possible to make your phone take essentially any action in response to a web request to a special public URL. For example, I setup a trivial Tasker profile that plays an ear splitting sound when button B is pressed on the M5. This is useful when I've put my phone down in my house and lost track of it; something that seems to happen constantly.

While I'm excited that I've got my first baby M5Stack project completed I'm even more excited to have this mountain of demo code to play with. Now the real fun can begin!

Here's the code to power the behavior described above, as well as some action shots. Enjoy!

 * Pressy!
#include <M5Stack.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>

WiFiMulti WiFiMulti;

#define AUTOREMOTE_BASE String("")
#define AUTOREMOTE_KEY String("--auto remote key goes here--")
#define AUTOREMOTE_PREFIX String("Pressy_")

void clearScreen() {
  M5.Lcd.println("Ready: ");

void autoRemote(String btn) {
  String url = AUTOREMOTE_BASE + "?key=" + AUTOREMOTE_KEY + "&message=" + AUTOREMOTE_PREFIX + btn;
  HTTPClient http;
  M5.Lcd.print(btn + "...");

void setup() {
  // init lcd, serial, but don't init sd card
  M5.begin(true, false, true);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.print("Setting up WiFi:");

  WiFiMulti.addAP("SSID", "PASSWORD");

  while( != WL_CONNECTED) {


// Add the main program code into the continuous loop() function
void loop() {

  // if you want to use Releasefor("was released for"), use .wasReleasefor(int time) below
  if (M5.BtnA.wasReleased()) {
  } else if (M5.BtnB.wasReleased()) {
  } else if (M5.BtnC.wasReleased()) {
  } else if (M5.BtnB.wasReleasefor(700)) {

Tuesday, June 25, 2019

Review: Crossed by Ally Condie

Crossed by Ally Condie was, for me, a dud. It had to have some redeeming qualities as I continued to listen to it (more on that below), but overall it just wasn't a fit.

I started reading Crossed randomly without any clue to its content. I quickly realized that it was in the Distopian-Young-Adult genre, à la Hunger Games. The problem is, the Hunger Games series ended up being so awful that anything I read that's remotely similar triggers a bad reaction. This, of course, is totally unfair to Crossed. For all I know Crossed could have been written well before Hunger Games (checks Amazon: nope, Crossed was published years after Hunger Games).

The other strike against Crossed is the whole young-adult aspect. I get it; you're a teenager and you're wrestling with your feelings. And you've got a crush on someone, and you so want to hold their hand and get that first kiss. Yes, it's cute. But apparently, I'm so over it.

Perhaps I need to track down an adventure story about a couple who's been married for decades. They've had crushes on each other, and their share of blow-out-fights. There's no question of will-they or won't-they end up together, because the are together. It isn't cutesy, but it's effective. By now, they're an unstoppable team that can read each other's minds and anticipate each other's moves. What they lack in fawning over each other, they make up for in support, love and respect. I'd so read that.

The final strike against Crossed was that it's book #2 in a trilogy, so by its very nature it has to be a transitional story. In other words, for all the action nothing really happens. Clearly reading book #2 first was a mistake, and that's what I get for going in cold. I suppose if the story had been riveting, I would be inspired to read the the other two. But alas, it was just OK, so I'll pass on the series.

Kvetching aside, Crossed isn't without its redeeming qualities. The characters are pretty likable, even if they are a bit insufferable. And the story didn't descend into the pointless abyss that the Hunger Games did. I also enjoyed using the clues provided to figure out the rules of the distopian society, an exercises needed because I skipped the first book.

While I don't totally get why, I thought it was clever to emphasize the beauty and power of poetry. I for one found myself Googling various poems, inspired by the book. If Crossed manages to get teens to read and think about poetry then I'll gladly take back any and all of my complaints.

I also liked the suggestions that the distopian society evolved not out of pure evil, but out of the intention to do good. Again, I didn't read the first book, so my interpretation of this may be wrong. It seems that in an attempt to cure cancer and most likely other woes, humans have backed themselves into a corner. Ah yes, the power of unintended consequences. Like the emphasis on poetry, this is another great topic for teens to encounter and explore.

For the right (and intended) audience, Crossed and the other books in the series are almost certainly a winner. Kids should dig the love story, and parents should appreciate that their kids are being exposed to important themes. It's a win-win. Just not for me.

Monday, June 24, 2019

mitmproxy: X-Ray Vision for Web Developers

Last week I found myself needing to debug a PHP upload error. The problem: PHP was reporting a UPLOAD_ERR_PARTIAL without giving specifics. For starters, I was curious what data was being uploaded. This turned out to be trickier to discover than I thought.

First, I tried using the file_get_contents('php://input') trick to grab the bytes being uploaded. This doesn't work, because as the manual reports, php://input is not available with enctype="multipart/form-data". In other words: you're out luck if you're dealing with file uploads.

Undeterred, I decided to move up the stack a bit. If PHP wasn't going to reveal the raw data, then I'd simply ask Apache to log this information for me. This lead me to try mod_dumpio, mod_dumpost and mod_security. mod_security was the closest to a viable solution, as it didn't just log the data but also gave me the option of extracting and storing the data being uploaded. The problem with mod_security is its complexity; it does so much. I found this post pointed me in the right direction to getting mod_security at least partially configured. I still wasn't happy with this solution.

I decided to take a step even further back: could I run some sort of proxy server that could sit between my browser and Apache?

I can, and I did! I give you the well named: Man-in-the-Middle Proxy, or mitmproxy for short.

I was skeptical about how much work setting up mitmproxy would be. I cheated and grabbed a binary download for linux. I then made a single tweak to apache: instead of listening on port 80, I changed it to listen on port 8080.

# Listen 80
Listen 8080

I restarted apache and then kicked off mitmproxy from the command line like so:

$ sudo ./mitmproxy -p 80 \
        --set keep_host_header=true \
        --mode reverse:http://localhost:8080

I pointed my web browser to the usual location and started accessing my web app. To my surprise, everything Just Worked. Flipping back to the mitmproxy screen, I saw my browser requests streaming in. At this point I started to appreciate the interactive side of mitmproxy. I realized I could choose to inspect individual requests on the fly, including those with multi-part file uploads. Finally, I could easily inspect these mysterious failed PHP uploads.

I've used Proxy Servers in the past to aid with debugging, but mitmproxy takes this to a new level. It's definitely going into my bag of tricks for future debugging sessions.

Friday, June 21, 2019

Gift-of-a-Playlist, Now Even More Gift'ier

It bothered me that Gift-of-a-Playlist started off with all of the YouTube videos visible. I want to give recipients a chance to read what I've written, then view the videos.

So I added a big 'ol Reveal button on top of each clip:

With that feature implemented, I can officially create my brother's birthday gift and send it off to him; only 1 month late. Not bad!

You can try this little app for yourself by visiting: and plugging in the sample playlist document:

I'm telling you, gifting playlists should so be a thing. And now they are.

Thursday, June 20, 2019

A Dopp Kit Dump

This past weekend I was organizing a shelf full of travel stuff and realized my dopp kit was missing. Did I leave it at a hotel? Or maybe I just put it back in a non-standard location? Regardless, I was bummed. Not because the kit contained anything of significant value. No, the problem was that after years of travel I'd finally tuned it to be Just Right. It was split into two sections: a compact set of toiletries and a collection of handy utilities. I didn't relish the idea of rebuilding it.

Fortunately, I found it. But before I put it away I dumped the contents and took a few pictures. If I do lose my kit, I'll now have a much easier time recreating it.

So here it is, my finely tuned travel toiletry setup:

The razor is an old Gillette Sensor Excel with off brand blades. It's a compact and inexpensive setup; perfect for travel. The little plastic bags contain soap and deodorant respectively.

The deodorant solution took me years to arrive at. I found the easy-to-pack deodorant crystal rock to be ineffective. And Shira wasn't having my, 'but I'm traveling I don't need to wear deodorant' philosophy. So now I buy a travel size deodorant, remove the tray of antiperspirant material and discard the bulky plastic shell. The result takes up far less space, yet is just as effective as any other standard deodorant.

The blue container has a blob of Bed Head Men Matte Separation Workable Wax in it. I find that stuff beefy enough to keep my hair relatively in check.

The utility side is more interesting. I keep quite a few over the counter meds in there, including allergy, cold and antidiarrheal pills. When you need any of these pills, they're a life saver.

I intentionally keep caffeinated and decaffeinated tea bags on hand, knowing the caffeinated options can be used to help keep me awake.

I carry the thumb-sized USB light for the same reason I carry a Bic lighter: it has an impressive size to functionality ratio. One common use of the light: plug it into a USB charger in the bathroom and use it as a nightlight.

Do you carry any unusual items in your dopp kit? If so, do share in the comments below.


Related Posts with Thumbnails