Using POST arrays can be a useful and powerful method for handling user input. In the last post I showed how to do some basic form handling using an Apache module, and I mentioned it would be nice at a later date to handle POST arrays, and it turn out to be less of a drama than I thought it might be. My main concern was that APR tables are really only intended to index strings, that said take a look at this function prototype
apr_table_set(apr_table_t *t, const char *key, const char *val);
The value that is stored is a char pointer, but regardless what a pointer is pointing to (its "type") a pointer is just a pointer - any pointer regardless of type is the same size. With some simple casting we can store some other type of pointer, in this instance a pointer to another APR table. I've used this technique a number of times with different libraries without any issues.
Most of the additional code is in post_example_handler but I'll show you the additional function to get POST array elements first.
const char* GetPostVarIdx(request_rec *r, apr_table_t* tab, const char* var, int idx) { apr_table_t* arr = (apr_table_t*)apr_table_get(tab, var); if (!arr) return zstr; // might not even be a post request... char *aidx = apr_itoa(r->pool, idx); if (!aidx) return zstr; const char* val = apr_table_get(arr, aidx); if (!val) { val = zstr; } else { val = ap_escape_html(r->pool, val); } return val; }
Again this function has been written in such away that even if the request isn't a POST, it will simply return an empty string without causing any issues, without even the need to check for a NULL pointer. The index could be a string but in practice it's more than sufficient to use an integer for the key.
Using this function is straight forward.
for (int i=9; i<12; i++) { // so the indexes are different length "9","10" str = GetPostVarIdx(r, tab, "in", i); ap_rprintf(r, "<input type=\"input\" name=\"in[%i]\" value=\"%s\">%s<br>\n", i, str, str); }
Parsing the POST variables is exactly the same for none arrays, but understandably a little more involved for arrays, here is just the code that populates the array tables.
if (lft) { // we have a left bracket int osb,csb; // Open/Close Square Bracket offset osb = lft - pair->name; rgt = strchr(pair->name, ']'); csb = rgt - pair->name; if (rgt) { // and a right bracket int i=0; // copy out the index part of the name for (char* adr = lft + 1; adr < rgt; adr++) { aidx[i] = adr[0]; i++; } aidx[i] = 0; // copy out the part of the name before the left bracket char* rootname = apr_palloc(r->pool, osb+2); apr_cpystrn(rootname, pair->name, osb+1); apr_table_t* arr; // see if the array has been used before if not // create it arr = (apr_table_t*)apr_table_get(tab, rootname); if (!arr) { arr = apr_table_make(r->pool, 1); // set the variable array element with the array apr_table_setn(tab, rootname,(char *)arr); } // the variables array is set with the value apr_table_set(arr, aidx, buffer); } }
The first job is to see if we actually have an array variable, by checking for square brackets, then the variable name before the square brackets is used for the index. If that variable hasn't been encountered before a table is created for it, or the existing one is used to store the POST variables value.
I dare say there might be some corner cases you can think of that these routines couldn't cope with, but then my aim is to keep to keep it simple and by and large I think I've covered all the use cases I'll need (at least for now!)
I may well look at PHP's source to see how they they bundle POST arrays, I may see some interesting tricks...
You can get the full source here
Enjoy!