I’m having a lot of fun learning Amber Smalltalk. It’s great having an IDE right inside the browser. My code changes in the IDE save and commit back to the webserver via WebDAV or NodeJS.
I also own a few shared hosting accounts which don’t offer WebDAV or NodeJS. They also forbid HTTP PUT. But I’d still like to be able to develop Amber projects on those sites too.
So I decided to modify Amber’s Browser>>commitPackage method to upload files as FormData via regular HTTP POST.
FormData is a newer XMLHttpRequest interface, so your browser compatibility may be an issue.
I made a few modifications to the Browser class (under the IDE package).
First, I created a helper method which uses the FormData object.
commitPackageAsFormDataToPath: path withFileData: fileData
| formData apiKey|
apiKey := self class commitAPIKey.
<
formData = new FormData();
formData.append('path',path);
formData.append('fileData',fileData);
formData.append('commitAPIKey',apiKey);
>.
selectedPackage ifNotNil: [
jQuery ajax: #{
#url -> self class commitScriptURL.
#type -> 'POST'.
#data -> formData.
#contentType -> false.
#processData -> false.
#success -> [ :data | console log: data ]
}.
] |
commitPackageAsFormDataToPath: path withFileData: fileData
| formData apiKey|
apiKey := self class commitAPIKey.
<
formData = new FormData();
formData.append('path',path);
formData.append('fileData',fileData);
formData.append('commitAPIKey',apiKey);
>.
selectedPackage ifNotNil: [
jQuery ajax: #{
#url -> self class commitScriptURL.
#type -> 'POST'.
#data -> formData.
#contentType -> false.
#processData -> false.
#success -> [ :data | console log: data ]
}.
]
Next, commitPackageAsFormData will call the helper method defined above.
commitPackageAsFormData
| d path fileData |
self commitPackageAsFormDataToPath: (self class commitPathJs, '/', selectedPackage, '.js') withFileData: (Exporter new exportPackage: selectedPackage).
self commitPackageAsFormDataToPath: (self class commitPathJs, '/', selectedPackage, '.deploy.js') withFileData: (StrippedExporter new exportPackage: selectedPackage).
self commitPackageAsFormDataToPath: (self class commitPathSt, '/', selectedPackage, '.st') withFileData: (ChunkExporter new exportPackage: selectedPackage). |
commitPackageAsFormData
| d path fileData |
self commitPackageAsFormDataToPath: (self class commitPathJs, '/', selectedPackage, '.js') withFileData: (Exporter new exportPackage: selectedPackage).
self commitPackageAsFormDataToPath: (self class commitPathJs, '/', selectedPackage, '.deploy.js') withFileData: (StrippedExporter new exportPackage: selectedPackage).
self commitPackageAsFormDataToPath: (self class commitPathSt, '/', selectedPackage, '.st') withFileData: (ChunkExporter new exportPackage: selectedPackage).
Now I override the standard commitPackage method (Since this is a destructive edit, you may want to first rename it to something else)
commitPackage
self commitPackageAsFormData |
commitPackage
self commitPackageAsFormData
Almost done… I still need to define a few class-side methods and then make the server-side commit script.
commitScriptURL
^'/amber/commit.php' |
commitScriptURL
^'/amber/commit.php'
For security, I will prompt for a key to be sent to the server-side commit script
commitAPIKey
| key |
commitAPIKey ifNil: [
<key = prompt('Enter the API Key');>.
commitAPIKey := key.
].
^ commitAPIKey |
commitAPIKey
| key |
commitAPIKey ifNil: [
<key = prompt('Enter the API Key');>.
commitAPIKey := key.
].
^ commitAPIKey
I need to create the class-side instance variable to store the key I prompted for
Browser class instanceVariableNames: 'commitAPIKey' |
Browser class instanceVariableNames: 'commitAPIKey'
Finally, I need to create the server-side commit.php script
<?php
$all = Array();
$all = array_merge($all,$_POST);
$all = array_merge($all,$_GET);
$correct_apikey = "Sometimes I Doubt Your Commitment to Sparkle Motion"; //make this your own
$root = dirname(__FILE__);
$path = $all['path'];
$data = $all['fileData'];
$data = stripslashes($data);
$fullpath = $root . '/' . $path;
$allowed = preg_match('/^js/',$path) || preg_match('/^st/',$path);
if (!$allowed) {
die('invalid path ' . $path);
}
if ($all['commitAPIKey'] != $correct_apikey) {
die('invalid API key, not allowed to commit');
}
if (empty($data)) {
die('empty data');
}
$fp = fopen($fullpath, "w");
$written = fwrite($fp, $data);
fclose($fp);
echo "OK, " . $written . " bytes written";
?> |
<?php
$all = Array();
$all = array_merge($all,$_POST);
$all = array_merge($all,$_GET);
$correct_apikey = "Sometimes I Doubt Your Commitment to Sparkle Motion"; //make this your own
$root = dirname(__FILE__);
$path = $all['path'];
$data = $all['fileData'];
$data = stripslashes($data);
$fullpath = $root . '/' . $path;
$allowed = preg_match('/^js/',$path) || preg_match('/^st/',$path);
if (!$allowed) {
die('invalid path ' . $path);
}
if ($all['commitAPIKey'] != $correct_apikey) {
die('invalid API key, not allowed to commit');
}
if (empty($data)) {
die('empty data');
}
$fp = fopen($fullpath, "w");
$written = fwrite($fp, $data);
fclose($fp);
echo "OK, " . $written . " bytes written";
?>
And there we go. Hacking Amber on a Shared Hosting Provider using HTTP POST and FormData.
EDIT: I updated the code relating to commitAPIKey