├── lib ├── multipart.js ├── utils.js ├── write.js ├── parse.js └── old.js ├── package.json ├── test ├── parse.js ├── write.js └── fixture.js └── README.md /lib/multipart.js: -------------------------------------------------------------------------------- 1 | 2 | exports.lib = 3 | { parse : require("./parse") 4 | , write : require("./write") 5 | }; 6 | 7 | exports.parser = exports.lib.parse.parser; 8 | exports.writer = exports.lib.write.writer; 9 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 2 | exports.emit = emit; 3 | exports.error = error; 4 | 5 | function error (emitter, message) { 6 | emitter.error = new Error(message); 7 | emit(emitter, "onError", emitter.error); 8 | if (emitter.error) throw emitter.error; 9 | } 10 | function emit (emitter, ev, data) { 11 | if (emitter[ev]) emitter[ev](data); 12 | else if (emitter[ev.toLowerCase()]) emitter[ev.toLowerCase()](data); 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { "name" : "multipart" 2 | , "version" : "0.0.0" 3 | , "description" : "A JavaScript library for parsing and writing multipart messages" 4 | , "contributors" : 5 | [ "Isaac Z. Schlueter " 6 | , "John Wright " 7 | ] 8 | , "repository" : 9 | { "type" : "git" 10 | , "url" : "http://github.com/isaacs/multipart-js.git" 11 | } 12 | , "main" : "lib/multipart" 13 | } 14 | -------------------------------------------------------------------------------- /test/parse.js: -------------------------------------------------------------------------------- 1 | 2 | var multipart = require("../lib/multipart") 3 | , assert = require("assert") 4 | , sys = require("sys") 5 | , fixture = require("./fixture") 6 | , testPart = function (expect, part) { 7 | sys.debug("test part: "+sys.inspect(expect)); 8 | if (!expect) { 9 | throw new Error("Got more parts than expected: "+ 10 | sys.inspect(part.headers)); 11 | } 12 | for (var i in expect) { 13 | assert.equal(expect[i], part[i]); 14 | } 15 | } 16 | , messages = fixture.messages 17 | , p = multipart.parser() 18 | , expect 19 | , e 20 | ; 21 | 22 | p.onPartBegin = function (part) { 23 | testPart(expect[e++], part); 24 | } 25 | 26 | for (var i = 0, l = messages.length; i < l; i ++) { 27 | var message = messages[i] 28 | , expect = message.expect 29 | , e = 0 30 | , body = message.body 31 | , b = "" 32 | , c = 0 33 | ; 34 | p.headers = message.headers; 35 | while (b = body.charAt(c++)) p.write(b); 36 | p.close(); 37 | } 38 | 39 | // again, but this time, instead of using the headers, just set the boundary. 40 | for (var i = 0, l = messages.length; i < l; i ++) { 41 | var message = messages[i] 42 | , expect = message.expect 43 | , e = 0 44 | , body = message.body 45 | , b = "" 46 | , c = 0 47 | ; 48 | p.boundary = message.boundary; 49 | while (b = body.charAt(c++)) p.write(b); 50 | p.close(); 51 | } 52 | 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # multipart-js 2 | 3 | A JavaScript library for parsing and writing multipart messages. 4 | 5 | ## Current State 6 | 7 | Pre-pre-alpha. Almost nothing is here, and what is here is likely completely broken. 8 | 9 | If you are asking about this, you probably ought to check out Felix's 10 | [node-formidable](https://github.com/felixge/node-formidable) library. 11 | 12 | ## Usage 13 | 14 | If you're familiar with [sax-js](http://github.com/isaacs/sax-js), then most of this should 15 | be pretty straightforward. Attach event handlers, call functions, close it when you're 16 | done. Please keep fingers and dangling clothing away from the state machine. 17 | 18 | var multipart = require("multipart"); 19 | 20 | // parsing 21 | var parser = multipart.parser(); 22 | 23 | // in all event handlers, "this" is the parser, and "this.part" is the 24 | // part that's currently being dealt with. 25 | parser.onpartbegin = function (part) { doSomething(part) }; 26 | parser.ondata = function (chunk) { doSomethingElse(chunk) }; 27 | parser.onend = function () { closeItUp() }; 28 | 29 | // now start feeding the message through it. 30 | // you can do this all in one go, if you like, or one byte at a time, 31 | // or anything in between. 32 | parser.boundary = "foo"; 33 | var chunk; 34 | while ( chunk = upstreamThing.getNextChunk() ) { 35 | parser.write(chunk); 36 | } 37 | parser.close(); 38 | 39 | 40 | // writing 41 | var writer = multipart.writer(); 42 | 43 | // attach event handlers for the things we care about. 44 | writer.ondata = function (chunk) { doSomething(chunk) }; 45 | writer.onend = function () { closeItUp() }; 46 | 47 | // now trigger the events to fire by feeding files through it. 48 | writer.boundary = "foo"; 49 | writer.partBegin({ "content-type" : "text/plain", filename : "foo.txt" }); 50 | var chunk; 51 | while ( chunk = getChunkOfFile() ) { 52 | writer.write(chunk); 53 | } 54 | writer.partEnd(); 55 | writer.close(); 56 | -------------------------------------------------------------------------------- /test/write.js: -------------------------------------------------------------------------------- 1 | var multipart = require("../lib/multipart") 2 | , assert = require("assert") 3 | , sys = require("sys") 4 | , fixture = require("./fixture") 5 | , messages = fixture.messages 6 | , aSimpleMessage = fixture.aSimpleMessage 7 | , aNestedMessage = messages[0] 8 | , writer = multipart.writer() 9 | , parser = multipart.parser() 10 | , expect 11 | , e 12 | ; 13 | 14 | 15 | sys.debug("Create a multipart writer."); 16 | sys.debug(""); 17 | assert.notEqual(writer, null, "should be able to obtain writer"); 18 | 19 | function output(msg) { 20 | sys.debug(" writer " + msg + "."); 21 | } 22 | 23 | errorHandlerCalled = false; 24 | errorMessage = ""; 25 | lastChunk = ""; 26 | 27 | writer.onError = function (err) { 28 | assert.notEqual(err.message, undefined, "should pass Error object to onError handler"); 29 | errorMessage = err.message; 30 | output("emitted error: " + errorMessage); 31 | errorHandlerCalled = true; 32 | } 33 | 34 | writer.onData = function (chunk) { 35 | lastChunk = chunk 36 | output("emitted data: " + lastChunk); 37 | parser.write(chunk); 38 | } 39 | 40 | writer.onEnd = function () { 41 | output("ended"); 42 | } 43 | 44 | parser.onError = function (error) { 45 | assert.ok("false", "parser encounted error: " + error.message) 46 | } 47 | 48 | parser.onPartBegin = function (part) { 49 | sys.debug("parser started part successfully " + sys.inspect(part.headers)); 50 | } 51 | 52 | parser.onPartEnd = function (part) { 53 | sys.debug("parser ended part succesfully"); 54 | } 55 | 56 | parser.onEnd = function () { 57 | sys.debug("parser ended"); 58 | } 59 | 60 | parser.headers = aSimpleMessage.headers; 61 | 62 | sys.debug("Write a part without setting boundary..."); 63 | try { 64 | writer.partBegin(aSimpleMessage.parts[0].part); 65 | assert.ok(errorHandlerCalled, "should emit onError if part written without boundary"); 66 | }catch (error) { 67 | sys.puts(error.message); 68 | assert.ok(errorHandlerCalled, "should emit onError if part written without boundary"); 69 | } 70 | 71 | sys.debug("Set the boundary property and try again..."); 72 | writer.boundary = aSimpleMessage.boundary; 73 | writer.partBegin(aSimpleMessage.parts[0].part); 74 | 75 | sys.debug("Write the body...") 76 | writer.write(aSimpleMessage.parts[0].body); 77 | 78 | sys.debug("Start another part without finishing..."); 79 | try { 80 | writer.partBegin(aSimpleMessage.parts[1].part); 81 | } catch (error1) { 82 | assert.ok(errorHandlerCalled, "should emit onError if part written without finishing the part before"); 83 | } 84 | sys.debug("Whoops, end the last part..") 85 | writer.partEnd(); 86 | 87 | sys.debug("Start another simple part"); 88 | writer.partBegin(aSimpleMessage.parts[1].part); 89 | sys.debug("Write body and finish.") 90 | writer.write(aSimpleMessage.parts[1].body); 91 | writer.partEnd(); 92 | writer.close(); 93 | parser.close(); 94 | 95 | sys.debug("Ok, looks good. Now write a more complicated nested multipart"); 96 | 97 | writer = multipart.writer(); 98 | parser = multipart.parser(); 99 | 100 | function testPart(expect, part) { 101 | sys.debug("test part: "+sys.inspect(part)); 102 | if (!expect) { 103 | throw new Error("Got more parts than expected: "+ 104 | sys.inspect(part.headers)); 105 | } 106 | for (var i in expect) { 107 | //assert.equal(expect[i], part[i]); 108 | } 109 | } 110 | 111 | parser.onPartBegin = function (part) { 112 | testPart(expect[e++], part); 113 | } 114 | 115 | writer.onData = function (chunk) { 116 | parser.write(chunk); 117 | } 118 | //a nested test from the fixtures 119 | 120 | var expect = aNestedMessage.expect 121 | , e = 0; 122 | parser.headers = aNestedMessage.headers; 123 | writer.boundary = aNestedMessage.boundary; 124 | writer.partBegin(aNestedMessage.expect[0]); //start inner 1 mixed 125 | writer.partBegin(aNestedMessage.expect[1]); //start inner 2 mixed 126 | writer.partBegin(aNestedMessage.expect[2]); //inner 2, part 1 127 | writer.write("hello, world"); 128 | writer.partEnd(); 129 | writer.partBegin(aNestedMessage.expect[3]); //inner 2, part 2 130 | writer.write("hello to the world"); 131 | writer.partEnd(); 132 | writer.partEnd(); //finish inner2 133 | writer.partEnd(); //finish inner1 134 | writer.partBegin(aNestedMessage.expect[4]); //start inner 3 mixed 135 | writer.partBegin(aNestedMessage.expect[5]); 136 | writer.write("hello, free the world"); // inner 3, part1 137 | writer.partEnd(); 138 | writer.partBegin(aNestedMessage.expect[6]); // inner 3, part 2 139 | writer.write("hello, for the world") 140 | writer.partEnd(); 141 | writer.partEnd(); //end inner 3 142 | writer.partBegin(aNestedMessage.expect[7]); // outer, part1 143 | writer.write("hello, outer world"); 144 | writer.partEnd(); 145 | writer.partEnd(); //finish outer 146 | writer.close(); 147 | parser.close(); 148 | 149 | -------------------------------------------------------------------------------- /lib/write.js: -------------------------------------------------------------------------------- 1 | // This API should be a symmetrical mirror of the writer API in writer.js 2 | // Instead of having onpartbegin and onpartend events, call the partBegin 3 | // and partEnd methods, and write file contents. Then, the writer emits 4 | // "data" events with chunks of data suitable for sending over the wire. 5 | // That is, it converts the data objects into one big string. 6 | 7 | // var w = writer(); 8 | // w.boundary = "foo-bar-bazfj3980haf38h"; 9 | // w.contentType = "form-data"; 10 | // w.headers = {...}; 11 | // w.partBegin({...}); // send the headers, causes the initial "data" event to emit 12 | // w.write("..."); // encode the data, wrap it, etc., and emit more "data" events 13 | // w.partEnd(); // close off that part, emitting a "data" event with the --boundary 14 | // w.partBegin({...}); // another part... 15 | // w.partBegin({...}); // if the last one was multipart, then do a child, else error. 16 | // w.partEnd(); // close off that child part 17 | // w.partEnd(); // close off the parent 18 | // w.close(); // close off all open parts, and emit "end" event 19 | 20 | var sys = require("sys") 21 | , utils = require("./utils") 22 | , error = utils.error 23 | , emit = utils.emit 24 | , multipartExpression = new RegExp( 25 | "^multipart\/(" + 26 | "mixed|rfc822|message|digest|alternative|" + 27 | "related|report|signed|encrypted|form-data|" + 28 | "x-mixed-replace|byteranges)", "i") 29 | , EVENTS = exports.EVENTS = ["onData", "onEnd", "onError"] 30 | , CR = "\r" 31 | , LF = "\n" 32 | , CRLF = CR+LF 33 | ; 34 | 35 | var S = 0; 36 | exports.STATE = 37 | { STARTED : S++ //nothing received 38 | , PART_STARTED : S++ // a part header was written 39 | , WRITING : S++ // client is writing a part 40 | , PART_ENDED : S++ // a end part was written 41 | , CLOSED : S++ // close was called 42 | }; 43 | for (S in exports.STATE) exports.STATE[exports.STATE[S]] = S; 44 | S = exports.STATE; 45 | 46 | exports.writer = writer; 47 | exports.Writer = Writer; 48 | 49 | function NYI () { throw new Error("Not yet implemented") } 50 | 51 | // Returns a new writer. 52 | // Attaches event handlers to it, and they'll get notified 53 | // This writer does not write http headers. 54 | // It only emits a properly encoded multipart stream suitable to being streamed to 55 | // a http body or any other place multipart data is needed 56 | function writer () { return new Writer() } 57 | 58 | function Writer () { 59 | this.firstPartReceived = false; 60 | this.state = S.STARTED; 61 | this.depth = 0; 62 | this.parts = []; 63 | } 64 | 65 | // Starts a part or nested part. 66 | // Emits data events to listeners with headers for part. 67 | // 68 | // Errors if part is added to a part of type other than multipart, 69 | // or if new part is started before the old one is written correctly. 70 | // 71 | // Params: A part headers object 72 | // These should be of the form: 73 | // {name:"test", type:"multipart/mixed", filename="foo.txt"} 74 | // Type is required and is encoded as "Content-Disposition" 75 | // Name is required and is encoded as the name of the part. 76 | // If set, filename is set as the filename of the part. 77 | // Optionally pass in a headers object of other headers 78 | // e.g Content-Length, which will be appended to the headers. 79 | Writer.prototype.partBegin = function (part, headers) { 80 | var writer = this 81 | , type = part["type"] 82 | , partString = "" 83 | , boundary = part["boundary"] 84 | , name = part["name"] 85 | , filename = part["filename"] 86 | , currentPart = writer.parts[writer.parts.length-1]; 87 | ; 88 | 89 | //sys.debug("writer depth:" + writer.depth); 90 | //sys.debug('writing part: ' + sys.inspect(part)); 91 | 92 | if (!writer.boundary) error(writer, "Missing property boundary on writer"); 93 | 94 | if (!type && !filename) { 95 | error(writer, "Missing required type property on part."); 96 | } 97 | 98 | if (!type && filename) { 99 | type = "inline"; 100 | } else { 101 | type = "multipart/" + type; 102 | } 103 | 104 | if (writer.state === S.WRITING) 105 | return error(writer, "Illegal state. Cannot begin new part right now."); 106 | 107 | if (writer.state === S.PART_STARTED && currentPart.type !== "mixed") 108 | return error(writer, "Bad format. Cannot add part to non multipart parent."); 109 | 110 | partString += "--" + writer.boundary + CRLF; 111 | 112 | //write the Content-Disposition 113 | partString += "Content-Type: " + type; 114 | if (name) partString += "; name:'" + name + "'"; 115 | if (filename) partString += "; filename='" + filename + "'"; 116 | if (boundary) partString += "; boundary=" + boundary; 117 | partString += CRLF; 118 | // go down a nested part 119 | if (type === "mixed") writer.depth++; 120 | 121 | //write out any optional other headers 122 | if (headers) { 123 | Object.keys(headers).forEach(function (key) { 124 | partString += key + ": " + headers[key] + CRLF; 125 | }); 126 | } 127 | emit(writer, "onData", partString + CRLF); 128 | writer.state = S.PART_STARTED; 129 | writer.parts.push(part); 130 | } 131 | 132 | // Writes a chunk to the multipart stream 133 | // Sets error if not called after a partBegin 134 | Writer.prototype.write = function (chunk) { 135 | var writer = this; 136 | 137 | if (chunk === null) return; 138 | 139 | if (writer.state !== S.PART_STARTED) 140 | error(writer, "Illegal state. Must call partBegin before writing."); 141 | 142 | // TODO - encode the data if base64 content-transfer-encoding 143 | emit(writer, "onData", chunk); 144 | writer.state = S.WRITING; 145 | } 146 | 147 | // Writes the part end 148 | // E.g. /r/n--boundary-- 149 | Writer.prototype.partEnd = function () { 150 | var writer = this 151 | , currentPart = writer.parts[writer.parts.length-1] 152 | ; 153 | 154 | if (currentPart && currentPart.type !== "mixed" && writer.state !== S.WRITING) 155 | error(writer, "Illegal state. Must write at least one chunk to this part before calling partEnd"); 156 | 157 | emit(writer, "onData", CRLF + "--" + writer.boundary + "--" + CRLF); 158 | writer.state = S.PART_ENDED; 159 | currentPart = writer.parts.pop(); 160 | if (currentPart && currentPart.type === "mixed") writer.depth--; 161 | } 162 | 163 | 164 | Writer.prototype.close = function () { 165 | var writer = this; 166 | if (!writer.depth && writer.state === S.PART_ENDED) return emit(writer, "onEnd"); 167 | error(writer, "Illegal state. Multiparts not written completely before close.") 168 | }; 169 | 170 | 171 | 172 | // Write the headers plus 2 CRLFs, e.g.: 173 | // Content-Type: multipart/form-data; boundary=---------------------------7d44e178b0434 174 | // Host: api.flickr.com 175 | // Content-Length: 35261 176 | // 177 | // 178 | function writeHttpHeaders(writer) { 179 | if (!writer.contentType) 180 | error(writer, "Missing contentType property. Must set this property on writer."); 181 | 182 | if (!multipartExpression.test(writer.contentType)) 183 | error(writer, "Invalid property 'contentType'. Must be one of multipart(|" + 184 | "mixed|rfc822|message|digest|alternative|" + 185 | "related|report|signed|encrypted|form-data|" + 186 | "x-mixed-replace|byteranges)"); 187 | 188 | if (!writer.boundary) 189 | error(writer, "Missing data. Must set boundary property on writer."); 190 | 191 | writer.headers = writer.headers || {} 192 | var headerString = ""; 193 | writer.headers["Content-Type"] = writer.contentType + "; boundary=" + writer.boundary; 194 | Object.keys(writer.headers).forEach(function (key) { 195 | headerString += key + ": " + writer.headers[key]; 196 | }); 197 | emit(writer, "onData", headerString + CRLF + CRLF); 198 | } 199 | 200 | 201 | -------------------------------------------------------------------------------- /lib/parse.js: -------------------------------------------------------------------------------- 1 | 2 | var sys = require("sys") 3 | , utils = require("./utils") 4 | , error = utils.error 5 | , emit = utils.emit 6 | , wrapExpression = /^[ \t]+/ 7 | , multipartExpression = new RegExp( 8 | "^multipart\/(" + 9 | "mixed|rfc822|message|digest|alternative|" + 10 | "related|report|signed|encrypted|form-data|" + 11 | "x-mixed-replace|byteranges)", "i") 12 | , boundaryExpression = /boundary=([^;]+)/i 13 | , CR = "\r" 14 | , LF = "\n" 15 | , CRLF = CR+LF 16 | , MAX_BUFFER_LENGTH = 16 * 1024 17 | 18 | // parser states. 19 | var S = 0 20 | exports.STATE = 21 | { NEW_PART : S++ 22 | , HEADER : S++ 23 | , BODY : S++ 24 | }; 25 | for (S in exports.STATE) exports.STATE[exports.STATE[S]] = S; 26 | S = exports.STATE; 27 | 28 | // events for discoverability 29 | exports.EVENTS = [ "onPartBegin", "onPartEnd", "onData", "onEnd", "onError" ]; 30 | 31 | exports.parser = function parser () { return new Parser() } 32 | exports.Parser = Parser; 33 | 34 | // the parser's "parts" object is a nested collection of the header objects 35 | // check the parser's "part" member to know what it's currently chewin on. 36 | // this.part.parent refers to that part's containing message (which may be 37 | // the parser itself) 38 | // child messages inherit their parent's headers 39 | function Parser () { 40 | this.buffer = ""; 41 | this.part = this; 42 | this.state = S.NEW_PART; 43 | // handy for debugging bad input 44 | this.position = this.column = this.line = 0; 45 | this.parent = this; 46 | this.type = this.headers = this.isMultiPart = this.boundary = null; 47 | this.test = true; 48 | } 49 | 50 | Parser.prototype.write = function (chunk) { 51 | // sys.debug("write: "+chunk); 52 | var parser = this 53 | , part = parser.part 54 | // write to the buffer, and then process the buffer. 55 | parser.buffer += chunk; 56 | 57 | while (parser.buffer) { 58 | switch (parser.state) { 59 | case S.NEW_PART: 60 | // part is a multipart message. 61 | // we're either going to start reading a new part, or we're going to 62 | // end the current part, depending on whether the boundary has -- at 63 | // the end. either way, we expect --boundary right away. 64 | if (!parser.part.isMultiPart) { 65 | multipartHeaders(parser.part); 66 | } 67 | if (!parser.part.isMultiPart) { 68 | error(parser, "Expected multipart message (did you set the headers?)"); 69 | } 70 | var boundary = parser.part.boundary 71 | , len = boundary.length 72 | , offset = parser.buffer.indexOf(boundary) 73 | if (offset === -1) { 74 | if (parser.buffer.length > len) { 75 | error(parser, "Malformed: boundary not found at start of message"); 76 | } 77 | // keep waiting for it. 78 | return; 79 | } 80 | if (offset > 0) { 81 | error(parser, "Malformed: data before the boundary"); 82 | return; 83 | } 84 | if (parser.buffer.length < (len + 2)) { 85 | // we'll need to see either -- or CRLF after the boundary. 86 | // get it on the next pass. 87 | return; 88 | } 89 | if (parser.buffer.substr(len, 2) === "--") { 90 | // this message is done. 91 | // chomp off the boundary and crlf and move up 92 | if (parser.part !== parser) { 93 | // wait to see the crlf, unless this is the top-level message. 94 | if (parser.buffer.length < (len + 4)) return; 95 | if (parser.buffer.substr(len+2, 2) !== CRLF) { 96 | error(parser, "Malformed: CRLF not found after boundary"); 97 | return; 98 | } 99 | } 100 | parser.buffer = parser.buffer.substr(len + 4); 101 | emit(parser, "onPartEnd", parser.part); 102 | parser.part = parser.part.parent; 103 | parser.state = S.NEW_PART; 104 | continue; 105 | } 106 | if (parser.part !== parser) { 107 | // wait to see the crlf, unless this is the top-level message. 108 | if (parser.buffer.length < (len + 2)) return; 109 | if (parser.buffer.substr(len, 2) !== CRLF) { 110 | error(parser, "Malformed: CRLF not found after boundary"); 111 | return; 112 | } 113 | } 114 | // walk past the crlf 115 | parser.buffer = parser.buffer.substr(len + 2); 116 | // mint a new child part, and start parsing headers. 117 | parser.part = startPart(parser.part); 118 | parser.state = S.HEADER; 119 | continue; 120 | case S.HEADER: 121 | // just grab everything to the double crlf. 122 | var headerEnd = parser.buffer.indexOf(CRLF+CRLF) 123 | if (headerEnd === -1) { 124 | if (parser.buffer.length > MAX_BUFFER_LENGTH) { 125 | error(parser, "Malformed: header unreasonably long."); 126 | } 127 | return; 128 | } 129 | var headerString = parser.buffer.substr(0, headerEnd) 130 | parseHeaderString(parser.part.headers, headerString, parser); 131 | // chomp off the header and the empty line. 132 | parser.buffer = parser.buffer.substr(headerEnd + 4); 133 | multipartHeaders(parser.part); 134 | 135 | // let the world know 136 | emit(parser, "onPartBegin", parser.part); 137 | 138 | if (parser.part.isMultiPart) { 139 | // it has a boundary and we're ready to grab parts out. 140 | parser.state = S.NEW_PART; 141 | } else { 142 | // it doesn't have a boundary, and is about to 143 | // start spitting out body bits. 144 | parser.state = S.BODY; 145 | } 146 | continue; 147 | case S.BODY: 148 | // look for parser.part.parent.boundary 149 | var boundary = parser.part.parent.boundary 150 | , offset = parser.buffer.indexOf(boundary) 151 | if (offset === -1) { 152 | // emit and wait for more data, but be careful, because 153 | // we might only have half of the boundary so far. 154 | // make sure to leave behind the boundary's length, so that we'll 155 | // definitely get it next time if it's on its way. 156 | var emittable = parser.buffer.length - boundary.length 157 | if (parser.buffer.substr(-1) === CR) emittable -= 1; 158 | if (parser.buffer.substr(-2) === CRLF) emittable -= 2; 159 | 160 | if (emittable > 0) { 161 | emit(parser, "onData", parser.buffer.substr(0, emittable)); 162 | parser.buffer = parser.buffer.substr(emittable); 163 | } 164 | // haven't seen the boundary, so wait for more bytes. 165 | return; 166 | } 167 | if (offset > 0) { 168 | var emittable = parser.buffer.substr(0, offset) 169 | if (emittable.substr(-2) === CRLF) { 170 | emittable = emittable.substr(0, emittable.length-2); 171 | } 172 | if (emittable) emit(parser, "onData", emittable); 173 | parser.buffer = parser.buffer.substr(offset); 174 | } 175 | 176 | // let em know we're done. 177 | emit(parser, "onPartEnd", parser.part); 178 | 179 | // now buffer starts with boundary. 180 | if (parser.buffer.substr(boundary.length, 2) === "--") { 181 | // message end. 182 | // parent ends, look for a new part in the grandparent. 183 | parser.part = parser.part.parent; 184 | if (parser.part !== parser) 185 | emit(parser, "onPartEnd", parser.part); 186 | parser.part = parser.part.parent; 187 | parser.state = S.NEW_PART; 188 | parser.buffer = parser.buffer.substr(boundary.length + 4); 189 | } else { 190 | // another part coming for the parent message. 191 | parser.part = parser.part.parent; 192 | parser.state = S.NEW_PART; 193 | } 194 | continue; 195 | } 196 | } 197 | } 198 | 199 | Parser.prototype.close = function () { 200 | emit(this, "onEnd"); 201 | Parser.call(this); 202 | } 203 | 204 | function parseHeaderString (headers, string, parser) { 205 | var lines = string.split(CRLF) 206 | , field, value, line 207 | for (var i = 0, l = lines.length; i < l; i ++) { 208 | line = lines[i]; 209 | if (line.match(wrapExpression)) { 210 | if (!field) { 211 | error(parser, "Malformed. First header starts with whitespace."); 212 | } 213 | value += line.replace(wrapExpression, " "); 214 | continue; 215 | } else if (field) { 216 | // now that we know it's not wrapping, put it on the headers obj. 217 | affixHeader(headers, field, value); 218 | } 219 | line = line.split(":"); 220 | field = line.shift().toLowerCase(); 221 | if (!field) { 222 | error(parser, "Malformed: improper field name."); 223 | } 224 | value = line.join(":").replace(/^\s+/, ""); 225 | } 226 | // now affix the last field. 227 | affixHeader(headers, field, value); 228 | } 229 | 230 | function affixHeader (headers, field, value) { 231 | if (!headers.hasOwnProperty(field)) { 232 | headers[field] = value; 233 | } else if (Array.isArray(headers[field])) { 234 | headers[field].push(value); 235 | } else { 236 | headers[field] = [headers[field], value]; 237 | } 238 | } 239 | 240 | function startPart (parent) { 241 | return { headers : {} 242 | , parent : parent 243 | }; 244 | } 245 | 246 | // check the headers of a message. If it wants to be multipart, 247 | // then we'll be returning true, and setting some additional data, 248 | // like type and boundary. 249 | function multipartHeaders (message) { 250 | // if it has a boundary already, then it's most likely the parser object, 251 | // and the user has told us what they expect the boundary to be. 252 | // take their word for it. 253 | if (message.boundary) { 254 | if (message.boundary.substr(0, 2) !== "--") { 255 | message.boundary = "--" + message.boundary; 256 | } 257 | return message.isMultiPart = true; 258 | } 259 | 260 | var field 261 | , val 262 | , contentType 263 | , contentDisposition = "" 264 | for (var h in message.headers) if (message.headers.hasOwnProperty(h)) { 265 | val = message.headers[h]; 266 | field = h.toLowerCase(); 267 | if (field === "content-type") { 268 | contentType = val; 269 | } else if (field === "content-disposition") { 270 | contentDisposition = val; 271 | } 272 | } 273 | 274 | if (!Array.isArray(contentDisposition)) { 275 | contentDisposition = contentDisposition.split(","); 276 | } 277 | contentDisposition = contentDisposition[contentDisposition.length - 1]; 278 | 279 | // Name and filename can come along with either content-disposition 280 | // or content-type. Well-behaved agents use CD rather than CT, 281 | // but sadly not all agents are well-behaved. 282 | [contentDisposition, contentType].forEach(function (h) { 283 | if (!h) return; 284 | var cd = h.split(/; */) 285 | cd.shift(); 286 | for (var i = 0, l = cd.length; i < l; i ++) { 287 | var bit = cd[i].split("=") 288 | , name = bit.shift() 289 | , val = stripQuotes(bit.join("=")) 290 | if (name === "filename" || name === "name") { 291 | message[name] = val; 292 | } 293 | } 294 | }); 295 | 296 | if (!contentType) return false; 297 | 298 | if (!Array.isArray(contentType)) contentType = contentType.split(","); 299 | contentType = contentType[contentType.length-1]; 300 | 301 | // make sure it's actually multipart. 302 | var mpType = multipartExpression.exec(contentType) 303 | if (!mpType) return false; 304 | 305 | // make sure we have a boundary. 306 | var boundary = boundaryExpression.exec(contentType) 307 | if (!boundary) return false; 308 | 309 | message.type = mpType[1]; 310 | message.boundary = "--" + boundary[1]; 311 | message.isMultiPart = true; 312 | return true; 313 | } 314 | 315 | function stripslashes(str) { 316 | // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) 317 | // + improved by: Ates Goral (http://magnetiq.com) 318 | // + fixed by: Mick@el 319 | // + improved by: marrtins 320 | // + bugfixed by: Onno Marsman 321 | // + improved by: rezna 322 | // + input by: Rick Waldron 323 | // + reimplemented by: Brett Zamir (http://brett-zamir.me) 324 | // * example 1: stripslashes("Kevin\'s code"); 325 | // * returns 1: "Kevin's code" 326 | // * example 2: stripslashes("Kevin\\\'s code"); 327 | // * returns 2: "Kevin\'s code" 328 | return (str+"").replace(/\\(.?)/g, function (s, n1) { 329 | switch(n1) { 330 | case "\\": 331 | return "\\"; 332 | case "0": 333 | return "\0"; 334 | case "": 335 | return ""; 336 | default: 337 | return n1; 338 | } 339 | }); 340 | } 341 | function stripQuotes (str) { 342 | str = stripslashes(str); 343 | return str.substr(1, str.length - 2); 344 | } 345 | -------------------------------------------------------------------------------- /lib/old.js: -------------------------------------------------------------------------------- 1 | 2 | var sys = require("sys"), 3 | events = require("events"), 4 | wrapExpression = /^[ \t]+/, 5 | multipartExpression = new RegExp( 6 | "^multipart\/(" + 7 | "mixed|rfc822|message|digest|alternative|" + 8 | "related|report|signed|encrypted|form-data|" + 9 | "x-mixed-replace|byteranges)", "i"), 10 | boundaryExpression = /boundary=([^;]+)/i, 11 | CR = "\r", 12 | LF = "\n", 13 | CRLF = CR+LF, 14 | MAX_BUFFER_LENGTH = 16 * 1024, 15 | 16 | // parser states. 17 | s = 0, 18 | S_NEW_PART = s++, 19 | S_HEADER = s++, 20 | S_BODY = s++; 21 | 22 | exports.parse = parse; 23 | exports.cat = cat; 24 | exports.Stream = Stream; 25 | 26 | // Parse a streaming message to a stream. 27 | // If the message has a "body" and no "addListener", then 28 | // just take it in and write() the body. 29 | function parse (message) { 30 | return new Stream(message); 31 | }; 32 | 33 | // WARNING: DONT EVER USE THE CAT FUNCTION IN PRODUCTION WEBSITES!! 34 | // It works pretty great, and it's a nice test function. But if 35 | // you use this function to parse an HTTP request from a live web 36 | // site, then you're essentially giving the world permission to 37 | // rack up as much memory usage as they can manage. This function 38 | // buffers the whole message, which is very convenient, but also 39 | // very much the wrong thing to do in most cases. 40 | function cat (message, callback) { 41 | var stream = parse(message); 42 | stream.files = {}; 43 | stream.fields = {}; 44 | stream.addListener("partBegin", function (part) { 45 | if (part.filename) stream.files[part.filename] = part; 46 | if (part.name) stream.fields[part.name] = part; 47 | }); 48 | stream.addListener("body", function (chunk) { 49 | stream.part.body = (stream.part.body || "") + chunk; 50 | }); 51 | stream.addListener("error", function (e) { p.emitError(e) 52 | if (callback) callback(e); 53 | }); 54 | stream.addListener("complete", function () { 55 | if (callback) callback(null, stream); 56 | }); 57 | }; 58 | 59 | // events: 60 | // "partBegin", "partEnd", "body", "complete" 61 | // everything emits on the Stream directly. 62 | // the stream's "parts" object is a nested collection of the header objects 63 | // check the stream's "part" member to know what it's currently chewin on. 64 | // this.part.parent refers to that part's containing message (which may be 65 | // the stream itself) 66 | // child messages inherit their parent's headers 67 | // A non-multipart message looks just like a multipart message with a 68 | // single part. 69 | function Stream (message) { 70 | var isMultiPart = multipartHeaders(message, this), 71 | w = isMultiPart ? writer(this) : simpleWriter(this), 72 | e = ender(this); 73 | if (message.addListener) { 74 | message.addListener("data", w); 75 | message.addListener("end", e); 76 | if (message.pause && message.resume) { 77 | this._pause = message; 78 | } 79 | } else if (message.body) { 80 | var self = this; 81 | if (message.body.pause && message.body.resume) { 82 | this._pause = message.body; 83 | } 84 | if (message.body.addListener) { 85 | message.body.addListener("data", w); 86 | message.body.addListener("end", e); 87 | } if (message.body.forEach) { 88 | var p = message.body.forEach(w); 89 | if (p && p.addCallback) p.addCallback(e); 90 | else e(); 91 | } else { 92 | // just write a string. 93 | w(message.body); 94 | e(); 95 | } 96 | } 97 | }; 98 | Stream.prototype = { 99 | __proto__ : events.EventEmitter.prototype, 100 | error : function (ex) { 101 | this._error = ex; 102 | this.emit("error", ex); 103 | }, 104 | pause : function () { 105 | if (this._pause) return this._pause.pause(); 106 | throw new Error("Unsupported"); 107 | }, 108 | resume : function () { 109 | if (this._pause) return this._pause.resume(); 110 | throw new Error("Unsupported"); 111 | } 112 | }; 113 | 114 | // check the headers of the message. If it wants to be multipart, 115 | // then we'll be returning true. Regardless, if supplied, then 116 | // stream will get a headers object that inherits from message's. 117 | // If no stream object is supplied, then this function just inspects 118 | // the message's headers for multipartness, and modifies the message 119 | // directly. This divergence is so that we can avoid modifying 120 | // the original message when we want a wrapper, but still have the 121 | // info available when it's one of our own objects. 122 | function multipartHeaders (message, stream) { 123 | var field, val, contentType, contentDisposition = ""; 124 | if (stream) stream.headers = {}; 125 | for (var h in message.headers) if (message.headers.hasOwnProperty(h)) { 126 | val = message.headers[h]; 127 | field = h.toLowerCase(); 128 | if (stream) stream.headers[field] = val; 129 | if (field === "content-type") { 130 | contentType = val; 131 | } else if (field === "content-disposition") { 132 | contentDisposition = val; 133 | } 134 | } 135 | 136 | if (!Array.isArray(contentDisposition)) { 137 | contentDisposition = contentDisposition.split(","); 138 | } 139 | contentDisposition = contentDisposition[contentDisposition.length - 1]; 140 | 141 | var mutate = (stream || message); 142 | 143 | // Name and filename can come along with either content-disposition 144 | // or content-type. Well-behaved agents use CD rather than CT, 145 | // but sadly not all agents are well-behaved. 146 | [contentDisposition, contentType].forEach(function (h) { 147 | if (!h) return; 148 | var cd = h.split(/; */); 149 | cd.shift(); 150 | for (var i = 0, l = cd.length; i < l; i ++) { 151 | var bit = cd[i].split("="), 152 | name = bit.shift(), 153 | val = stripQuotes(bit.join("=")); 154 | if (name === "filename" || name === "name") { 155 | mutate[name] = val; 156 | } 157 | } 158 | }); 159 | 160 | if (!contentType) { 161 | return false; 162 | } 163 | 164 | // legacy 165 | // TODO: Update this when/if jsgi-style headers are supported. 166 | // this will keep working, but is less efficient than it could be. 167 | if (!Array.isArray(contentType)) { 168 | contentType = contentType.split(","); 169 | } 170 | contentType = contentType[contentType.length-1]; 171 | 172 | // make sure it's actually multipart. 173 | var mpType = multipartExpression.exec(contentType); 174 | if (!mpType) { 175 | return false; 176 | } 177 | 178 | // make sure we have a boundary. 179 | var boundary = boundaryExpression.exec(contentType); 180 | if (!boundary) { 181 | return false; 182 | } 183 | 184 | mutate.type = mpType[1]; 185 | mutate.boundary = "--" + boundary[1]; 186 | mutate.isMultiPart = true; 187 | 188 | return true; 189 | }; 190 | function simpleWriter (stream) { 191 | stream.part = stream; 192 | stream.type = false; 193 | var started = false; 194 | return function (chunk) { 195 | if (!started) { 196 | stream.emit("partBegin", stream); 197 | started = true; 198 | } 199 | stream.emit("body", chunk); 200 | }; 201 | } 202 | function writer (stream) { 203 | var buffer = "", 204 | state = S_NEW_PART, 205 | part = stream.part = stream; 206 | stream.parts = []; 207 | stream.parent = stream; 208 | return function (chunk) { 209 | if (stream._error) return; 210 | // write to the buffer, and then process the buffer. 211 | buffer += chunk; 212 | while (buffer.length > 0) { 213 | switch (state) { 214 | case S_NEW_PART: 215 | // part is a multipart message. 216 | // we're either going to start reading a new part, or we're going to 217 | // end the current part, depending on whether the boundary has -- at 218 | // the end. either way, we expect --boundary right away. 219 | var boundary = part.boundary, 220 | len = boundary.length, 221 | offset = buffer.indexOf(boundary); 222 | if (offset === -1) { 223 | if (buffer.length > MAX_BUFFER_LENGTH) { 224 | return stream.error(new Error( 225 | "Malformed: boundary not found at start of message")); 226 | } 227 | // keep waiting for it. 228 | return; 229 | } 230 | if (offset > 0) { 231 | return stream.error(Error("Malformed: data before the boundary")); 232 | } 233 | if (buffer.length < (len + 2)) { 234 | // we'll need to see either -- or CRLF after the boundary. 235 | // get it on the next pass. 236 | return; 237 | } 238 | if (buffer.substr(len, 2) === "--") { 239 | // this message is done. 240 | // chomp off the boundary and crlf and move up 241 | if (part !== stream) { 242 | // wait to see the crlf, unless this is the top-level message. 243 | if (buffer.length < (len + 4)) { 244 | return; 245 | } 246 | if (buffer.substr(len+2, 2) !== CRLF) { 247 | return stream.error(new Error( 248 | "Malformed: CRLF not found after boundary")); 249 | } 250 | } 251 | buffer = buffer.substr(len + 4); 252 | stream.emit("partEnd", part); 253 | stream.part = part = part.parent; 254 | state = S_NEW_PART; 255 | continue; 256 | } 257 | if (part !== stream) { 258 | // wait to see the crlf, unless this is the top-level message. 259 | if (buffer.length < (len + 2)) { 260 | return; 261 | } 262 | if (buffer.substr(len, 2) !== CRLF) { 263 | return stream.error(new Error( 264 | "Malformed: CRLF not found after boundary")); 265 | } 266 | } 267 | // walk past the crlf 268 | buffer = buffer.substr(len + 2); 269 | // mint a new child part, and start parsing headers. 270 | stream.part = part = startPart(part); 271 | state = S_HEADER; 272 | continue; 273 | case S_HEADER: 274 | // just grab everything to the double crlf. 275 | var headerEnd = buffer.indexOf(CRLF+CRLF); 276 | if (headerEnd === -1) { 277 | if (buffer.length > MAX_BUFFER_LENGTH) { 278 | return stream.error(new Error( 279 | "Malformed: header unreasonably long.")); 280 | } 281 | return; 282 | } 283 | var headerString = buffer.substr(0, headerEnd); 284 | // chomp off the header and the empty line. 285 | buffer = buffer.substr(headerEnd + 4); 286 | try { 287 | parseHeaderString(part.headers, headerString); 288 | } catch (ex) { 289 | return stream.error(ex); 290 | } 291 | multipartHeaders(part); 292 | 293 | // let the world know 294 | stream.emit("partBegin", part); 295 | 296 | if (part.isMultiPart) { 297 | // it has a boundary and we're ready to grab parts out. 298 | state = S_NEW_PART; 299 | } else { 300 | // it doesn't have a boundary, and is about to 301 | // start spitting out body bits. 302 | state = S_BODY; 303 | } 304 | continue; 305 | case S_BODY: 306 | // look for part.parent.boundary 307 | var boundary = part.parent.boundary, 308 | offset = buffer.indexOf(boundary); 309 | if (offset === -1) { 310 | // emit and wait for more data, but be careful, because 311 | // we might only have half of the boundary so far. 312 | // make sure to leave behind the boundary's length, so that we'll 313 | // definitely get it next time if it's on its way. 314 | var emittable = buffer.length - boundary.length; 315 | if (buffer.substr(-1) === CR) emittable -= 1; 316 | if (buffer.substr(-2) === CRLF) emittable -= 2; 317 | 318 | if (emittable > 0) { 319 | stream.emit("body", buffer.substr(0, emittable)); 320 | buffer = buffer.substr(emittable); 321 | } 322 | // haven't seen the boundary, so wait for more bytes. 323 | return; 324 | } 325 | if (offset > 0) { 326 | var emit = buffer.substr(0, offset); 327 | if (emit.substr(-2) === CRLF) emit = emit.substr(0, emit.length-2); 328 | if (emit) stream.emit("body", emit); 329 | buffer = buffer.substr(offset); 330 | } 331 | 332 | // let em know we're done. 333 | stream.emit("partEnd", part); 334 | 335 | // now buffer starts with boundary. 336 | if (buffer.substr(boundary.length, 2) === "--") { 337 | // message end. 338 | // parent ends, look for a new part in the grandparent. 339 | stream.part = part = part.parent; 340 | stream.emit("partEnd", part); 341 | stream.part = part = part.parent; 342 | state = S_NEW_PART; 343 | buffer = buffer.substr(boundary.length + 4); 344 | } else { 345 | // another part coming for the parent message. 346 | stream.part = part = part.parent; 347 | state = S_NEW_PART; 348 | } 349 | continue; 350 | } 351 | } 352 | }; 353 | }; 354 | 355 | function parseHeaderString (headers, string) { 356 | var lines = string.split(CRLF), 357 | field, value, line; 358 | for (var i = 0, l = lines.length; i < l; i ++) { 359 | line = lines[i]; 360 | if (line.match(wrapExpression)) { 361 | if (!field) { 362 | throw new Error("Malformed. First header starts with whitespace."); 363 | } 364 | value += line.replace(wrapExpression, " "); 365 | continue; 366 | } else if (field) { 367 | // now that we know it's not wrapping, put it on the headers obj. 368 | affixHeader(headers, field, value); 369 | } 370 | line = line.split(":"); 371 | field = line.shift().toLowerCase(); 372 | if (!field) { 373 | throw new Error("Malformed: improper field name."); 374 | } 375 | value = line.join(":").replace(/^\s+/, ""); 376 | } 377 | // now affix the last field. 378 | affixHeader(headers, field, value); 379 | }; 380 | 381 | function affixHeader (headers, field, value) { 382 | if (!headers.hasOwnProperty(field)) { 383 | headers[field] = value; 384 | } else if (Array.isArray(headers[field])) { 385 | headers[field].push(value); 386 | } else { 387 | headers[field] = [headers[field], value]; 388 | } 389 | }; 390 | 391 | function startPart (parent) { 392 | var part = { 393 | headers : {}, 394 | parent : parent 395 | }; 396 | parent.parts = parent.parts || []; 397 | parent.parts.push(part); 398 | return part; 399 | }; 400 | 401 | function ender (stream) { return function () { 402 | if (stream._error) return; 403 | if (!stream.isMultiPart) stream.emit("partEnd", stream); 404 | stream.emit("complete"); 405 | }}; 406 | 407 | function stripslashes(str) { 408 | // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) 409 | // + improved by: Ates Goral (http://magnetiq.com) 410 | // + fixed by: Mick@el 411 | // + improved by: marrtins 412 | // + bugfixed by: Onno Marsman 413 | // + improved by: rezna 414 | // + input by: Rick Waldron 415 | // + reimplemented by: Brett Zamir (http://brett-zamir.me) 416 | // * example 1: stripslashes("Kevin\'s code"); 417 | // * returns 1: "Kevin's code" 418 | // * example 2: stripslashes("Kevin\\\'s code"); 419 | // * returns 2: "Kevin\'s code" 420 | return (str+"").replace(/\\(.?)/g, function (s, n1) { 421 | switch(n1) { 422 | case "\\": 423 | return "\\"; 424 | case "0": 425 | return "\0"; 426 | case "": 427 | return ""; 428 | default: 429 | return n1; 430 | } 431 | }); 432 | }; 433 | function stripQuotes (str) { 434 | str = stripslashes(str); 435 | return str.substr(1, str.length - 2); 436 | }; 437 | -------------------------------------------------------------------------------- /test/fixture.js: -------------------------------------------------------------------------------- 1 | 2 | // each message contains a header, body, and a list of the parts that are 3 | // expected. Any properties in the expected objects will be matched against 4 | // the parsed parts. 5 | 6 | var messages = exports.messages = []; 7 | 8 | var bad = exports.badMessages = []; 9 | 10 | var longString = ""; 11 | for (var i = 0; i < (1024*1024); i ++) longString += Math.random(); 12 | 13 | // content before the first boundary 14 | bad.push({ 15 | headers : { "Content-Type":"multipart/mixed; boundary=boundary" }, 16 | body : "blerg\r\n--boundary\r\nblarggghhh" 17 | }); 18 | // no boundary 19 | bad.push({ 20 | headers : { "Content-Type":"multipart/mixed; boundary=boundary" }, 21 | body : longString 22 | }); 23 | // header unreasonably long. 24 | bad.push({ 25 | headers : { "Content-Type":"multipart/mixed; boundary=boundary" }, 26 | body : "--boundary\r\ncontent-type: "+longString+"\r\n"+longString 27 | }); 28 | // CRLF not found after boundary 29 | bad.push({ 30 | headers : { "Content-Type":"multipart/mixed; boundary=boundary" }, 31 | body : "--boundary"+longString 32 | }); 33 | // start first header with whitespace. 34 | bad.push({ 35 | headers : { "Content-Type":"multipart/mixed; boundary=boundary" }, 36 | body : "--boundary\r\n fail: blahrg\r\n\r\n"+longString 37 | }); 38 | 39 | // The comments in this first test case tell a story about what the parser is 40 | // doing at each step. If you mean to touch the code, it's best to read through 41 | // this test case first so that you know what you're getting into. 42 | messages.push({ 43 | expect : [ 44 | { type : "mixed", boundary : "--inner1" }, 45 | { type : "mixed", boundary : "--inner2" }, 46 | { filename : "hello.txt" }, 47 | { filename : "hello2.txt" }, 48 | { type : "mixed", boundary : "--inner3" }, 49 | { filename : "hello3.txt" }, 50 | { filename : "hello4.txt" }, 51 | { filename : "hello-outer.txt" } 52 | ], 53 | boundary : "outer", 54 | headers : { 55 | "Content-Type":"multipart/mixed; boundary=outer" 56 | }, body : [ 57 | // s=new part, part = stream, part.boundary=--outer 58 | "--outer",// chomp to here, because it matches the boundary. 59 | // mint a new part without a boundary, parent=old part, set state to header 60 | "Content-Type: multipart/mixed; boundary=inner1",// move along 61 | "", // found the end of the header. chomp to here, parse the headers onto 62 | // the current part. Once we do that, we know that the current part 63 | // is multipart, and has a boundary of --inner1 64 | // s=new part, part = --inner1 65 | "--inner1", // chomp to here. 66 | // mint a new part without a boundary, parent=--inner1, s=header 67 | "Content-type: multipart/mixed; boundary=inner2", // move along 68 | "", // again, found the end of the header. chomp to here, parse headers 69 | // onto the newly minted part. Then find out that this part has a 70 | // boundary of --inner2. 71 | // s=new part, part=--inner2 72 | "--inner2", // chomp to here. 73 | // mint a new part without a boundary, parent=--inner2 74 | "Content-type: text/plain", // move along 75 | "content-disposition: inline; filename=\"hello.txt\"", // move along 76 | "", // chomp to here. found end of header. parse headers 77 | // then we know that it's not multipart, so we'll be looking for 78 | // the parent's boundary and emitting body bits. 79 | // also, we can set part.filename to "hello.txt" 80 | // s=body, part=hello.txt 81 | "hello, world", // chomp, emit the body, looking for parent-boundary 82 | "--inner2", // found parent.boundary. leave it on the buffer, and 83 | // set part=part.parent, s=new part 84 | // on the next pass, we'll chomp to here, mint a new part 85 | // without a boundary, set s=header 86 | "content-type: text/plain", // header... 87 | "content-disposition: inline; filename=\"hello2.txt\"", // header... 88 | "", // chomp to here, parse header onto the current part. 89 | // since it's not multipart, we're looking for parent.boundary 90 | "hello to the world", // body, looking for parent.boundary=--inner 91 | "--inner2--", // found parent.boundary. In this case, we have the 92 | // trailing --, indicating that no more parts are coming 93 | // for this set. We need to back up to the grandparent, 94 | // and then do the new part bit. Chomp off the --inner2-- 95 | // s=new part, part=part.parent.parent=--inner1 96 | "--inner1", // chomp to here, because this is part.boundary 97 | // mint a new part without a boundary 98 | // s=header, part = (new) 99 | "Content-type: multipart/mixed; boundary=inner3", // header... 100 | "", // chomp to here, parse headers onto the new part. 101 | // it's multipart, so set the boundary=--inner3, 102 | // s=new part, part = --inner3 103 | "--inner3", // chomp to here. mint a new part with no boundary, parse headers 104 | "Content-type: text/plain", // header 105 | "content-disposition: inline; filename=\"hello3.txt\"", // header 106 | "", // end of header. parse headers onto part, whereupon we find that it is 107 | // not multipart, and has a filename of hello3.txt. 108 | // s=body, part=hello3.txt, looking for part.parent.boundary=--inner3 109 | "hello, free the world", // body... 110 | "--inner3", // found parent.boundary, and it's not the end. 111 | // s = new part, part = part.parent 112 | // next pass: 113 | // mint a new part without a boundary, s=header 114 | "content-type: text/plain", // header 115 | "content-disposition: inline; filename=\"hello4.txt\"", // header 116 | "", // chomp to here, parse headers on to boundaryless part. 117 | // s=body, part = hello4.txt 118 | "hello for the world", // body, looking for part.parent.boundary=--inner3 119 | "--inner3--", // found parent.boundary, and it's the end 120 | // chomp this off the buffer, part = part.parent.parent=--inner1 121 | // s = new part 122 | "--inner1", // chomp to here, because part.boundary = --inner1 123 | // mint a new boundariless part, s = header 124 | "Content-type: text/plain", // header... 125 | "content-disposition: inline; filename=\"hello-outer.txt\"", // header... 126 | "", // chomp to here, parse headers onto the current part. 127 | // has no boundary, so we're gonna go into body mode. 128 | // s = body, boundary = parent.boundary = --inner1, part = hello-outer.txt 129 | "hello, outer world", // body, looking for parent.boundary=--inner1 130 | "--inner1--", // found the parent.boundary, and it's the end. 131 | // chomp off the --inner1--, part = part.parent.parent, s = new part 132 | "--outer--" // we're looking for a new part, but found the ending. 133 | // chomp off the --outer--, part = part.parent, s = new part. 134 | ].join("\r\n") 135 | }); 136 | 137 | messages.push({ 138 | headers : { 139 | "Content-Type": "multipart/form-data; boundary=AaB03x", 140 | }, 141 | boundary : "AaB03x", 142 | body : [ 143 | "--AaB03x", 144 | "content-disposition: form-data; name=\"reply\"", 145 | "", 146 | "yes", 147 | "--AaB03x", 148 | "content-disposition: form-data; name=\"fileupload\"; filename=\"dj.jpg\"", 149 | "Content-Type: image/jpeg", 150 | "Content-Transfer-Encoding: base64", 151 | "", 152 | "/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg", 153 | "--AaB03x--", "" 154 | ].join("\r\n"), 155 | expect : [ 156 | { name : "reply" }, 157 | { name : "fileupload", filename : "dj.jpg" } 158 | ] 159 | }); 160 | 161 | // An actual email message sent from felixge to isaacs. 162 | // Addresses and signatures obscured, but the unicycle pic is preserved for posterity. 163 | messages.push({ 164 | headers: { 165 | // TODO: When node's parser supports header-wrapping, these should actually be wrapped, 166 | // because that's how they appear in real life. 167 | "Delivered-To":"isaacs...@gmail.com", 168 | "Received":"by 10.142.240.14 with SMTP id n14cs252101wfh; Wed, 3 Feb 2010 14:24:08 -0800 (PST)", 169 | "Received":"by 10.223.4.139 with SMTP id 11mr194455far.61.1265235847416; Wed, 03 Feb 2010 14:24:07 -0800 (PST)", 170 | "Return-Path":"", 171 | "Received":"from mail-fx0-f219.google.com (mail-fx0-f219.google.com [209.85.220.219]) by mx.google.com with ESMTP id d13si118373fka.17.2010.02.03.14.24.05; Wed, 03 Feb 2010 14:24:06 -0800 (PST)", 172 | "Received-SPF":"neutral (google.com: 209.85.220.219 is neither permitted nor denied by best guess record for domain of isaacs+caf_=isaacs...=gmail.com@izs.me) client-ip=209.85.220.219;", 173 | "Authentication-Results":"mx.google.com; spf=neutral (google.com: 209.85.220.219 is neither permitted nor denied by best guess record for domain of isaacs+caf_=isaacs...=gmail.com@izs.me) smtp.mail=isaacs+caf_=isaacs...=gmail.com@izs.me; dkim=pass (test mode) header.i=@gmail.com", 174 | "Received":"by mail-fx0-f219.google.com with SMTP id 19so626487fxm.25 for ; Wed, 03 Feb 2010 14:24:05 -0800 (PST)", 175 | "Received":"by 10.216.91.15 with SMTP id g15mr146196wef.24.1265235845694; Wed, 03 Feb 2010 14:24:05 -0800 (PST)", 176 | "X-Forwarded-To":"isaacs...@gmail.com", 177 | "X-Forwarded-For":"isaacs@izs.me isaacs...@gmail.com", 178 | "Delivered-To":"i@izs.me", 179 | "Received":"by 10.216.12.146 with SMTP id 18cs33122wez; Wed, 3 Feb 2010 14:24:00 -0800 (PST)", 180 | "Received":"by 10.213.97.28 with SMTP id j28mr2627124ebn.82.1265235838786; Wed, 03 Feb 2010 14:23:58 -0800 (PST)", 181 | "Return-Path":"", 182 | "Received":"from ey-out-2122.google.com (ey-out-2122.google.com [74.125.78.25]) by mx.google.com with ESMTP id 4si11869270ewy.8.2010.02.03.14.23.54; Wed, 03 Feb 2010 14:23:57 -0800 (PST)", 183 | "Received-SPF":"pass (google.com: domain of hai...@gmail.com designates 74.125.78.25 as permitted sender) client-ip=74.125.78.25;", 184 | "Received":"by ey-out-2122.google.com with SMTP id d26so431288eyd.17 for ; Wed, 03 Feb 2010 14:23:54 -0800 (PST)", 185 | "DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:mime-version:sender:received:from:date :x-google-sender-auth:message-id:subject:to:content-type; bh=JXfvYIRzerOieADuqMPGlnlFbIGyPuTssL5icEtSLWw=; b=QDzgOCEbYk8cEdBe+HYx/MJrTWmZyx4qENADOcnnn9Xuk1Q6e/c7b3UsvLf/sMoYrG z96RQhUVOKi9IAzkQhNnOCWDuF1KNxtFnCGhEXMARXBM3qjXe3QmAqXNhJrI0E9bMeme d5aX5GMrz5mIark462cDsTmrFgaYE6JtwASho=", 186 | "DomainKey-Signature":"a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=mime-version:sender:from:date:x-google-sender-auth:message-id :subject:to:content-type; b=VYkN8OeNNJyxAseCAPH8u2aBfGmZaFesmieoWEDymQ1DsWg/aXbaWt4JGQlefIfmMK hOXd4EN2/iEix10aWDzKpuUV9gU9Wykm93t3pxD7BCz50Kagwp7NVyDJQLK0H5JSNEU/ IVRp90kKNBsb3v76vPsQydi9awLh/jYrFQVMY=", 187 | "MIME-Version":"1.0", 188 | "Sender":"hai...@gmail.com", 189 | "Received":"by 10.216.86.201 with SMTP id w51mr154937wee.8.1265235834101; Wed, 03 Feb 2010 14:23:54 -0800 (PST)", 190 | "From":"Felix Geisendoerfer ", 191 | "Date":"Wed, 3 Feb 2010 23:23:34 +0100", 192 | "X-Google-Sender-Auth":"0217977a92fcbed0", 193 | "Message-ID":"<56dbc1211002031423g750ba93fs4a2f22ce22431590@mail.gmail.com>", 194 | "Subject":"Me on my unicycle", 195 | "To":"i@izs.me", 196 | "Content-Type":"multipart/mixed; boundary=0016e6d99d0572dfaf047eb9ac2e", 197 | }, 198 | expect : [ 199 | { type : "alternative", boundary : "--0016e6d99d0572dfa5047eb9ac2c" }, 200 | {}, // the first bit, text/plain 201 | {}, // the second bit, text/html 202 | { name : "unicycle.jpg", filename : "unicycle.jpg" } 203 | ], 204 | boundary : "0016e6d99d0572dfaf047eb9ac2e", 205 | body : [ 206 | "--0016e6d99d0572dfaf047eb9ac2e", // beginpart->header 207 | "Content-Type: multipart/alternative; boundary=0016e6d99d0572dfa5047eb9ac2c", // headers. isMultipart 208 | "", // bodybegin->beginpart 209 | "--0016e6d99d0572dfa5047eb9ac2c",//header 210 | "Content-Type: text/plain; charset=ISO-8859-1", 211 | "",//bodybegin->body 212 | "*This was 4 years ago, I miss riding my unicycle !*", 213 | "", 214 | "-- fg", 215 | "", 216 | "--0016e6d99d0572dfa5047eb9ac2c", //partend->partbegin 217 | "Content-Type: text/html; charset=ISO-8859-1", 218 | "", 219 | "This was 4 years ago, I miss riding my unicycle !

-- fg

", 220 | "", 221 | "", 222 | "
", 223 | "", 224 | "--0016e6d99d0572dfa5047eb9ac2c--",//partend, walk up tree-->partbegin 225 | "--0016e6d99d0572dfaf047eb9ac2e",//beginpart->header 226 | "Content-Type: image/jpeg; name=\"unicycle.jpg\"",//header 227 | "Content-Disposition: attachment; filename=\"unicycle.jpg\"",//header 228 | "Content-Transfer-Encoding: base64",//header 229 | "X-Attachment-Id: f_g58opqah0",//header 230 | "",//bodybegin->body 231 | "/9j/4AAQSkZJRgABAQEASABIAAD/4gUoSUNDX1BST0ZJTEUAAQEAAAUYYXBwbAIgAABzY25yUkdC",//bodybodybody 232 | "IFhZWiAH0wAHAAEAAAAAAABhY3NwQVBQTAAAAABhcHBsAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAA",//bodybodybody 233 | "AADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAty",//bodybodybody 234 | "WFlaAAABCAAAABRnWFlaAAABHAAAABRiWFlaAAABMAAAABR3dHB0AAABRAAAABRjaGFkAAABWAAA",//bodybodybody 235 | "ACxyVFJDAAABhAAAAA5nVFJDAAABhAAAAA5iVFJDAAABhAAAAA5kZXNjAAABlAAAAD1jcHJ0AAAE", 236 | "1AAAAEFkc2NtAAAB1AAAAv5YWVogAAAAAAAAdEsAAD4dAAADy1hZWiAAAAAAAABacwAArKYAABcm", 237 | "WFlaIAAAAAAAACgYAAAVVwAAuDNYWVogAAAAAAAA81IAAQAAAAEWz3NmMzIAAAAAAAEMQgAABd7/", 238 | "//MmAAAHkgAA/ZH///ui///9owAAA9wAAMBsY3VydgAAAAAAAAABAjMAAGRlc2MAAAAAAAAAE0Nh", 239 | "bWVyYSBSR0IgUHJvZmlsZQAAAAAAAAAAAAAAE0NhbWVyYSBSR0IgUHJvZmlsZQAAAABtbHVjAAAA", 240 | "AAAAAA8AAAAMZW5VUwAAACQAAAKeZXNFUwAAACwAAAFMZGFESwAAADQAAAHaZGVERQAAACwAAAGY", 241 | "ZmlGSQAAACgAAADEZnJGVQAAADwAAALCaXRJVAAAACwAAAJybmxOTAAAACQAAAIObm9OTwAAACAA", 242 | "AAF4cHRCUgAAACgAAAJKc3ZTRQAAACoAAADsamFKUAAAABwAAAEWa29LUgAAABgAAAIyemhUVwAA", 243 | "ABoAAAEyemhDTgAAABYAAAHEAEsAYQBtAGUAcgBhAG4AIABSAEcAQgAtAHAAcgBvAGYAaQBpAGwA", 244 | "aQBSAEcAQgAtAHAAcgBvAGYAaQBsACAAZgD2AHIAIABLAGEAbQBlAHIAYTCrMOEw6QAgAFIARwBC", 245 | "ACAw1zDtMNUwoTCkMOtleE9NdvhqXwAgAFIARwBCACCCcl9pY8+P8ABQAGUAcgBmAGkAbAAgAFIA", 246 | "RwBCACAAcABhAHIAYQAgAEMA4QBtAGEAcgBhAFIARwBCAC0AawBhAG0AZQByAGEAcAByAG8AZgBp", 247 | "AGwAUgBHAEIALQBQAHIAbwBmAGkAbAAgAGYA/AByACAASwBhAG0AZQByAGEAc3b4ZzoAIABSAEcA", 248 | "QgAgY8+P8GWHTvYAUgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABzAGUAIAB0AGkAbAAgAEsAYQBt", 249 | "AGUAcgBhAFIARwBCAC0AcAByAG8AZgBpAGUAbAAgAEMAYQBtAGUAcgBhznS6VLd8ACAAUgBHAEIA", 250 | "INUEuFzTDMd8AFAAZQByAGYAaQBsACAAUgBHAEIAIABkAGUAIABDAOIAbQBlAHIAYQBQAHIAbwBm", 251 | "AGkAbABvACAAUgBHAEIAIABGAG8AdABvAGMAYQBtAGUAcgBhAEMAYQBtAGUAcgBhACAAUgBHAEIA", 252 | "IABQAHIAbwBmAGkAbABlAFAAcgBvAGYAaQBsACAAUgBWAEIAIABkAGUAIABsIBkAYQBwAHAAYQBy", 253 | "AGUAaQBsAC0AcABoAG8AdABvAAB0ZXh0AAAAAENvcHlyaWdodCAyMDAzIEFwcGxlIENvbXB1dGVy", 254 | "IEluYy4sIGFsbCByaWdodHMgcmVzZXJ2ZWQuAAAAAP/hAIxFeGlmAABNTQAqAAAACAAGAQYAAwAA", 255 | "AAEAAgAAARIAAwAAAAEAAQAAARoABQAAAAEAAABWARsABQAAAAEAAABeASgAAwAAAAEAAgAAh2kA", 256 | "BAAAAAEAAABmAAAAAAAAAEgAAAABAAAASAAAAAEAAqACAAQAAAABAAAAyKADAAQAAAABAAABWwAA", 257 | "AAD/2wBDAAICAgICAQICAgICAgIDAwYEAwMDAwcFBQQGCAcICAgHCAgJCg0LCQkMCggICw8LDA0O", 258 | "Dg4OCQsQEQ8OEQ0ODg7/2wBDAQICAgMDAwYEBAYOCQgJDg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4O", 259 | "Dg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg7/wAARCAFbAMgDASIAAhEBAxEB/8QAHwAAAQUBAQEB", 260 | "AQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1Fh", 261 | "ByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZ", 262 | "WmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXG", 263 | "x8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAEC", 264 | "AwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHB", 265 | "CSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0", 266 | "dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX", 267 | "2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD4KEuQBkZp3m+5PaskTDPUUhmweDVt", 268 | "kamt5+M89KkWf5uv4VifaDilFwS/PFK47tnQed6HB9KcJ+eTWIs/qTTvtB5OaQ7G35/uKd54PfrW", 269 | "H9oyev40Cfn/AOvSBI+r/gxtfwHqZIz/AKZge3yivXGBB6c+gryL4HMz/CzUG2LtOoEbsf7Ir2V0", 270 | "OR7mpluM8y1kL/wkN0B/ewePasRox5hwBW3rKg+KL3uPM/oKy227h0zUgZ72ykSE4yOvesu40xWT", 271 | "IUZPbFdIkYZiR0+lOeINKQp7daTVxp2PM77w8kis2wAkccVxl7ocsQY7N2PQV7ubdSpUjK+9ZVzp", 272 | "iOp46jpWUolxnY8ZsdU1PSm2A/aLccGN+ePY12HhnxLLpOqveeFNRk0C+kO6ewlG60uT/tJ6/wC0", 273 | "uDV7UfDqkMVTk9K4+90J0TdsII7jqKiSvozrw2InRlzU3Zn1v4U+MGk380On+K4B4X1V8Kksj5s5", 274 | "z/sS9if7rYP1r3CIq0aupVlIyrA5BHrX5q2+rX9lbta3ca6lYMMPFMM8enPWvW/BHje78M2sc9nr", 275 | "80GgkH/iVahmRF/65sTmP6ZI9qiztofVYHP4zXLWVn+B9uoVC59qtoQTivhg/tDalZ+OSZbiO40r", 276 | "zgfLBU7Bnnkdsdq9a8N/tBeErrWZrfUr+O3tmcC2lUZ2j/aOaV31R20s2w83a9j6WKLJGUYBlI5B", 277 | "HWuR1PwhbzRytZgBnJYxk8ZJycV01he2t/p0N1ZXEVzbSANHLGwZWHqCK0QcMDx1FNM2xODpYiNp", 278 | "K/mfKHizw9a2+mSx3U8VlKgAzI4QqeOuenNFdl8aNR8InxXbaHq9z9k1uSz+1W8ZiBW8TzSvl5Pc", 279 | "EE44zRXlYvmVTSCZ+Z5hhalKvKCV7HyJr3wM120R5/D19b6zb4yIZCIpvw/hb8xXjGqaXq+i3xtd", 280 | "X06802cH7txEUz9D0P4V3mmeKfFPhLwPpmp6dq920c8pBtpj5kRA9j0/CvUrH4waZqXhe2Xx34bg", 281 | "l0+4OwTRxiVCe5KNyPwNfQqfcHqfLnnYXrzTllyfSvqCf4W/DTxvC114L15NLu3XIgR96A+hjY7l", 282 | "/A15J4j+D3jnw0XmfTv7YsV5+0acTIAPdfvD8vxquZAlY4ETHHXNOEvufzqkVdZGRwyOpwykYIP0", 283 | "qQKSKbAtiXPOeaXzeepNQrGcdTUoj55zRYD7A+ArsPhFfZ5V9RbGfUKte3sCUDE14v8AAmJR8E7h", 284 | "skn+0ZMc+y17MwYwEjt2B61MtwPMNYwPE17tJx5v9KyfldguBnuRWnqR3eIrzP8Az0ql5QMxPB9h", 285 | "SAdAu4HBxjmggAsxzUmAsRx1zzk4qB7mzgTdLcQIvffIKtUqj2ixcyJY1DZY9utI8SmMYBJIxUUe", 286 | "pWG13S8tZFAywRwxx9BUsN7ZXty0FrdW0su3cEVxkj1q5YWskm4v7hc8e5G1skkA+X2INY9xpqnO", 287 | "Uziur8p1XaVOQM4xXOeK/EOk+EPB39ua9K1tYmZIU2pueR26BV6noT9BWDpS2sWpHHalpFlFaNdX", 288 | "J8iBOXbHavLfEV5pl5cm1toxE7AIuc4CjgZ/niu18Y61q2t/DbS9S8PeF/ENzpt3deWjXNhJAZHP", 289 | "CYDAZXPccV4VfW+o2OvzWGoRqlzE21wueG6MAfbpmud2TPTpUZqHM0Y3im7trK3NhaXLb/SFMAms", 290 | "XQmu7aG686WSVVQMCD80ZPfnr7ir9/Aq6rGr25t4hzJI5+Zj7VHdTR2kDm0C+UxXcW9O9aRemopJ", 291 | "3ufXH7MfxGutA8W6lpWvancN4entfMjEm5hFNuAGB2BGentX6IQXcF3YLc2siTQOuUdTkMK/D7Sf", 292 | "FFxpF9HNZvIFZt7KT+GB+Ffor8K/ixLafCDSl1ez+06UI2BuIZB51vg870P3l75HI9KwqRs9D3sp", 293 | "zFRtTqPToaX7RnhvTtX8RaVfzWtxLqP2J7a1mt03SRlpGPyjo3UnB9M8UVt+MviNpNn8WvCutW1w", 294 | "t/4XvrB4ZL6BN6W7mQ4Y56EcZBwcUV59blc78x8vnOJccZUSl1PgvXbiMfBLwo7N5YkdyN1S66q/", 295 | "8KP8KupU75HOQetZfirK/BPwWnXKuf8A69SeLd0fwI8CqrbCVc8HFetzd+5z23Letwtpfw+8J3lm", 296 | "z211IjOZImw2fqOa9LsviZ438JJokDXa69b3VqJXivvmZcns/wB4frXlvjSeSD4UeAsENusmJDc5", 297 | "6Vs+LLwW3iPwhbGJnaTS4OAeBlhRowZ7bfeJ/hr4tuPsXjPw9/Y2rOoP2lU6Z9JU5/76Fcnq/wAG", 298 | "bOWA3/hPX4NSs25WOZ1Yj2Drx+YFctqUgl/aStNMeFWiZolJJ7bc9KpaPNen4i+JhY3M2m/YkmlB", 299 | "t3K7tp4yOho52thK7My98J3ukzbNRtLmDB4Yr8p+jDg10nh74b6/4ltDPoukSX0QOC3nIv48kGtD", 300 | "SPiFrdx4NlvNYs7bUrSOZYXYLtkYn26H8q9++D11r9t4v1d5NGnsdNe2RYBND5eAWJOPzzVfWLbo", 301 | "qNKUnorieA9Kv/BfgIaTrlm+nXEl07gNgrjA/iHFejrIW06S4w5t0QsZQh2ADvn0rb8VXWn3emxp", 302 | "e3cUTIDwSO9VvDPi/TdN8Gz6DNqLX1nIrRiEnICHsPasPrcU3zM76eV4iotInjLX2n6j4h1CW2u4", 303 | "rm3WUl2hO8qK5zWfGWj6RDss4pLu9Y7I1bHze9e8XMOhHRr6DSNJsrQ3ELRmRIwG5BHWvz41y5ub", 304 | "fxHqcM0pW4juGhxnlQDg17/D9ehWlK8btHJm2X1MMo3lueuT+K2mQvfX1vFu5Krwq+1YUnifQZg5", 305 | "j1G3uCD8y+Xla+fvEeqM9x9nWdljUqCAeMYrEbVLey0IBMs8r/IPUDvX1UsaouyWiPEVFs9xutU0", 306 | "k3DtbTLDIckNE/yj+orNm8S38ECvC0N3JC29m/ibtkY7+teIx6lIBLM5HmPwgHpSx6xfW1ws0UzI", 307 | "2c46g/hXHLHpm0aLPZbb4sa/Z6gIvNMIBzGpkJCn0H+yfT61718JvF2n+K/EF5J4is7LVr6x2NYi", 308 | "8jEixFidzKrZAPQZ618VyzWms2mIglpfL8wQH5ZPXb6H2rtvhb4wfwz8TLSeaXy45h9nnLdsng/g", 309 | "QP1ryc0lUq4acYvU9fKJU6WLhKSP1G1bWN/hpsxfaLloyIkB2t07H+HA6Yr4w8f/AA9ujrPh620i", 310 | "IG7mjkee6dy2xjg/MfXmvTJfF81yghhlDyn7x3dB7VoJq9tLaGeaZXIXABPIPrXwEJyTufpWIhSq", 311 | "0uR7Hyx4i+F2saXZ/aTKNUfbhkTgp9PWvNbnR9Qi0xvtVncWxUhXMqEBueMV9P6z4hu/7ReGOI3c", 312 | "Lk7Cv3lP+FWNH8Nah4i04STaenkNJtkEhBX3PNehSqTbSPn8ZhcPBN7WPjeCykkvoY0WSV1kHKjl", 313 | "iTjA+gFfrZ8INZ8KaT+xZZ2Wqax4e0O/t7K4imgu1j86ZWV9sjjG4E5A5PbpXhsfwu8KWt/a3VvY", 314 | "eTNBKX4YkOTyc59/yr7h07RtF039mA3M1rah/wCxTO08sSb95ik2ncR1+bCk+4Pau1JnylapF2SP", 315 | "jnXrvwzffDDT7ZFEFxbLEguoxI0U+VGGI6EjkYx2A6UVpeItL1jTPhu062UR0i7srcy3IfckEgAC", 316 | "q+T8rMCDgdyOe1FeLBUX/EvfyPEqUuaTcr3PjrxiCvwn8Epzxbsaf41GPgr4AXp/o7mjxoGPw78E", 317 | "xqGLfZTwB9K3PFWg6hqXwq8DxWsQ3RWZLhjg817D/U9lQk9kYPxAXb8O/h8g/wCgcT/Ktbxum/4p", 318 | "+DYh2022GP8AgQrT8ceEtdvfCvgmK1sxP9n04LKFcfKeK6XxH4H1zUPjB4auI44UghsbZWLN3U5I", 319 | "qU9fmX7Gp2Mm4TzP2wbcdds6A/glbPw/8K6nr3xD8aC3t5EhmimiWZl4yXI49a9Etvh6sPxzl8S3", 320 | "85aNZA6IOFHy45Ndbf8AjbSvCunS2+ixRm6ZjuZBgZNYVK0Yo9LCZTOes9EXPDXw28NeCPAJh1p1", 321 | "upmlWdhJgncB6VW8QfFFIFe307ZGqjaCp5wOK8j1fxTrWus8ssj+UTyQeBTtN8GaxqOqi3kRo3Kh", 322 | "zu7A1xSdWoz3VUw9COljP1bxNqWp3DNJPK2T611fhaO7aEXJE8qr/dUnNdW/wys9M0+KW7cyylCc", 323 | "GvUfhpp1pH4HuFUwSeZJIPnQHaAePx7V00cGpbnnV87in7up5o+t65LKbWws7jzAMBQmDXy34y+H", 324 | "PxN1r4o6xf6Z4c1Ca3ml8wSAAAkgZNfpPb6Wkt0kl1DEqrwiBQf/AK44960YreOKafynlAwSzM5C", 325 | "4Hs2f0r0sFD6vNyj1PHx2OeJSUlsfjprvw58caPFJN4i0i60iBvmea6IHA9K4J41kvGcvuAG1MdA", 326 | "K+pv2jPHcvjX4nS6Rp1213oumDyV8rgOwOWyewz+Jx2r5wSzdDvZQABwqjCivootygm9zyYu5nOo", 327 | "igjJxuI6mq6xRyT5dmkP14rV1C0wTM2WjC/LUVlaiSVQFLMzhI1HVmPAAo5dSys9lIhDwMW/nUEs", 328 | "rtKJCfLuF4bPG/8A+vXpT+FdWt9JFw+niSRWPmQCfDAe2OM+1cxqdmbjR5L7TVzHD8tzbTLmWI9y", 329 | "aydSL2ZWpLpnxM1vSb21G77bDEQHSRcsV7jfnj8a+gY/iToF34UXU4tXt7KHyvngkwZA3cbep/Cv", 330 | "ktbZ538yZmWIfwgAZprIn2jzdigD5YxXlVcvpzldKx6tDNa1OLTdz6Ktvixo7a1BINJupySFeSVg", 331 | "u0Z6qP8AGvtDQoFtvC1pGoDbkEm5RgHdz/WvzK8K6Y+seOdJ0+PJkurtEUAdcsBn6Cv1ItY1gtY4", 332 | "EJMcSBF+gGB/Koq4anSa5dzGvj61aHLN6BIAwI6k1uT/ABLceErvwzq1xYDSodPaGNJFOZmKYSPP", 333 | "A6nJ9wtYsgPlsyk4r5x8Y3Ev9u3f2mdLSMx7raNHDNIwY4OOeMgHbke9ebjE2lY8jFTcbWPcvE/j", 334 | "PQ5/2XtZtITDHcxyW8NxaCURGYArtbDDLYxyQcg5PTiivkwy6ne6LLbnTr+S6d1kDTxHymzuQ7e4", 335 | "Ocf5FFec3OKSTOedeoupY1uxn1Cx8A2sB2P9k3E46AYzXtt1/Z1poGlRSWysgtwsbOcc968W13Uv", 336 | "7H/4Qi5xlEssOPbIrY8ba1JH4S0m7LNi4TdEnTA9a9apfp3Psssq0oU5Oe56VFryp5ccttHIiphM", 337 | "N0FbE3i3zoEdbeIXMOPKLHrXiXiPVzol14ciTP8ApdjHI2PU8Vp6leSWvxgstHTPlt5BOP8AbrPl", 338 | "Z0vM6O6O8uvFupa3q66Uz+TKxJ2px26VxNhINQsPE0knzPZWzMD77sVZ0wFf2kbqPJ2RSSAD221p", 339 | "fDbRJPEHiTxDpEYJN7IsTY7KZOT+WaSpRvc8uvmVWSsnoaeraBN4f+BHhWeZdmoatNFcS88qjv8A", 340 | "KPyAr6JKCP4igIgUDT4gQB/tNXjvxS1WLVfEptbQL9gsNXgsrcL02xALxXt8iqPiLc5A40+H/wBC", 341 | "arlboefzylq2V/FMmLWLnHyGqvw78T6MPDLWn2kxzpcyB8xk87iCM4xiovGshFjAVPBQ968i8J3H", 342 | "2e0vlMsaRyXTscg55JziinUcXczaPr8ajpzWPmRahaXGV+dzIAx5xwK8q+Ml3qY+B19a6LPJFc3k", 343 | "sdtLICS8EZPzuD67QQPrXIWF3GWC+bI6ocEA5yPXHY/WvGfiR48uri6vfDFjqF39kik/0uVFzhv7", 344 | "ikdPevUwH76slbTqZ1XaJ4PrNppekxm3jKTSrnEKNkA+rHua4GVJbi73zgrGOQicCuhuZD5sgg0k", 345 | "sc/eklJP41gXd3copEtlHEP9h+a+jqzjcxhF2KutBZNIgaMGOGOQB89ye/4U7wvbJffELSx5iJbW", 346 | "7+cdxwDs5/EmqU99C+j3MILea4GFbjkVzkdy0UTRyIUlXhfp7etcVaSeiNUj6N8ReIrbTraa7jC3", 347 | "UXnIrNFIPkB6mvGtY1eJvFmp3OhzXUFpeIBP5gwXP8X0Brl5JHkSGUO7RbMKCeAc+lXoYw5C5AUc", 348 | "t71jClrqXsRkM6BQvUcL7VTlieW+8uNN0cY27jwue5Jq5e3aJKsMGHkPU/57UkUbvtVsgd81Vk9g", 349 | "Z7t8AfBuseIvipNqWjWC6l/YsIuZhI+wSOx2ooOMDqxA/wBmvsptUfTdQNvren32jTZwxuYj5Z/4", 350 | "GOP5Vt/smeCk0D9lr+3biEC+167a4CkYLQJ8kfPpkO3419LyaZZ6pE/2i1injP8AG0YIz+NeVine", 351 | "ehasj5kWeGe28yGaOWJl+V0cEGvj3WPEcemeN9RtTZrcQLqc+2VgWWPe5BDDtzlsd8A1+gvjT4c6", 352 | "BY6BqevwmTw+trGZpp7PKgDjLFBw+D1GMntXxx8UvA3h/wAGX9rqljqsmpX9w0dzm8j8mO7mZSZE", 353 | "ii+8yA/xnGCcV5+Io88dTlxUVKxnQ6/pmoB5P7UjntPsxgzDYANsVwwVxyOx+oNFef6RBa3Ph7VN", 354 | "ft54jdyztH9m88xRx5KqFOAchmICkkdD1ycFeTKjFO3M/wCvkedJSi7Ib4940fwquB/yDxUvj8t/", 355 | "wjPguMH5f7MGR+VM8fDNp4YBGcaeOv1qT4gYOjeDQRn/AIla9Pwr2d7ep7qJviEAfEPg1PTTYf5i", 356 | "um1ePd+05Z552i1H6VznxAXHjbwinYWEH/oQrqr8bv2n4+vDW/b/AGaS3Q+qNbS13ftFam47NNz/", 357 | "AMBrofhbqD6FqviDXdjbYFYq+ON3zf41i6Sv/F+dWcDgGbqOnFeg3eixaD+yjaeZ8mp6sZL2QdCI", 358 | "ywVP0FTqlcls87glkvPDlpdSEtJPrwkYnuSRX1bdHHxGvBzgWEHH/Anr5U0uPPhLRATydZT+Yr6n", 359 | "vTj4l3ozyLGD/wBCepmrIqJj+L/nsoFHJ2GvLfCXhvW7nT7meDRb6VTcyNFMkOc5Y+pxXqPik/uo", 360 | "R0JQ1t/DZ7hvAqyKyvCssmcS4Jwx456HNFKCk3ciTszyrUJb7ToZLqexuoLtV2mOWPZ5hHAO4jH1", 361 | "r501s69JNM9xq2m6PbFiVtrOMMck5JLEZJ96+ivjd421+0vIdFewsjbLbi4WaVmLhjkY2qMcdj71", 362 | "8OeINY1q5uWaKKBmztUESMSPX7or6zK6EMPQ5nuzkqNznZdBdQht/OkabXL24bJ3DzQoP4CuLZXS", 363 | "5eRAzJngBy1VbuTWkVzdvBbjtyqE/nzWILy9SUGEoxHfdnP5VVSqpPY1jFmzMVExPlttx/d6VSmQ", 364 | "PZyrtIliAkQ4rPmuHmy0sL+Z1JjlKkfSoIrq4W2dYLrdcsQCbj5WVfQdia55TRoi0rlNNmQHOJsJ", 365 | "7ZGakjD3E8dqs6pLjO05+YntXUeA9J0rWfiHaW3iFpLXQ4Y5LrUJPM2kQxJlirD+InAAHrVq20Kw", 366 | "ufFFxd6JYz28LzlrSKaVpGSPPG8nqccmuOvi1T0Z04fDSqySR02nfDayudHS6e+ube6dMspAdc/z", 367 | "r6Z8Mfsa6lrPhTTdRufGkGlz3EIlmtG0tnaFW5X5hJySMZ44zXHfDrRJbnxbYWd9IblFcSTk8AKO", 368 | "cD+VfX1hc3OmXJkivtQjKJhG8/BAJ7EdfpXkUMfVvdvQ7syoUqTjGK1PfPDulW/h3wJovh2xISz0", 369 | "+zjtVRcA4RQu4keuP1rbT7RHHFGtypOeCy/Nn+uK8KtfFWqq6yzTLdAEqyyxgE++Rgk11sPj+0d0", 370 | "+1Wc8DMoLHIYD0Yd/wAKv28Zas8qxtfEM3+o/AfxXp1jZR6jfT2LxWsYynmyEgAMR0BPOew5r5D+", 371 | "JHgTXrvwHpVn4W8N/aHsImiGmCz3HzJ4v3032h3DMFcAKh44zgdK+zLbxLpGrQrBBdoJXXb5LLtL", 372 | "H06c042iibO3v1xWiaZDgr3Pyn0z4PfFa3udRtz4Q1I3V3pcsJhdlhQ5cASOdxVyATjnIzmiv1Rk", 373 | "hxfLhWP7sjpnvRScENxR+UHj8fuvDg5408dKf4/z9k8ILzxpifzFVfiDcFP+EdzbXM7HTQVEKgDP", 374 | "uSeKZ8SLfWJovBrWU1nbj+yo/OEyljnI6YoUHp6lOSubXxABb4h+Flx0sbfr9RXUXKE/tQMcE4eD", 375 | "/wBBriviHbavJ8VvCjWl1ZxwLZWwlV4ySx3DOK39TfVYv2wLfyrq0FhJPAHiaEl8bezZ/pUxg9Pm", 376 | "O53/AIZt1uv2gb2BzhJZZEJ9ASBXe/FDVor3xJrmm2ZH2DSrOGygAPAC4zXm/giDWLj4xeNNXee3", 377 | "Om6WznKQkMCzYUE5weh7Vz0PiM6j4Y8YapIhyJlYlm+9lqn2bshNnTaUpPhzQBgH/ibrx+Ir6evP", 378 | "+SqagvXFjB/6E9fJ+iavbyeCfDFzKwgaXWhHGoG7ccj34r6suW/4uzqXJGLG3/m9RWTRUTJ8VhQs", 379 | "Kjn5Ca2/htbNH4DMslrcuheUgqy4IJOcgkVgeLH/ANV3+Q9K2vhzds3g+CCaKJl3PtbH3huPBGev", 380 | "pWmCjedjOo7Js+afjVPfDWWm1pNa0GORS2larbQrLDLCeVVlYbWx7EMO+a+R59U8YadqBu4Nd0rx", 381 | "BBn/AI93QIWH+4VX/wAdJr9cdb0W/utImg1Lzxou0tMuowbkI+hGOnAAFfMfiL4I+EtW0m51GHTb", 382 | "KxmKmSNY4pLYlB/EwBGzPYEE+1fX18srpWpyvE8/D5lQ+3Hlf3nyWPGHhq/09F8aeDbqwwdgu4Yv", 383 | "Mjz9eCPpk1nanpfgFIVntNYu9OjflEuYnjz9N6ivbNR+HDWXh+y063uLix0i3uBes74kEs4OQGyP", 384 | "mUfQcYrx7Wvhh4t1bUJrq2ddYE828siOw3E9cHPNeHUy/E05+4mevTzKhUi+Zr5nmmot4Zt0do9a", 385 | "mu8dFhizn8elctHvubmFo7dpLOaXylw2X3dvpXslv8C/EzuEvpbe0fzdkyCE/u88qc4wc+3TpXpl", 386 | "n8G9A0bwReTS6k019ZCO8kkcApkHPlgDuV9K6KWEr3978Tnq4qlpy/geHeEPDmrav4gewYyra25H", 387 | "2lmXZn0U+vTNfQFrpVnpcIgtkVmxhnx1NS6DqXhe40W7fQJg87PvuYRC6NETwAcitSeyki8FTawj", 388 | "oyif7OqhssrEZzivlc1xD9o09kfd5DgFNU4Racp/1+B3HgnxB4O8PR3Mes3erW2qO4/ewWwlhVfQ", 389 | "87s/QV7JZeIPCmtTQxWvirQZZd4WFZZXtmJP8OJFXJ7Dmvjry1WELkuxUsxCnJP410+2zTUNKEcB", 390 | "SdbqN94lJBGVONn175715kcZJaWPvsb4e4KtH2inJP8AC6R9rTaHqtl89zpt0g6BxEZAQPpkVWVd", 391 | "+I2Z0l/hyOfYc817nE7TqY3U4UAMMkEt1qy9kl7+7njimXGSjqGz7ZPT617v1fqmfhrlZ2PH9EtT", 392 | "B4v02Q4XdcDAzyeDXspzkHmsf/hHNGTUkvYYGtrmFsgJIQDjsV6Vo78AHPFa0oOCdzOTuMlJEwOM", 393 | "5zRTXLGQEdMc0Vo1cR+P3xK+0mfwx5d5cQ/8SxdwQ43c1c+JQlkbwaBdXMZGkx7tj4zyOtUfiU5F", 394 | "z4Y9P7NXn8asfE2Xy7jweoB/5BMX860h9n1Bs0/iDFv+L/hYmadAtna4CyEA/N3rS1aGNv21LOXz", 395 | "JNy3UPAkOPuelZPj6Uj40+F0AyPslrkn6itPVAT+2pZMM83UQPP+xSjuvmB9H6Tp0HhT9nfWppUC", 396 | "al4s1u6uSWOSYYwVX6AnmvlfQrO3i+CHjSJgWWSaLeCx5+avob4j+J4dQ+N2j+HLEgWek+HXLIh4", 397 | "DsuT+PWvn/RyW+Cfi5iSSZ4v51EFe7fkK9kbmlWdqnwv+H8YiQIPFAZFx907hX21ctn4t6p1P+hW", 398 | "/X6yV8T6dID8P/h2oP3/ABKD/wCPrX2hcPn4v6vjPFnb/wA5KyxK1+bNE0ZniogvCMZGw5rZ+GN1", 399 | "Yv4SgWW3nKxzSFpWwFTDnJ9cCsLxW2DCR/cNXPh9d+T4Gn81XeAtJnYMfxnP6V05RDnxMY97HNjJ", 400 | "8tKT8j1/XkDajbaZaxXFxBDH5jtNwm4sSOTxisqdbZrQ2cUUOoys4aZYocoW92PWrdqH1fVIoY7t", 401 | "jbeWrhpLVnPsPQY96f4gk1k6THpmhi7h+f8A0i+3RRkD0Udvrj8K/R6snBtHylJJq5y2s6bpiBbj", 402 | "VLKwV8fdeMEAfjXiPjr4neHPDNnLDZQpc3CKdkFrHkr78cCvWrvwJpk9v52qnVdauuS8l3fSBPwG", 403 | "Rn8hXxh8Z/GGkeFTdaZo9pp8bsSkiwxkkn0Ld/xNeXXxMoK97HfSpKTsc/B498X/ABF8ZRaR4d0p", 404 | "W1CSTzGPmnYgxwW7ADqSa+ivD/ho+AvAUp1e7Or65cZaacDCIcdEH9TWZ8IE0LwD8MPCizyWjeKP", 405 | "FF3FJN9lh85grj5U+XnaF6nsTzXoXxBjifxDJZW8iMRGZGCsMD/CvhM1zKrVbinp+Z+nZJk1LDQU", 406 | "5K8/yPm6FIX8V6jpbwrbnUrwXccsa4Byu1lJHfIBGfU12XiHw5b6X8H5DM5h063uluLyYKWaKLo7", 407 | "DuSMg/hWfpVgbn4x28kluZRBEpyWAUHJ5r6A1TRU1XwNqem+UpW9sJYMHkZdCo/Uiu7C5fHE4T39", 408 | "bo8GWb1MBmPtqO8X+Z8bardeC1gtz4c8XjXJZXKNbTQmF1GODySDn0rovscEWm6fJBdwyiOWNiu/", 409 | "kcjIz2/+tXyj4ftms/iDa2kkMkVwt35UwZ84ZWwf1r1+2iu/+EinklnH2WQAQwBMFWydzZx04r5b", 410 | "McHCjUVmfunCOf4nM8FKU47O2nofrPB4x8NmBYofEHh5psgKE1GMk+33s5NdGZZZLQRW5aKU8klD", 411 | "hT1ya/HrT7eODWdMuWeUXBvEwhUeWw3qQV7k+ua/YC3nZrrymLlmA3M2AB+Feth6/tEfkXEeQ/2b", 412 | "OHvX5r9Ow+Ka7KL9pijDt8pZASH9xnvQ2crgELu6Adq0Li2j+zl4GTZHjcmSefUf1rMLEdc/jXRs", 413 | "fNB39j70VG3OTyfpxRRdAfj98THIm8L4wf8AiVrmrfxOBN54N6DOlRfzql8SBmfwupKjOmAZNX/i", 414 | "Vj7T4ODEf8guLqfcVrDePqPqXPiD/wAlt8Nf9etp/wChV6b4b8LHxJ+2lrt06sbTR9Pa9lbsCEAQ", 415 | "fmf0rzPx+R/wu7w4Mrn7La4/76r6p0qGPwp8NvG/iWXEV74j1iDTrUngmJFXdj2JJqHK0b+oPQ+a", 416 | "NIvZ7/8AaX8YXMzZ221xGvsFAA/lWZopB+BHis8k/aIfx5qbwu+74+eMDuGTBdEg1T0KZf8AhQXi", 417 | "1lYEC6hycj1px0XyQuh0unY/4Qn4aqc5PiX/ANnWvs6U/wDF3tY4x/oVt/OSvi/THD+DfheQwwfE", 418 | "uc5/21r7LlfPxg1rsfsVt0+r1jif8yomf4pOTBxn5DWj4GlSH4dyJeWyJDcGQh2kxkbsf0rM8St8", 419 | "0Gf7hrovCZsJfhXFbPFcYYOwZX+4dxyOfzxTwM3CfMt1/mZ1oKUGn1PQvDFzFqNvbWLyxiaKPZl2", 420 | "IDrnivQptKSGBEW0jgUHP7kcH8e9eDxeZpfiW0liLNAThWYEZ9RXtP2gtp8FxHungZcn5uV9q/S8", 421 | "TNVKUK0dpI+Qw0XCcqct0cX4qd/7Nl0+yDx3Mw27kXLKPWvzE/aQ0mS01p7SwjAs9NKveS4y0sjn", 422 | "HJ9sj86/UPV7hjM8kcRR8cE8nOK+NPir4POrfDDxr8u6+lsHl3EZO8HcP5V4uJjzpnq0JcjTPmLw", 423 | "p8dL3TPHejavqmj2ssGm6QNN06CwG1oHKhRKAT8zHGD+lddpnxP1DxX4nv7fS7C/EwyJrid/mJP8", 424 | "O0c//qr5Te0vmhtbZFEmGJVkHzIeMgnrX2T8APC1lLayrdq81wzh5SWI3k+vrXgUsthWnex9LUzm", 425 | "vThbm3PYfh/pN3F5l5qLq90w53ncx/4CuSBXaa23iC+065TTXlhlMLrbtHbOEjfadpIOM84r2fQt", 426 | "AtrDTQba0jibbxsUDisO9imh8ShMbYpMsSexHX8xXs+wcYqHQ+fnW525Pc/J+30S/wBH8dabPqM3", 427 | "mTNfhJDyWL55zn3zXs8+hajZWyXk0MfkocsVcnOScHHtmvLfiQdQ0b41+LLGe/JdNWklhVFKNEjE", 428 | "so/JgcisBvEesvKc69cS28mNsKyvmMk55JPPpXyOZ5dKtO6drH6jwhxbDLaLoyg5czvp6WPoaPwl", 429 | "en4d2XiNb6xaHzo3FmJh9ob5guSnqMfliv1Bgje4so4nh84FAS7HDA471+QMMEiajY3bXcgYyBXt", 430 | "mUbTym1lfOSeTkY4x71+u9nOYdOS5kdyERSQAQcYHaufCRSbsbcczqTdJz/vfobCRG00wwBwuP4c", 431 | "8t3GKphydvDD61ameCay3D5pBgq+cn6fSs4tg9h611s/Pyfcxzjk9+KKrkuXBxxzzmiqTA+aNO/Z", 432 | "y+EnjjXrvTfFOta9bXulXbWNhHBeRxtJEuMFsocn6Yrdsf2W/ht46TUZde1DxFbpoN01jZyQXUa/", 433 | "uo8EF8oQT6nis2x1q0k8Qa7LJe+GlhkuZbgXc8q+eMY2tEc5Iz74r07wDf8AhfVfD2s2HiLXHmku", 434 | "LtpmtI9VW3SZNozIVDAkfiRXzWXZni69epCUbJJNHq5jgaOHw9Kqql3LdXTPnnxt8FPAeo/CfW/i", 435 | "Curao/iPw5dRwRW4mjMMkKyhULDG4Eg/eziua+KPimC41f4Y+E4hvWGX7RIFbAEhJwT619Z6JoOg", 436 | "618APFegWsVsrvqRa5zIpZoEkDIDzkjC9TxX54+Jbh5/2w7OJiPLguI0jGegwTX0FJtuzPKTTemx", 437 | "leFUsB8ePF5js3S4Nvdea5kzu9eMVV0Gz06L4A+LUW0KwtdQtJGXzk54q14VbPx98XDji3uu9UtE", 438 | "cH4AeLmAJH2iE4/Gujmuvkgex1Gi2lrN4N+GMNvYTTCPxCXiRDkoQwJY8dua+wpDn4y62OT/AKFb", 439 | "E4+slfIfhfV7nR/Dvwyv7FY2mOvSQ4cZG2QhW/Q8V9bs/wDxeXWsY/48Lb+clZYr9WOO5V8T9Yf9", 440 | "w1tfDK0kn0K3V7xAhkkzkkqoDHHGOtc74rkwIGHXY1a3w31Fh4LEUEMORJKpJJDBix596zwr1YSP", 441 | "SvElo9z4ekePyHNu2+Nkbk461a8Oa/JLoMMccMsrlOVA3DI7Gudsb97TzLO6nR7eZto3rySeeKp6", 442 | "XcT6Jqz7lY6e0w3kfwAnAP51+j5Detl0o31iz5TM2qeNT7o63Wb+UxeYbG8ibGH/AHLHFeOeItO1", 443 | "O/0++i+wypHPHsbzSEBH86961SW4fQWkybiEgFXX72M1zWpWrXOmMF+YY79RXJKDu0dKkrKx+MPi", 444 | "vQ7vwp8VL/T2TH2W8Zo1HRkJ3L9eDj8K+yfgRewHVoJ4NpDxgmPruU9fxBryv9pvQJNG8fabq0cO", 445 | "9bq2KuVH8SOeP++WH5Vz3wD8XG08drYTSfYpA6yWhl4V2zgp+PpXJlslDEOm+p2Yu86Cn2P2B0mG", 446 | "GfTUeJgyFcgt/I1yfiXTZHSVYV/eHByTVzwzrMK6VFMf3OR+8i/un29RXamyTUbYz8HA6+tddZWn", 447 | "Y44O8T8e/wBpHQdRsf2ldW1eS0MGnXsUPkzsvDMIgGGcdQVNeUXng/xFpOmJql/p7QWQZMyMP73T", 448 | "j3r71/a/0OMfDHRLuCB7uZNXCMqRlsBo264+lfEAtvHWsStaroHiG/08qCrR6fI65B4HC9a8DG0a", 449 | "iqtRPbwOIioxk+h6fb6Vfz+HILwJGYRslRI1yzjjtjrx261+r9pIJ9JtpYkAVoFOd2D90YJ9K/HS", 450 | "90nxz4W1DQLrXvt+kwTzQ/Z7acskg+dcN146dMd6/YINPbkYykW3+Igqp78+leLDCui3dn1OfZ9D", 451 | "MuRxjblv17l2QyqVV8DjGd2c/jiq5yHGcdehpZXnfQJ1hkWW7K5TIJAPUE+3tVmz8G+L30My6jde", 452 | "HLS9mVPs5ht5HAckZDbj0xnp3Aps+dSKgceYFPU9KKzb3wx4xi+IcGif8JNYRRLEkk80OmKSoZjk", 453 | "DceDgUUrt9B8vmeCaL4G0nVrvWXk0jxXdrDcNCn2OSCRoVwPvA/x/wC0vFami+FLS71TUornSvF+", 454 | "oWMFw8QjhsoXkVihUeZK3zBgGJKg4PFY/hrx/wDAzw7Prba94mfSbXUZSNNLwXBMltwQflHBz681", 455 | "t6V8Qfgzpunaja+IfGkGlWF3f/a9I80XA8+LGFcYBx1781KTTTsefUoyaafkTadc2fgf9nnx7rga", 456 | "/W6mxaLJcIFPy5RV653ckketfCt/cGT9rvT2Zsl5oTz/ALlfWfjTxb4Mi+Amu6VqOtW8aXf2mbSU", 457 | "cMz3krECHHGc4JbJr48viU/a70n3ng4/4BVQd5/I7qcLKxf8KHH7Q/jAZ4MN3xU2h6JrsP7P/iSG", 458 | "TSNRjuLqWFrSJrdg84B6ovVuPQVU8Js3/DS/i9ef9Td45ro/EU+oaPe6fqbzSQzpCssUkTZbBjHI", 459 | "969PAYSNZNydkkjkxmKdJxUVdsv6L4W8Ut4I+HS/8I/rG+18QmW5VrZlMUe8Hc2RwMd6+tpEcfFT", 460 | "Vb0eWtlJZwJHKXAVmUvkde2RXx9Fr2vX+iaBfSeINVMGq6kLJAHwYzuA3Hn3r2AfDSY+O73R7zxX", 461 | "rM4gtopjKhxu3lhjBzjGP1ravh8uXxVH16GMauNe0EvmeneIY0vFTyruwARSGL3KjH60vhi+0bQf", 462 | "B81pqOqaMlwzOw23OR8xJySPrXl2rfDTR9NWMtqWsXhcHPmzY/kK3/BPw38Iap4fF7qFlc3UivIr", 463 | "Ri6YD5SQO9ZUf7Njfl5mN/Xn1SOm/wCEh8KwXcclz4qsXVDnarFue1b1n4t028h+zvdxy2tw48uZ", 464 | "WwrDPBrAj8HeDLbU/s8XhrT5AeWaQs20Drzkj/69N1/wfZXumomjNDp0kQCpEiYjx6EDkdeor3Mr", 465 | "zXC4ZuMU0n8zgxuX4islKTTse/Wfnf8ACMNEd08GRjDckeorQnVFt5QiscLyG615b8N7nW9Kso9L", 466 | "1i4ttRjgulIKSlzsOcA5weCK9p1R7a4gNx8se8HOTXo4ipCcueDumc9KEorlluj4i/aF0zRrjRND", 467 | "vtcsvtmk2usRfbYkmMRaKQFD845Xkqc+1eDx658A9GuY5LPwtoD3EbAo8urXM7Ag8Hhute4/tNXf", 468 | "2f4I6rt8uVGmhXg9P3o5r4DXwxqbYuIrKEQyjfE0kqjcp5B5NeFVx0aNR3S+Z7OFyyti42hzO3Y/", 469 | "Un4M/EfQ/G/ha9tbSRBJp8oRcBvlQjKjLckDBFfQaa9LBpT21uQyjJYg4r8wP2eNUvfDXxIvtGvS", 470 | "ijUIhIu1w2CpPcfWvvaz1DdYAmROWKn5q7lX9tBTXU5amGeHqOnLS3cr6tqcx1C7aRsSSL+6ZugP", 471 | "b6fWvkT4o+M/Hvh62Oo6TqN9daR5nk3ImmkV7SQ5wCoP3Tjg5/pX1Brd5Ap2kCQgEYXmvJ/EkkGr", 472 | "+DNU8P39hFe6fexmKWcJiSPP3drexwc+1VRq3Tg3Z9PU5qtOKkpWufNPjI67rHhzTNSvruW9+yyQ", 473 | "XbNI2diuFOOc9zX6sWrzmKF5G3AKD1wBwOxr8q/isl1oeg6NZBhEEighuVBGGKxY/EZFfbekeNte", 474 | "bQLDckKxvax4eb5mJ2DIz9DXhZ1+7r2f9aHpZVLnw6fqfRw1i0Fm8F5NsiJxJJH8rbT1I9MDJH0p", 475 | "dW8SeAIdK+1Q6t8Q9VX5oYX3TeSZNnY8fMAdw/CvMPhtPca98d9CjvpTOhmZmTGUIRGIyPqK+0JL", 476 | "W1bWSv2a38qKItt2DGTxn68V5MW5q6PSdkfMdrNo2la1pd1pFx4kurnVNOMsh1m4d5YwMgZDeuCc", 477 | "0VXfRrvWPE1/4kt9QtrSOGSUYnjLJ5YZjxgjHBNFUtBNHwL4s/Z48aazpegxWuseGxJZWvlTb5pM", 478 | "FvbC1neMfg14pvNS8CwSnSjp1mkVtqVx9sCCNN67nAbGVAzR4h1XUg8mL++jOMkC4bj9a8wjuJ7j", 479 | "xGguLmeUM+D5khb+ZriWOjfS5+x43gLCRjo9zo/i3YXOofFfTbXw81nqGh6cAqzR3abRh8Zzn0FZ", 480 | "d9aTXH7SGn69BLavpUTwtJOJxkbVwfl6muiv7fQdJkt5dStmv7N/lVLYsjhvruxir94vgO2tYrq/", 481 | "sdetw/3dk+4dM1wrM56WR7tLwmwUY3nUk+v9aHK6JFFpfxt1/XLu7txZ3aziJRu3nf04IArsPiFY", 482 | "XNzpmkWdlE094+noqRqwG5tg7k4rMW80ttKeTTbBExIPLaXDHZzy3GSfxrqfHUxGqaLcxMAxtlOQ", 483 | "vAOz0r6jIMTUnhsQ30iv1Pzbj3g/L8vx+X06Tfvzs9emmxzmkeG9dj+HngmGbTJoprTX/PuUZ0/d", 484 | "R71O84OMda+nrjVtMj+MOrXj6jYraPYQIsvnLtLBnyM568ivkk6jfSOwa4ORjoij+lMF1etcEfaL", 485 | "hsrk4Yivm6mPlLdH2c/DXAJ+7Vl+B9Q+KNf0i5hhNrqNtPs3BtpJwfyrC8A+K/EGm2V3HDBaSWD3", 486 | "Eux3djwSecYr57xK8ZLO4YHgu3X869I8HajGvhc27fMcurKvI/EjpWmFxEnNo+Y4r4Qw+WYSNajJ", 487 | "t3s726nrR8dPBAyJYqjMf9eMHJ64BNS6LqOs+IvFkGmWk8VpLLuLSoN3lqBliR647eteZMDuVSF2", 488 | "febI4T0Fd98J5Fk+MRSSV4UNnIY5QeN2Rz+Wa9zLqXtsTCnJ6Nn5ni6jp0JSW6PpjwJ4Q0/SdNmv", 489 | "rs3sGY/MnmnXkk9MngLx2HrVDxDrMOoTXEuliRNItEPnXkhxGD6A9z7DmunvdT8LWXh9J/EJvtTu", 490 | "v+Wds12TET68cV4N4w8RTayQ1y0dppcOfsthbDbFGPXA6n3r6vHzhF8lNWSPFw0ZtXnqeS+L/Eul", 491 | "2tvdnXhZvpdzHJG32y3aZFOwsjbFBJOVHA7nnjNfO3j7xV4f8XeKdN1Hw3JMLC3gNs7S2vl5QOzo", 492 | "qDqMK+3kAYAxXSfErV1vdXstMQAoswll3YwowQM+/Oa8c8L2F7q2oXeg6dHLqWqwyMzRQoSQqnae", 493 | "w/2elfGZhX53KFr2P07hvBvC1MPiJu3PzL/I3PDt9eWnx/8ABzWm0rPPslGM5Qkgj8ua/QyKK3tP", 494 | "DlkfKtWu5RuWMg7kHqRmvzbXzLP4q6FOsk1vdWEm9jGMHIcAj8c9a+7/AA/qP2qVSmZETA8yQ9fz", 495 | "r3skXtMHZbny/F7Uc1qPp/wx3DiJ9OcXFp5rKPvAFQPwFeW+I75Fs2YbWA7A4Ar0fXdftrPw7cAC", 496 | "JXZcZFfOGt62JkkRH3JkkYPFehDDuM0fOTqqUDzj4zedq/hHS9Rt4wxR9txgfcKKfmz2BGK+mtHv", 497 | "p/8AhEtNeGBMGygdSSG3ZjUdP614EI7fU9DlsLiFZ1eYSbC2Qcdcj0xmvfdNjEGmW8WBBGmwRxjG", 498 | "FA4AA+lfO59Nuuk97X/Q9vLcK44ONTo2192v6nunwZunt/2gPDslxEIzMXi2g9C0bc19n6peCz8N", 499 | "a7qDHHkWrtn6IWr4Z+GV40fx58LxbSXF4HJwBhQrbmJ7ADv7V9f+M7uP/hTmpmKRJEvGWGN0bKsH", 500 | "dV4PcYNcGH+E1nueD+Jr46J+zncxh9t1eRR2w9d0mAx/Ld+VFcZ8UtREsGhaUhwnmvcOAeyjav8A", 501 | "6EaKu9iT4b8Qffbqa84iZR4jjBOPn59q9G8QH527DH5V5rGFPiKIsCBu5r56D9+5/UeYp+zsd3qo", 502 | "0jT47a7uYotYtmYK1vGyrhuzEjmtfV7/AMNR6Lbtq/h67liCkxCG6I/h+tc9qt7pWlxQ3kUMeqpk", 503 | "LJazMAmf7x4zW3q3iLSY9BtpNT8N2t3G33EjmIx8vNS6tJv4T6KnCtGi1zmHFPb3GlmSytvstu8T", 504 | "bImYnBVu5z712Xjdi9toLIRua2UZHP8ABXHwSLe6FHfW0AsrUXJRYVOdgYHFdD4ynNt4e0S6cSOq", 505 | "W6swAyxAX+dfU8OP9ziV/dPxbxUfLjssm5fb/wAjjAs63RzJIqlckjCiiTakscks/wC7ORkyZ5/C", 506 | "uXk1jy9PNzBYyzW9wQSSf9WuerZ+tSSXt7FdWsDxRpYPkLcA4O7rj2FfMKEj9G9tTuk32R2KCAXf", 507 | "kvjc+MYUkc+9dX4RJSPVyDNapH84keMY6dRmvKrWTVJNflt7+eNJchrR1HAUg8cdTXtXgLSNcuvC", 508 | "d+TZ3N5G9wUMiAPjgHk54+mK9TKstxGJxChSjd9j4LxEzjC4fJpzqaK61fe/5ma3iE287fuZXYqT", 509 | "vlfrn2xXffC/V3uvifHvWNLd4JVG3OSQmeMnnpXPy+HoU1CZZ7Wa7cE5ViASfoMn9a63wdpw074g", 510 | "2Ev2KeBPKkCs0bAJmNh34r9Cy/hHH06katSKSjrufzViOLcBUXsoNty0Wmh6J4g1RWtxMk10wiXY", 511 | "Vkk6kcdO3GKpaW8Or+Gb4z2waSJCVYnvXJa3eT3F9LbRHKykO2MEkf0rovDXm2mj3m/LBo29+1dF", 512 | "bAc8pM3p4vlsj4d8beJdSm+M2v6dbWMEkdrP5fIOdqgc9elcDBrmqeG/iPdapZ3baTczrsklgO7y", 513 | "w4G7gH1A4r2T4yeGPBFp8M5/FCTzxeL9Qv1aOLzmZJQrYc7ei4UDn1r52037DcXSJN5hbPz+Y33v", 514 | "8K+dxmWrDVEmldq59Lgs9rYzDpcz5YOyv5aaHTajr0N4lxKl/dXNx5YV7p/4yTknZj5efc19IeFP", 515 | "F19J4J02azuMSNAm8EdWxg8dua+erjw7bS2ZmtX8uUxbApGQ3Oc4x/LNdv4Wj1HTfBNo13G0Sh3E", 516 | "T4+VwG5we+M4Nd2UydOo47Jo4czvUipSd2e5ah4kutXso7S6m8l0BBVejGuTuYXijDMf3eeD1zmo", 517 | "4pY7qGNwTu7gVp3dk1t4OFxPK376ZUhix75zXuune7R43NYm0uBoo/NI/dqdx9MV4bd/FfxTeQeI", 518 | "rBL+cJfX263aNGzGin/VxtwVHT8j619E29pMmhs5yuIzuHZuOlfLsunCxW4eTYtqkjtCfNTK891B", 519 | "JHWsK2VKpUVSSVrW/U9HDY//AGf2S3vf8Ev0PRdH+Knif/hObfX47y90y8h0xtPu7pFHzISR90nh", 520 | "tpI3Y719OfC39pLV5fh38O/h1qtlpsfh+3llR71XZp41V3+zxFc9FZlXODkAelfF2nXEFvqUUt1C", 521 | "ptsK8oZsb1z1/Sut8LwwXPxR0eKKaG1t2lRxJcSpGsab8l3JwQAOSScccV5udU6WHw6hFI9LK4e3", 522 | "xEVN6NpH3Z421ZbjxtqM7sTDYQ+USTwNo3N+pP5UVasfh/8ABrxLf6lqS/FC5vJ2k869MervHCWk", 523 | "JOAhwCDhsAZ4FFfJKLavY9GeDrxk04P7mfKGvMJCzL0Oa84TB19OT9/mvS/EXMjcY4rzMf8AIcTv", 524 | "83NfO09Kh/TWaX9kzs9T1G00+yivrCGO8kjIWWGcOU574PBOa1tW1/7JodtPeaLo9/E4OI3jAx8p", 525 | "PpXH6lPPZRxXukWF09/GcAPbMykH0GcE1019r+tW/hdry4tbW5KQhjBLa4ycdOlXOvFPWJ6NCUnT", 526 | "lr06GXbuut2lkzxJZWrs58mI7UUjOK6nxJcXOleH/D17DHK0lvAGiC4O7C9q4i2kbVfB66leLFal", 527 | "b7mL7qgMD0HpXo3idxa/D7wpLHbpepFaoRCHKiQBfu5HIzX1nDtT93iEl0PxjxTgnVwE3rea/Q8N", 528 | "a8nS0kmtbNDaXRLTRsxxApPOPXrT3MieVE8kb6XyFwuWD9ck+naod97PZXF7ZW8FskjF57flvLXP", 529 | "KLnnjnn2qRrWaNLW5Wd3sSxRoAOAeu73r55Sm3oj7+NOmkk32LMEMsepeTe3Es/mHfHKxwVUj7ue", 530 | "1ey/De0tLfTNRuLnXYIEeQiKJJpBg+pxx6V47YWNvHrkscTS6jbyEM3lkysjd1wue+OK9/8Ahxo+", 531 | "oQaDfwaj4W1CC2actHcX1wLZSmMDAYZ/SvsOB4NZrGVR6JP8j8n8Ya8Hw9OlTtzScfN7/wBajbTU", 532 | "J41ljOlWuqW6lxHeMGLSrnnJwe/p1rq/By+Ip5yJdaU2EkuLWwS4QbIyMbduAR171nrouj6MZJdR", 533 | "+ICaZaFmZLCx/ftGM5wCwx+nWt/4eW/gl/H0kXhzTNZ1C7k33E2qX52pFhcbgAAMnPHua/Yp5hhk", 534 | "+W7P5aw+U4htSta1vM6a78MWun26XVy0LOsZPU5OTwKnjiEemysoKq0RZh0xxXZ6hYWay+ZITMRC", 535 | "AARkKenWuS1i8isPB+q3ox5ccDAk8ZJHFeFOlGCZ9dGrzNH5sfFK9ubrxyLaR5JI4wyQqeylycD8", 536 | "Sa5nwz4e1HW7mZbDTWv3RMuI5wjr0GRng/Q113jBPP8AEmn30mDtaYn8BuFaPwqv5bTxBqdrbzQw", 537 | "XFzb5jeRQQcfeH45/SvgoUFiMb7OTsmz66tXlh8E5wV2kVNMaeC7fT7xJVlU7QJU2upHZlPce+RX", 538 | "UK91ZrEZjLLaEsSpJKKCRkgkhfrtFYPji8uf+Fm+bO6zXksgjnIxjdwO3HpW1ZT/AGuwbKv9qAIL", 539 | "CISOfxbhR+orFydGq4p/C2vuZ2ypc1OLktWk/vVzutHtiZ4mglRoGwcZ6fT1r0pdKbVNbtDIirp9", 540 | "omACeGY9TXiOl6m1jcCOSTe4Py7JhIwz6kcV7P4f1X7RorJJcQxQKCzvPIAAvr/k19VhcXSnBP7z", 541 | "5zEYerGRoakcw3EcZQRW8W7C9Cdw4/LNfI2o2GmjV9ft47tLuG4mLSTplQpPoGHY8fhX21oegWvi", 542 | "HWP7La+g02G7IihvrrCIZCfuYJB57HgZFbmt/seWmn61G2oeLnt7q6G/yokhIAHGcLwAf1roqZph", 543 | "40Epu7u/krJF4bB1Yzb8kfEEkekxWOgibTzeSTTC3jVnPyqFJ3jpz6A8V1PhLw/qvivxpp/hPSLO", 544 | "4Op3NjuSSU7YAMY54yD36/hX2t4d/ZY8M2k9s+r30/iFIJxNbrMDEsbYxnCHnPucV9Q+G/CmheGd", 545 | "IisNJ0mxsYEXCpDGB/8AX/OvmMxq08TJqKPfwVaeGnGa3Wp8QeE/2cPiVaXk63Umh2iyw7UuGk87", 546 | "Z77AATxnvRX6GLHIQBFCE9+lFfO/2RBdWfU1uNMyqSvzJH5Q+IB87V5iP+Q9H/vj+deoeIOWbvj2", 547 | "rzDgeIIjz/rB/OvmaK/eo/oPN21RlbsevXWl682m2sug6LImsBP9HePyneTPZRnOe/StvUbb4kaZ", 548 | "oFtN/ZGqT3BwJY5NOL9j6D1rdn8fW9z4Ys7DSBFpevRxqLaeNiJGwMELlf613upeKtfsfAVlPpWu", 549 | "Xc+qfKJ4ztkx8pz29cV+s0uE8nrU7xxdnppo/wDI/ner4rcZYSbhPK21qrpTXo9LrY+YNRMz6Mbz", 550 | "xRtsNSa4Rhb3EflEghgSFPOBXXeIg0vwv8KLYNAzvbols7nCEkYGfaud+JUl3qbaJq+sTNNqky4f", 551 | "cgBbG7OAPrWj4jdm+BXhY2+MiBFTtzk14uX4anhsViaKd4xW/ddzr4yzXEZhl+XYiUeScmnbs+2v", 552 | "mYieC9PshFNrvjS0092O67ttOg8wyHuu5ug/CtmJvhvpV39qsPD+oa5c7dok1C6YQ49dg2rXjkc+", 553 | "p3Wn3PlvFp9zDyfLjBLHPTc2TziopoPtcNnPLNJJqOTlXcksufeuKOOwdJfuqF356nZUyrPcW74r", 554 | "GcifSOh7IvxQNvFJY6BDoehojkGLTbUbge+SoAz9TWFN4ov9SdJb271K6aWYoQ8+0HAJzhf8a5PS", 555 | "rD7b4xH2G1lSRRtdCmNxA6genvWvq0cekzac00YhZ7nywqrjJOR0/rWmIx2ZPDyqQXLFO2isYZfk", 556 | "XDsMbChWn7WpJXV3dW7/APAPsD4Z/CbQfFPw7/tt5obOccMMAt90HOWz6/pXR+E/C9jovxr1Dw/D", 557 | "11DTJ4baSRxlLhoi8LHaccSIv515j4Uv57fwNpzQMoV4FyHRW6fUHFdXouqNb+P9G1G4ljiEF5E7", 558 | "uqBAqhwSTjHav0TC5fiJUufmXK4bdb232/U/CswzPAQr+zpxftI1Hd9LXatuWL3xJ/bfw00XXb7U", 559 | "zpdneQC4+z21v1c5DEt3IYEe2K8b8b+LoJvDR06xury4R/mZpLcorD39axfEXxMvPh54ev8A4V6j", 560 | "o8c2saV4kuDFqE4zELSSTegVepDBtwPTBFcV448Tyaxozpoc32rUVTeqWNvvwAOc7Vwo/Gvnqmcu", 561 | "VHXc+njlSU7x2PE/F8oGuWNqDlvKkdyR3YYA/KsvQpY7XUrK5Z3hiDeXMyDkAjHFdZ4ltF8Qx6Vq", 562 | "WjyW9/OqKlyiNsZJMZKsGA5688g1lJ4W1+08J3F9e6ZeW2nOxW2u3jPlOyMMgN0OAefTNfO1puOI", 563 | "bi72Paw6UqSU1a+6NpPDeoa741tHh0bU72z84l5baPac54wx+Xr6nFRAy2XjrV7G4iSExTbmjL71", 564 | "UNzg46nmvWvCXjHVr/4f33hOPw42ua5JF5aPaAM1upGAdwJCj5unTOM81xGn/DvxtrvivVbnSdBe", 565 | "UWQxqqllD2x3EAHuTlTwAeBXDCqlJK9z3Mbh5TUq0rLay62tuUJrSS6eKCOSa2aSbdDKwEUStwAV", 566 | "Ucn6V29ovjvwJq0cutaDb+INI/ju9JTzCv8AvKK88MjhisUaeceCFgMknXoCeFPvX07H4U+K/wAU", 567 | "Pgx4O1P4fI1heaEk0LqwSyjnUlQpRwuJD8vO4nnNdTk17yZ5GnU8T8MePLrW/G17YarqFloVleTO", 568 | "yzzRlzAD/B1H5k/4V9T/AA98e+GvBnjnRvD+n+KtW8ZxancLFLp0f+lmIsf9cpA/dqpPK5xgfjVP", 569 | "4ZaBr3inx7qPhH4i/Cnwz4k1XT4xJcX93HBasq5CkfKrF8Mcbh1619T6X8MVsbQ2Wi6B4S8Bac7D", 570 | "zW0m3EtzIvpu2Ko+rb/pVSr1Vpy/joSo05K6Z6lFaQbFeNVMZGQyng+9XI1RASqn61Da20NpZQ2c", 571 | "KylIkCIWfJwBjk96vC2dmztPuc10cysYuOpESzHjOfrRVuKyCqfmc5H1opOoPk8j8jtfHL54HNeX", 572 | "S5GuIT/fH869S17ln715ZdfLqyE/3x/Ovgqd/aI/q7NdaTO41WLUL7w6be3YQS8GORXIYVUvDrVn", 573 | "4ShWxvrtLtNvmMsx6d+tehweHtYT4f2/iKXTbhdFlfyY7wr+7Z8fdrGvoV/seQgDIAz+dObmmz0M", 574 | "NTpVaCcXfSxmSwXF78J/Ds12ZLrVYbyVJS3zMRtya1deheb4KeHYYGMUjFERiPuncQKls/EGh6Z4", 575 | "Pltb5pft39oPIiJEWBj2YPPQcmq3iu8W9+B+kXFlHLBGx/dk8MuHIB9q/Tsqo4T6n7ZTvOUfeWnQ", 576 | "/lfjrE5qsylhZ0eWlCp7krPVvXf5nDWHhi3n0e+Gq3Yt76M4iLSBRJID+vTpWvHb6FI9roeT/wAJ", 577 | "Bar9omIjPEfoT0P0ridU1W31GDTJwJE1OwwUEjk72GMtj39TWdc6/cXWstq0Ci0vkUiV4xt3+3HO", 578 | "Kh5tleGfLTp81tL909X+JquGs/x/7zEV+S/vWXSS089OXV+p6rpeu3Gr38uraHpSRa4JvsUFrIwL", 579 | "SE/KGI45OfwrjPF11Lb/ABMXTdZurPVvEcEqQzG0G5LfIyVyOPlyQT61xqapdw6hY6hDJJFdzT5f", 580 | "ychuBnjvXrPg/wAH+IPFdy9wnhnWYIJFLLPIqRBn7Eluoz1xk14GZ5xXxyUbWXVLZvufR5VkeAyW", 581 | "bk5pvo3q0nbReR9nfDr4c+H9V+B/hvUp5tSW4ntA8m26IGckcDHA9q6S7+ENlNHttNV1CENxhgr8", 582 | "flWl8P8ASrnQ/hloukXabri2tlSUo+VDck49smvU7WSIIp8ps47CvUoZnjKcEo1GvmfnWPynAVcR", 583 | "Ofs07tvY+R/ip8Dx4y0TTbKLUktviNpFsI9Ju7xQkWu2icrC7jgTR8gHrtxnjkee3XijxhpGlnSP", 584 | "Efwq8WW+qLF5RW2tA9vMQMbhKPlIPrk1+gVzYWurWBtb6wivLViCUkXOD6g9Qfcc1TuvA+iap5Qv", 585 | "rW4niThIHun2D2xn+tec3Vu9nc7IxhbsfHVv8MFvP2D38Zw6DaweO9LgmljjQPNujEpbydiffO07", 586 | "R6EdeK+TtR+LGs+IPhLYeC7+ziXSbfUZLtYY5WEccki7XKKc7S46n2HFftBBpVva6QNPtra0gsli", 587 | "MQt0XCbSMFcDtX47/GD4Z3Xw5/aP1zQo9j2TD+0LFo0CKYHJOAoJwFO5fX5c1TlKG/UIpNn1x8Kv", 588 | "hv4a8MDSfFvg6Vriz16xjgsYZYw52eYokaYnO5twIOMYGAOTXudnon2fVLudYLHUbC8Z0vWe22yO", 589 | "QQjs+D/DuHPX8q+V/wBn7x7ZaYdP8Na/cx22jTXLyWd7LNtWzLRnfHzwFLYcejA+tewa7+0D4X0S", 590 | "bfpBl8Q6kL2dbuKJfLt5AwK7957kAcgdK6aOFdRe4rmWJxCpv32fnv480tNB+LfiTRzIjx22oTRq", 591 | "PPZVChjtxjqMEfWvsX9lr4neGvC/wW17T/EutW9vHbXge1gjtnMkgI52jnIyPbFfG3jbV21n4maz", 592 | "rIhhsjc3RkaKF9qpnsKXwKl7c+Irm3sLee4uZE4WJC7Nz155P1xinhqa9qoSZOIquNFzR+i/wX8T", 593 | "WPi/9rb4ganp8c8VtJYZiEygNtM6kZAzjrX1qLQFPmZQB618EfA/QPG3gvxvrGuSpbWCX9mINrss", 594 | "j8OrcjoOnrX1LF4n8Xn5nvdLePPAks/8DXpYjA1Ks+aG2x5mDx1KnBRluerx21t1DKG9aspCgGRI", 595 | "h+hrzBPGOtrKqyabpFyccld8Y/rVtPGGqrMN2gWUqdzHekH9VrjeX1zuWYUO56gsSkAYyfRaK89P", 596 | "jaVeTol7Eo67LhGorN4KqtyvrtJ7M/K3Xh8z85ryq/41Id/mr1fXs7nOOa8m1FiLzcOgPWvgqfxo", 597 | "/rHNtKT9D7QEurXf7H+m6UjGa2toVumt1ZMKmSdx7556V4dcuH0Ob6d6+tPAHwQ8JeJ/gR4b1a4/", 598 | "teG9vtPR55ILsqCT7YIqTW/2Z9Jt/Dt7dQeJtT0+yghaWZ7iBZQiKCSeMHpXq4zB15z5uU+LyXjX", 599 | "K8HTnSqSad29uvyPgbXNStbe+WIwM00ZYuegO4DFdfqEy3P7O2kXC4A5OPQh+lenWfwa+FniTUxf", 600 | "XHxdbyJQAIl05oCR/vNmofH3hHQPDdvpnhbwjdnXdKhlhzNGxkA3OC2T+Zr38gwVWlz88bXifm3i", 601 | "DxTRzJ040ZXUZp7W2PlLTtE8R69rMx0bRL+6ZjgMkRCfmeK9g8N/s4+NdXKyapJHpdu5+YKu5v8A", 602 | "Cv1A0fwJ4Ws9Og/s6TTHTYCvkyIe3tXTx+HYwP3UI29jxXHHL+V+8cmJ4lxNXSLsfGfgX9nvQ/DS", 603 | "xTPFJf3a4JlnAbH09K+htP8ADsdpEiRwqOOMCvU4tDwcsAfwrSi0pEX/AFa8eldVOlGK0R4lStOp", 604 | "K83dnBWujODkKUrfg0xgqlua6cWgUcZI9qJBFDC0spWONRl2c4C+5J6VqoNmbklqyjDbFVHyjFXV", 605 | "QA4VBke1eHeNP2hvh34QaW2g1BvEuqJkfZdNwyqfRpPuj8M18j+NP2lfHfiZp7fSZU8K6Y2QUs2J", 606 | "mI95Tz/3yBXo0cvqy1lovP8AyPPq5hShtqz738WfEHwZ4LtWk8R6/Z2MoGRbKd8zfRFya/P34/8A", 607 | "xV0D4lLpR8P+Hpbe50m5MsGo3bASzIRhoyi/wn0JP614RcX1zfXzz3M09zcSNl3kYu7k+pOSTXY6", 608 | "L4D8R62VZbX7Dbn/AJa3GQT9F6/yrrlgcPGFpas815hWnJW2OOsxJaWC61YW5vvDcz4u4VO57Jz1", 609 | "VvRT2NQ3Wo+HYbT7VBrAZwvFokRaQt0VR9fU4xXtNz8I9f0Gx/tXwndfb9QXm80+ZV8q5Xr0PBPs", 610 | "favn/Vmnk8TXpu9MGk37sS9r9lMPln0CnoOK8NOvh6jV7I9pOhiYK5yV7IfLcnIlLZYgnr1r6o/Z", 611 | "R8PNdL4o8QyxuYg8dtFIQMMfvMPXjjvXztoXg/xJ468UQaP4a0ye8lLjz5whEEGeCzydABn6+ma/", 612 | "TH4eeBbHwF8LNN8N2b+Y8QL3VxjDXMzcu+Ow4wPYCu7KqDqVudrRHHmuIjClyJ6s6+CCOIAAD246", 613 | "1qRIOAy5GOMGmRR4bnJ5wCe1WOE2gbgB1z6V9W2tkfNRiOUKJi2f1wKlbYcAMoA9B1qiwUzM5O5w", 614 | "vfoPfFMk5jyHYN14XOR9PWsE9zS12i60wDjLBVHbHWisZA3nMcsQeQD1opWYXPz31zlnOMZrybUx", 615 | "+/YEDNeua5j5upHvXkuqY81+K/GKfxn9t5s/3TP2C+A0kUn7Jfgh2IJ/s4Djvgmtz4tagtj+zF47", 616 | "uIQcjRZ0XHcshUD9a4z9nOfzf2PPBrZU7bdl+mGNWv2grqeD9krxWtuN1xOkUEYzgEtKo/lX3M7O", 617 | "nbyP5ext1Xn6s+APB2lMbBfkZWMSb0JyEIXGBXb39jcW3ha7uLWEz3HkkpETjeccCqfgu2nTRZWu", 618 | "UjSctlvLzjpivRCsX2eFZQWJO1Ttzg47+lfXKKjgvdXQ+CqSbxd5O+pzHhT+0JtBsJZw6X4twZER", 619 | "ztXPfNdsuu6nap5Vlql9HM7hQ3nMAPXvV61gjjtwioqLtxwKsR2VqXLqis2euMmunDUEqSUtzlr1", 620 | "5Oq5RZOfGvjGzlDweIb/AADjaz7gfzrbtvib48S0i2axHcZb5vPtk6deuKxp7Vbiy8kFUZjhT6VT", 621 | "khktGZFQugjyxA4Hat5YShJO8UZxxuIhJWm7Hbf8Lp8VQpqCPHotx5MZKgREsMDOSAefpXiPjCPx", 622 | "z8RrK2uNU8aamLK5jEq2CRCGBFPbYpGfxya3pfCNobfUbtYngv7lAplRiSvHQen+Na+m2a2umWNu", 623 | "DMzR2ygyO2Tjpk+/FeBgqc5VnFx5bdvU9zHYpeyTjJv19P8AM+eZvg3rKI7R6pZ4AyPMjYZ/LOKq", 624 | "Wfwe16S4R9Qv7OGzxu3QEux/A4Ar6sjtA0ocu0ik/MGOcii7tkTRp9oC4jwpFeg8G2/iZ50cU7ar", 625 | "U8t8N+CvDmhvmO3Q3actNMCzn8e34V6XatpipCBdwDzuEGQN2fTP0rHWNZbXe6cnjcOuCDmuP8Qa", 626 | "tH4f0fSb2WO6ngScRyzFOApyAc+pPH41w4iMcL70tV3PQwd8W1Fb32+X+Z7dC1uMCJ4A4AyGPOKs", 627 | "S6TpeqoGvrDTL5SODNCsn8wa4j4KfH/SdG8fa/F4q09/Ef26ZViCwqzjA8tSxYY2qOAoxj8av+Lf", 628 | "HmmQfEWz0uOaw0qx1C+aXTrZdMCrDGCSFkcE7sHjAGDmuGpmj9mpxpc8fVfr3PUjk0faOnOpyStf", 629 | "r+h31na2lpZLb2sFva26/cjhiEaj8BxV5p1CkAqCBwx5NfLPjH4ox6P4mlfS75r9ZIRGq2TmBIpM", 630 | "/eMbDjoBjiofDHxH8R69oV0sF6jasjny4ZdpULkfMeOw7d6dHO6cmoJcr7afoTWyOpSg6jd49/1P", 631 | "qz7QSEDvkkdQKaJ2L5DZI46dM18w3Pxc1vSNUNvqNrDcRq6mSS1iLqF2kkEjGGyOhruNC+JR1e7m", 632 | "X7KsCR2qTlpAysFbsfy/WrjmFOc1BS1Mp5dUhT52tO57IWYvlWXk8g8Y/wA+9RGbAbLqD/eboBXn", 633 | "N/42bTtEe+nsHZF5CRZc9cdAOeuasN4t0+5t4t9vOI3IZWMYbnGRkV6NLmV7K7PNqShu3ZHcugEp", 634 | "kU5LfeYHGfxorih4ks5mKPdTrjBG6IhT/n+tFUpye0RWhHeR8Z64MBvX3ryTVCPMOOuT2r17XgQH", 635 | "9+9eQ6vxKcnvX43B2kf2xmaTps/UD9mvUGP7IfhxQAfLeVMk4/iNX/jpqH2j4KCyOGE99EDg+mT/", 636 | "AEryP9nbxJa2/wCzlY6dJLGZUuJMoX5AJz0rp/i1dpPoOiwxsGWS6Z8ZzwFP+NffYOHNKDt2P5Yz", 637 | "98s63q/zPLNDg8rT0B5JbJrrYlyOfwrn9PI2Kq447DtXRwjgdq+6aTp2Pzp6TNBDhR1xUw4iYRkK", 638 | "Sc1y+veKdF8M6Kb3VbllUEhUhjMjsR2wOn41zdv8WfB8+l/aory9dB/rALVsx8Z+Y4xUvGYen7sp", 639 | "JP1RtTwGJqR54wbXex67ArZy+DzlfaryqChyARnvXN+HvEGk+I9G+36Pdfa7bcV37SvIOD1rpl+6", 640 | "K2U1ON07o55U5U58slZjJ1YpGqjJLc8+1MihVJCcckBcegFTk5HXvTPMG4DPTnOK5aNLlm2aVZtx", 641 | "SCJj8+ehY4Aqvds4jiAVpQ8gUAJkKPU+g96sRMPKzz04p55SrqwlJaMUZxWtjFSz+dFkB8pYiOD3", 642 | "5rhfiRHYW3wqkS5tZLi3M8YVVlKtnqMGvTyAFPY1l6jZRXsEUUgJAlVyPcEH+lcGcUZTwzj5HZlN", 643 | "dUcRGZy3hzQ9PtYoZItJSwkjtogAWDFML93gc9eSe9UvFvhaHWfE3he9dXk+zXwWTamT5ZDEj/vr", 644 | "HNehgbd2MDc3p1pWj3CPPZs1y/2VBYRU7dV+ZvHMqixLqX6M+fPHfguyuntmsri1069UyNI9wDvn", 645 | "DMBnjrjtXQeBvhw+g+I9T1H7a0UE9oIUt1ycE9SxP0zj1JrtNa0VNW1q28xUFvA6bhtzuG7cR+YH", 646 | "NdfbII4SehLGvEoZRF49ztZJntYjOqn1GNLmvda/eeMz6UbjQ9RWSd4pk1oDzIlx5nlQhc89iTWv", 647 | "JYzW+oXmyadIBHFBvB5LmQfkBnFadxp9x/wha28MQ+1XOrea/bCG4Usf++RWtqlncmNY42LxfbrY", 648 | "4/uqH3MffOBWUsMm+brZfqaRxaSUel3+hcs7OSezjeaQMd7Dp1G4gY/CmTWcax3M1v8AvjGxBBP3", 649 | "cDPHrWhYM8WkRedG5ZIw7ELySc8YqCySWO41Mzbgsk2UO7OQR6fjX1tCcl7NLqtfkrnzFanBqpJ9", 650 | "Hp82YwB1CynNsy+TjYCVwVOfm5659KKW1t7g21sw3xp86uHOCSrZH8qK46vNNp8z+R006ip6LT8T", 651 | "5u18ZRjzxXi+t/K7Yx617ZrwOxgR0rxTXRzJxj0r8j6n9sY/WmbPgLxJqGn60kUNzIkK87B0r6i0", 652 | "7XIfFVxG2q6l9nlt4wsUbjG0H618e+CgX8YRAAnPB5969I+JNlPbxWN5bSvbu427ozgnjua+2y3F", 653 | "OlTjKWqR/M3E+H9riqsFpqfRGjW13ea3qM2kBb/TIm8sSFwhaQH5gAevXrW7DHrmpXt9pulWy2l3", 654 | "b4Wa4uMFIWIBAAB+ZsHOO2ea+EPDvifxBp91PbWWuXttNuL7RLw3vivpX4eahrGqeEr+5/tzULbU", 655 | "1vd11O7hkkDLxhccEY619FTx6+rJyk7PqlsfFVMA/rDUUrroz2G58G+Hr/UbLw7rK202pTo821jk", 656 | "g/xSbQeBn14rwbxd4g0zTvEth8PvAukRW3hu3vnhv78APcX8yj5yT12A/nj04r1J7Pxrp73N3pus", 657 | "2kl3foC11JEm75VIAU/3R1+teOPBc/CHxVaa9qtra6iuoQ8mM7pFYNli2R1Oc8V5V8FJtQVpS0u1", 658 | "0+Z2xji6aXM24rou57z8N/DFr4Y8JfLPLNcXKKXDrjy+pwB2HP516YJBtHOK+dz8XtD8R3trb6pJ", 659 | "qeg6ZGnmM1r8rSycbQxHO0DJx61ryeOvDcevaDDp/jK7ms7i5P2tLgKdsaqTjJGQScDP1r6HATp4", 660 | "WgqUVdLqeNi6dXFV3Um9We5Fht68fSmDv3Nc3rPjXw1F4dWbSHgv9RuZFhs7f7SFUuxwCx7KOp9h", 661 | "WI3iLWrLSru5vdKtruO0TNzJZ3YIXjJADDmut4ulCfLJ2ORYStKN0j0IYEQHenFsJ1qSysri40W1", 662 | "uZCkEk0YdoWbJQnnaSOM1xEvjXw6mtXdm+oqvkSmJpmjYRuw4O1sYbByMiup1Yaa7nNGjUbasdaX", 663 | "zk9aG5ZfrnpWfp+oWWqpI2m3MN8sWPM8lg23Pr6VbmJQjzAUz0BGKnEWlEdJSi9hxP4c1NnEY9TV", 664 | "LeCR2qdpMxgcEVUvhSIi3djWUGZQPXNWv+WePaqwJLr9OKkL5JHtXLCCUmzZydkVEiXYj/xAjnPv", 665 | "mkEgmnlA5RJMc9yBk/lmrIxsX1zTFCqpAGASScDqTXLHDppo2lVe5MuNh9cU3A3nvkk0g5BHTjrm", 666 | "lLjdkV6EadmjlcrohkiU7Ceg7Djt/wDXoqQsC4Pv0orGOHijd1G+p8n68vyuc4PPNeKa6hO/8a9t", 667 | "1w5ifjJPYGvGdcU5YdOtfiMlqf3JipXpGP4KlEfji3BbHPrz1r2v4kRGTw7o8ikn72TjpxXz1pL3", 668 | "lr4tiuba3eeKEgzhRnAJr6E1h38TaFplnaI0V0gJaKb5OCPU9a+nw0k8LbqfztxBG2YTfmfL2syy", 669 | "6ZrUF7Dw6PyM9R6V6N4c+KKaTiSw1bUdHeQASGMlQ3scda6K7+Gkrpm8USE8hRnafxqI+DLmDwnq", 670 | "Wnx6db3Hmr+6jkiBAbGN27qMe1b0MyqYaDSjddjwa+DhWd3oz2fSfHfxAutCt7+Kc6xYSx/u5ZbO", 671 | "OcFfTOMiue8ZeLr7xToEGk6vY6TZyWLGTzY7do5BkHgjP9K89+H6eOfh/fgBTf6RKf39nuOM/wB5", 672 | "c9DXSeO73R/F9n9qksNU0PXIFzBdom4MR/C+OormlnlX2/LLDpxf2luvVHNLLml/Edux57JeQhTG", 673 | "LhJJsYEKEbs9s4yR+OKpK011emG3ht1nCHCyv97jrkcgD61V0jRZLpZIb2b+zBu3ExxZLNntjr+N", 674 | "akfg6/1Se6GnmG6gizjfIqO+OuFyDk+ldGLzKdvflZfcdFDB0YaxiFnpl1Yi3a+ujcNK+WeNjhT2", 675 | "x/jXZ7NURZrWPU75YHQZTziQw9K57wJf6Tq+tS+Ftdv5tEuFfZam5gG1iD9wnPBr2DxB4P1DQtB/", 676 | "tOA/2tDAPnjt0/ebMdQO/wBBV4XPMJTmqNepaXRvqn57HBmGDqv36SujCt/GXjmzgMMetTPGFwod", 677 | "c1Npvj/xJp+hWelXNjp2pWdqAI1liweO5PrzXLeH9e03xNry6fpc2++KnbBL+7ZiOwz39q6680HV", 678 | "LSJnutNuoY0BJcxnA/GvoquKotqMqiv2bWx4yp1YfY/A6rwx8X5tAW/STw9HGt1dtcSC36cgDB+g", 679 | "FN1P4n6R4o+IkEviBdYs9GtbTNqlu5XE5Y7mbaecKAB+NeeAQyDcrRuOxUg002sT87Vx612Sc+VQ", 680 | "VrIwjNKTm1qz0HTvHVjF8Q9Jjj8QXz6Q0xe4DyMWCqCQhBHQnAzXt2sfEPwdaeC7/VIJ7e5nhtmk", 681 | "WBXwWYDgfnXyStgkN4ZYtqtjacgEEHqKt2Zeze82QWkhuITFKJIgw2n0HY+9c8KmIhFr4r7a7HVK", 682 | "WHquLlpbstz6U0++8YJpcDzyeGtVkdAzfZ5mQLkZxnn1rsfDs8usaHJd3wt7GYTvEsUcvmA7SVJz", 683 | "9Qa+PLi5nOk2Frbh7NrWMqs0ExV5PTee+KrWWq+JtNYR2us3caklsFs5JOTXRRxDuue6+4569KD+", 684 | "Fr8j6+8R+ItN8OataWlw9xdTTwtM32aEuIUBxuc/wgngeuD6VWh8V6RK0aedNE8jBFEkLKSxOAOn", 685 | "c18wWvjDxjY67dail+k881ukEizRhgyIzMOPqx/Otqb4r+Kp7a2E1lYMYruO43ouCShyB+eK0+sS", 686 | "jJ2tbzvcz9hTaXc+s2sr1eDbyE+3NUFuI2mdA6F0JVwD0IOCD714UPj/AK22mCN9MMUgx864fNcv", 687 | "ofxE8O2Ph+wXUdAuJdUYGTUJzcODLMxyz8dckk10yxiUlHR3/rqYwwl4uVz6ekmjhUNNLHGrNhSz", 688 | "AZJ7D1PtRXhln8WvDmn6/LdWVlJcRtFGqRXcpcIcsWZd3Q8qM9sUVDxTb0S08xywaVrtnVXfwW8V", 689 | "X120bXOmwIfuNuZifwArGf8AZh1W6uc6l4htkXdgpaxEkj1yf8K+3HRWmAKjHBqCcmNwE+UZ9K/N", 690 | "3llO5+z1+Nc1qQac7L0R86+EvgP4d8M222O3N1c7t0lxOAWfv06Cu7ufh9okoUzWcTYXglATXdSX", 691 | "U/2vHmcY9BUMjs/3jnj0rpVCMVZHzNSpOpJyk7s8ovfAeiwMHWB22j5VycA1hT+DFnuN1tH5Y6/M", 692 | "gx/9evXjFG7Hcu7HqTTyqow2gDgVjKirk81jwi+8F3cYBkitZI/UEg/1rmLnwbHkn7KSpPpkV9JX", 693 | "aI7YZQwyODXG6iAZymBsHQDpWMqTjsy1JPofO1z4Z0tLrZc2aCTPoePfIrMm8DaBJcNJbQCGZwQz", 694 | "Cc7jn69K+i1sLN43d7eNnx1I5rOudNsCcm1iJC9xXJNyb1NUo9j5xufhfYXWHECs2clsBm+ua14t", 695 | "P8S6ZCtvZaxcNHGNqRypuAHpzXpGoWduisUj8skfwMR/KuO1G5uLXT90E0qNnruz/OpqVY1bQqRU", 696 | "vVXGsLG107HiniD4b3Op+KzrKN/Z+olgzSWi+WC397A6H6V67oXijxXpfh2LT9esH1x0XYLlcIzr", 697 | "j+MdCfeuh0W4lu9L33DLK4ONxUZ/lXQ3VtAqAiJQSuTSxeFw2IjGnUgmlsZexlB+7I+S/HXhiO51", 698 | "v+3fBlvqej3nm7rmxziLd13Ic4HPbpXqvgLxlZ6lYppXj3w/aWOpRrhdREAEc/8AvEfdb9K9QMEJ", 699 | "fYYoyp6gr1qhcaZp8s4ElpCwaQAgr7GtKuGUsOqXPJW2alqjD2XvbL7jzX4g6BcravrXw/1PTbxE", 700 | "XM+lM6sSB/FGc5/4DXDfDrxVoHiXUzo/ii7n0DWGfbBKAPJlP905+634817Ne6PplrqMdxb2kUU0", 701 | "Th42XPBHOa8T13TbB9SiZrWEs05LHbyTya7sJPHQw3svrDfZtK69d7nHWw1BSvKmtT2XxF4BvtN8", 702 | "Py32lynWXiG820aBZGXvt5wT7V4pp/i3SNU8Rw6YJJLG/ebywl4vlhX/ALrE8A/Wu20vxNr1nFFb", 703 | "2+qXSQoNqISGAA4xzmvK/jBDDLr2l6m0MQv7mBjcTIgUyEEYJxgE+9Y5bnmZUqzw+Ikpt7O1noZS", 704 | "y7Czs4po9rm8IeIYEJfS7h1xw0WHB/I1yZMUd5JbSMiTxth42OGU+hFd18Ata1XVPhHImo3094LW", 705 | "58qAyHJRMD5c9SPrXEftI6ZY21lous29skGpzTGOa4jJVnUDIBx1+vWjL+Nq0sx+qV6a7XX+TJq5", 706 | "BDk5oS+8aIkORhevFRPbRFsFV61pfs7Tya14U1uw1by9QtbaVfISeNWKbuuCRn9a7/4paVp2jfDx", 707 | "tQ0u0hsrwXCL5kY7E8jHSvapcUUJ4z6o6bve1+hxVsoqU4cymeSvYw5UlAcNRUNhczT226V97cck", 708 | "CivqJ00meTzNH//Z", 709 | "--0016e6d99d0572dfaf047eb9ac2e--",//partend, then complete 710 | "" 711 | ].join("\r\n") 712 | }); 713 | 714 | 715 | exports.aSimpleMessage = { 716 | headers: { 717 | "Content-Type": "multipart/form-data; boundary=AaB03x", 718 | }, 719 | boundary : "AaB03x", 720 | parts: [ { 721 | part: { 722 | "type": "form-data", "name": "test" 723 | } 724 | , body: "hello" 725 | , encoded: [ 726 | "--AaB03x" 727 | , "content-disposition: form-data; name=\"test\"" 728 | , "" 729 | , "hello" 730 | ].join(",") 731 | } 732 | , { 733 | part: { 734 | "type": "form-data", "name": "test1" 735 | } 736 | , body: "hello1" 737 | , encoded: [ 738 | "--AaB03x" 739 | , "content-disposition: form-data; name=\"test1\"" 740 | , "" 741 | , "hello1" 742 | ].join(",") 743 | } 744 | ] 745 | }; 746 | --------------------------------------------------------------------------------