├── license ├── MimePart.as ├── readme ├── MimeFile.as ├── MimeString.as └── HTTPForm.as /license: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006 David Verhasselt 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /MimePart.as: -------------------------------------------------------------------------------- 1 | /** 2 | * MimePart Class 3 | * 4 | * (C) David Verhasselt 2007 5 | * Licensed under the MIT license 6 | */ 7 | package crowdway.http 8 | { 9 | import flash.utils.ByteArray; 10 | 11 | /** 12 | * Interface that defines the message parts a HTTPForm is compiled from. 13 | * 14 | * @see HTTPForm 15 | * @see MimeString 16 | * @see MimeFile 17 | */ 18 | public interface MimePart 19 | { 20 | /** 21 | * Get the name of this field 22 | * 23 | * @return The name of this field, for example "age" 24 | */ 25 | function getName():String; 26 | 27 | /** 28 | * Get the filename of this field (only for file-types). If there is none or not available, it returns null. 29 | * 30 | * @return The filename of this file, for example "horse.txt", or null 31 | */ 32 | function getFilename():String; 33 | 34 | /** 35 | * Get the head-string of this field, or null if not available. 36 | * 37 | * A head-string starts with "Content-Type: " and includes the content-type, an optional charset-parameter, and an optional boundary. 38 | * 39 | * @return The head-string, or null if not available. Does not include the final CRLF 40 | */ 41 | function getHead():String; 42 | 43 | /** 44 | * Get the actual content of this field 45 | * 46 | * @return The content of this field, not including the final CRLF 47 | */ 48 | function getBody():ByteArray; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /readme: -------------------------------------------------------------------------------- 1 | Crowdway.http Actionscript 3 Library 2 | ------------------------------------ 3 | 4 | This little library helps you create HTTP requests which a server would interpret as true HTML form submissions. It is possible to set the encoding for each field, please check out the source code for specifics. 5 | 6 | Put these files in subdirectories ./crowdway/http/ under the root folder of your project, so that the actionscript compiler will be able to reach them. 7 | 8 | Example code: 9 | 10 | httpForm = new HTTPForm("http://www.google.com"); 11 | httpForm.addEventListener(flash.events.Event.COMPLETE, eventCompleteHandler); 12 | 13 | httpForm.addField(MimeString.fromString("name", "David Verhasselt")); // Add field named "name" 14 | httpForm.addField(MimeString.fromString("email", "david@crowdway.com")); // Add field named "email" 15 | httpForm.addField(MimeFile.fromByteArray("avatar", "david_at_the_beach_07.jpg", filedata)); // Add filefield named "avatar" 16 | httpForm.send(); 17 | 18 | function eventCompleteHandler(event:Event):void 19 | { 20 | var loader:URLLoader = URLLoader(event.target); 21 | trace(loader.data); 22 | } 23 | 24 | The code above emulates a form with 3 inputfields: 25 | 26 | - an inputbox named "name" with value "David Verhasselt" 27 | - an inputbox named "email" with value "david@crowdway.com" 28 | - a fileinput named "avatar" in which the user selected a file named "david_at_the_beach_07.jpg" 29 | with contents the ByteArray filedata 30 | 31 | It is sent to http://www.google.com. The server at google.com will think it is receiving a form submission with an attached image. The result will be trace()d. 32 | 33 | 2009 Addendum: The code above works perfectly with Flash 9. However, starting with Flash 10, Adobe has issued some really constrictive security policies which don't really increase security in my opinion. One of them, explained in [1], pertains to any networking call that contains a file upload. Practically, starting with Flash 10, you can send file uploads using httpForm or an alternative only when it is instigated by a user action. For example, only in an onClick eventhandler or a function called by that eventhandler will httpForm.send() work. If you haven't attached a MimeFile with a filename then this restriction doesn't apply. 34 | 35 | If you are willing to forgo the server recognizing the file as a fileupload, you can still send the form with the file added as a regular, raw binary inputfield by dismissing the filename: 36 | 37 | httpForm.addField(MimeFile.fromByteArray("avatar", null, filedata)); 38 | 39 | The server won't recognize it as a file anymore, but the POST-field named "avatar" will still contain the binary filedata. 40 | 41 | [1] http://www.adobe.com/devnet/flashplayer/articles/fplayer10_security_changes_print.html#head34 42 | 43 | (c) 2006-2012 David Verhasselt 44 | Licensed under the MIT license 45 | -------------------------------------------------------------------------------- /MimeFile.as: -------------------------------------------------------------------------------- 1 | /** 2 | * MimeFile Class 3 | * 4 | * (C) David Verhasselt 2007 5 | * Licensed under the MIT license 6 | */ 7 | package crowdway.http 8 | { 9 | import crowdway.http.MimePart; 10 | 11 | import flash.utils.ByteArray; 12 | import flash.events.*; 13 | import mx.utils.StringUtil; 14 | 15 | import flash.net.*; 16 | 17 | 18 | /** 19 | * Implements the "file" input-field 20 | * 21 | * @see MimePart 22 | * @see MimeString 23 | * @see HTTPForm 24 | */ 25 | public class MimeFile implements MimePart 26 | { 27 | private static const crlf:String = "\r\n"; 28 | 29 | // Content parameters 30 | private var name:String; 31 | private var fileName:String; 32 | private var contentType:String; 33 | private var charset:String; 34 | private var content:ByteArray; 35 | 36 | 37 | /** 38 | * Factory Method: Creates a text/plain MimeFile with the supplied name, filename, content and optional charset. 39 | * 40 | * @param name The name of the new MimeFile 41 | * @param fileName The filename of the new MimeFile 42 | * @param content The content of the new MimeFile 43 | * @param charset The optional charset of the content. Defaults to "us-ascii" 44 | * 45 | * @return A new MimeFile based on the supplied parameters 46 | */ 47 | static public function fromText(name:String,fileName:String,content:String,charset:String = "us-ascii"):MimeFile 48 | { 49 | var result:MimeFile = new MimeFile(); 50 | 51 | result.name = name; 52 | result.fileName = fileName; 53 | result.contentType = "text/plain"; 54 | result.content = new ByteArray(); 55 | result.content.writeMultiByte(content,charset); 56 | result.charset = charset; 57 | 58 | return result; 59 | } 60 | 61 | /** 62 | * Factory Method: Creates a binary MimeFile with the supplied name, filename, content and optional content-type. 63 | * 64 | * @param name The name of the new MimeFile 65 | * @param fileName The filename of the new MimeFile 66 | * @param content The content of the new MimeFile 67 | * @param contentType The optional content-type of the content. Defaults to "application/octet-stream" 68 | * 69 | * @return A new MimeFile based on the supplied parameters 70 | */ 71 | static public function fromByteArray(name:String,fileName:String,content:ByteArray,contentType:String = "application/octet-stream"):MimeFile 72 | { 73 | var result:MimeFile = new MimeFile(); 74 | 75 | result.name = name; 76 | result.fileName = fileName; 77 | result.contentType = contentType; 78 | result.content = content; 79 | 80 | return result; 81 | } 82 | 83 | public function getName():String 84 | { 85 | return this.name; 86 | } 87 | 88 | public function getFilename():String 89 | { 90 | return this.fileName; 91 | } 92 | 93 | 94 | public function getHead():String 95 | { 96 | var head:String = "Content-Type: "; 97 | 98 | head += this.contentType; 99 | 100 | if (this.charset != null) 101 | { 102 | head += "; charset=" + this.charset; 103 | } 104 | 105 | return head; 106 | } 107 | 108 | 109 | 110 | public function getBody():ByteArray 111 | { 112 | this.content.position = 0; 113 | return this.content; 114 | } 115 | 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /MimeString.as: -------------------------------------------------------------------------------- 1 | /** 2 | * MimeString Class 3 | * 4 | * (C) David Verhasselt 2007 5 | * Licensed under the MIT license 6 | */ 7 | package crowdway.http 8 | { 9 | import crowdway.http.MimePart; 10 | 11 | import flash.utils.ByteArray; 12 | import flash.events.*; 13 | import mx.utils.StringUtil; 14 | 15 | import flash.net.*; 16 | 17 | /** 18 | * Implements any input-field from a form that can be represented as a string 19 | * 20 | * @see MimePart 21 | * @see MimeFile 22 | * @see HTTPForm 23 | */ 24 | public class MimeString implements MimePart 25 | { 26 | private const crlf= "\r\n"; 27 | 28 | // Content parameters 29 | private var name:String; 30 | private var contentType:String; 31 | private var charset:String; 32 | private var content:String; 33 | 34 | /** 35 | * Factory-Method: Creates a new MimeString with the supplied name, content, and optional charset 36 | */ 37 | 38 | public static function fromString(name:String,content:String,charset:String = null) 39 | { 40 | var result:MimeString = new MimeString(); 41 | 42 | result.name = name; 43 | result.setContent(content,charset); 44 | 45 | return result; 46 | } 47 | 48 | 49 | /** 50 | * Set the content of this field. 51 | * 52 | * If charset is set, content-type and charset are populated and the content is written with an extra linebreak at the end, as in this example: 53 | * From: Nathaniel Borenstein 54 | * To: Ned Freed 55 | * Subject: Sample message 56 | * MIME-Version: 1.0 57 | * Content-type: multipart/mixed; boundary="simple boundary" 58 | * This is the preamble. It is to be ignored, though it 59 | * is a handy place for mail composers to include an 60 | * explanatory note to non-MIME compliant readers. 61 | * --simple boundary 62 | * 63 | * This is implicitly typed plain ASCII text. 64 | * It does NOT end with a linebreak. 65 | * --simple boundary 66 | * Content-type: text/plain; charset=us-ascii 67 | * 68 | * This is explicitly typed plain ASCII text. 69 | * It DOES end with a linebreak. 70 | * 71 | * --simple boundary-- 72 | * This is the epilogue. It is also to be ignored 73 | * 74 | * @param content The new content of the field 75 | * @param charset The optional charset of the string, if none set, uses default us-ascii 76 | */ 77 | private function setContent(content:String, charset:String = null) 78 | { 79 | if (charset != null) 80 | { 81 | this.contentType = "text/plain"; 82 | this.charset = charset; 83 | //this.content.writeMultiByte(content + crlf,charset); 84 | this.content = content; 85 | } 86 | 87 | else 88 | { 89 | this.contentType = null; 90 | this.charset = null; 91 | //this.content.writeMultiByte(content,"us-ascii"); 92 | this.content = content; 93 | } 94 | } 95 | 96 | 97 | public function getName():String 98 | { 99 | return this.name; 100 | } 101 | 102 | public function getFilename():String 103 | { 104 | return null; 105 | 106 | } 107 | 108 | public function getHead():String 109 | { 110 | if (this.contentType == null) 111 | { 112 | return null; 113 | } 114 | 115 | else 116 | { 117 | return "Content-Type: " + this.contentType + "; charset=" + this.charset; 118 | } 119 | } 120 | 121 | public function getBody():ByteArray 122 | { 123 | var result:ByteArray = new ByteArray(); 124 | 125 | if (this.contentType != null) 126 | { 127 | result.writeMultiByte(this.content, this.charset); 128 | } 129 | 130 | else 131 | { 132 | result.writeUTFBytes(this.content); 133 | } 134 | 135 | return result; 136 | } 137 | 138 | } 139 | } 140 | 141 | -------------------------------------------------------------------------------- /HTTPForm.as: -------------------------------------------------------------------------------- 1 | /* HTTPForm Class 2 | * 3 | * Class HTTPForm fakes a form-submission via POST. Includes support for all INPUT-type arguments. 4 | * 5 | * (C) David Verhasselt 2006 6 | * Licensed under the MIT license 7 | */ 8 | 9 | package crowdway.http 10 | { 11 | import crowdway.http.MimeFile; 12 | import crowdway.http.MimePart; 13 | import crowdway.http.MimeString; 14 | 15 | import flash.utils.ByteArray; 16 | import flash.events.*; 17 | import mx.utils.StringUtil; 18 | 19 | import flash.net.*; 20 | 21 | 22 | /** 23 | * Class that implements a method to fake html-form uploads using POST-method. Supports all possible fields and file-uploads. 24 | * 25 | * @see MimePart 26 | * @see MimeFile 27 | * @see MimeString 28 | */ 29 | public class HTTPForm implements IEventDispatcher 30 | { 31 | private const crlf:String = "\r\n"; 32 | private const boundaryLength:Number = 10; 33 | 34 | // Variables for net-connection 35 | private var request:URLRequest; 36 | private var loader:URLLoader; 37 | 38 | // Variables for content 39 | private var autoBoundary:String; 40 | private var fields:Array; 41 | 42 | 43 | /** 44 | * Generates a random boundary of "length" characters. 45 | * 46 | * Boundary can exist out of upper-case and lower-case alphanumericals. 47 | * 48 | * @param length The length of the boundary to be generated 49 | * @return The randomly generated boundary 50 | */ 51 | private function generateBoundary(length:Number):String 52 | { 53 | var boundary:String = new String(); 54 | var map:Array = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z", 55 | "A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z", 56 | "0","1","2","3","4","5","6","7","8","9"]; 57 | 58 | for (var i:int = length; i > 0; i--) 59 | { 60 | boundary += map[Math.floor(Math.random()*map.length)]; 61 | } 62 | 63 | return boundary; 64 | } 65 | 66 | /** 67 | * Generates the body to be sent to the server 68 | * 69 | * @param boundary The boundary to be used to split the message-parts 70 | * @return All the messages from fields compiled in 1 ByteArray 71 | */ 72 | public function getBody(boundary:String = null):ByteArray 73 | { 74 | var body:ByteArray = new ByteArray(); 75 | 76 | if (!boundary) 77 | { 78 | if (!autoBoundary) 79 | { 80 | autoBoundary = generateBoundary(boundaryLength); 81 | } 82 | 83 | boundary = autoBoundary; 84 | } 85 | 86 | for each (var field:MimePart in this.fields) 87 | { 88 | body.writeUTFBytes("--" + boundary + crlf); 89 | 90 | // Add the head of the part 91 | body.writeUTFBytes("content-disposition: form-data; name=\"" + field.getName() + "\""); 92 | 93 | if (field.getFilename() != null) 94 | { 95 | body.writeUTFBytes("; filename=\"" + field.getFilename() + "\""); 96 | } 97 | 98 | body.writeUTFBytes(crlf); 99 | 100 | if (field.getHead() != null) 101 | { 102 | body.writeUTFBytes(field.getHead() + crlf); 103 | } 104 | 105 | body.writeUTFBytes(crlf); 106 | 107 | // Add the body and end of the part 108 | body.writeBytes(field.getBody()); 109 | body.writeUTFBytes(crlf); 110 | } 111 | 112 | body.writeUTFBytes("--" + boundary + "--" + crlf); 113 | 114 | return body; 115 | } 116 | 117 | 118 | 119 | /** 120 | * Creates a new HTTPForm object with "url" being the URL of the destination 121 | * 122 | * @url The url of the destination 123 | */ 124 | public function HTTPForm(url:String) 125 | { 126 | this.request = new URLRequest(url); 127 | this.loader = new URLLoader(); 128 | 129 | this.fields = new Array(); 130 | 131 | // Events 132 | this.loader.addEventListener(flash.events.Event.COMPLETE, checkComplete); 133 | this.loader.addEventListener(flash.events.HTTPStatusEvent.HTTP_STATUS, function():void { trace("Processer - URLLoader.httpStatus"); }); 134 | this.loader.addEventListener(flash.events.ProgressEvent.PROGRESS, function():void { trace("Processor - URLLoader.progress"); }); 135 | this.loader.addEventListener(flash.events.IOErrorEvent.IO_ERROR, function(event:Event):void { trace("Procesosr - URLLoader.ioError"); trace(event);}); 136 | this.loader.addEventListener(flash.events.SecurityErrorEvent.SECURITY_ERROR, function():void { trace("Processor ( URLLoader.securityError"); }); 137 | } 138 | 139 | private function checkComplete(event:Event):void 140 | { 141 | trace("Complete"); 142 | var loader2:URLLoader = URLLoader(event.target); 143 | trace("Length: " + loader2.bytesTotal); 144 | trace("Data: " + loader2.data); 145 | } 146 | 147 | /** 148 | * Returns the correct contentType to be set, including the boundary. 149 | * 150 | * @param boundary The boundary to be used. Must be consistent with getBody() 151 | */ 152 | public function getContentType(boundary:String = null):String 153 | { 154 | if (!boundary) 155 | { 156 | if (!autoBoundary) 157 | { 158 | autoBoundary = generateBoundary(boundaryLength); 159 | } 160 | 161 | boundary = autoBoundary; 162 | } 163 | 164 | return "multipart/form-data; boundary=\"" + boundary + "\""; 165 | } 166 | 167 | /** 168 | * Adds a message-part (an input-field or file) to be sent to the destination to this HTTPForm 169 | * 170 | * @param field The field to be added 171 | */ 172 | public function addField(field:MimePart):void 173 | { 174 | this.fields.push(field); 175 | } 176 | 177 | /** 178 | * Compiles all the fields together, and sends this HTTPForm to the destination specified on creation 179 | */ 180 | public function send():void 181 | { 182 | var boundary:String = generateBoundary(10); 183 | 184 | this.request.method = URLRequestMethod.POST; 185 | this.request.contentType = getContentType(boundary); 186 | this.request.data = getBody(boundary); 187 | this.loader.load(request); 188 | } 189 | 190 | 191 | // IEventDispatcher Implementation 192 | public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void 193 | { 194 | this.loader.addEventListener(type, listener, useCapture, priority, useWeakReference); 195 | } 196 | 197 | public function dispatchEvent(event:Event):Boolean 198 | { 199 | return this.loader.dispatchEvent(event); 200 | } 201 | 202 | public function hasEventListener(type:String):Boolean 203 | { 204 | return this.loader.hasEventListener(type); 205 | } 206 | 207 | public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void 208 | { 209 | return this.loader.removeEventListener(type, listener, useCapture); 210 | } 211 | 212 | public function willTrigger(type:String):Boolean 213 | { 214 | return this.loader.willTrigger(type); 215 | } 216 | } 217 | } 218 | --------------------------------------------------------------------------------