selenium-webdriver 0.0.17 → 0.0.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/chrome/prebuilt/Win32/Release/npchromedriver.dll +0 -0
  2. data/chrome/prebuilt/x64/Release/npchromedriver.dll +0 -0
  3. data/chrome/src/extension/background.js +64 -48
  4. data/chrome/src/extension/content_script.js +253 -132
  5. data/chrome/src/extension/manifest-nonwin.json +1 -1
  6. data/chrome/src/extension/manifest-win.json +1 -1
  7. data/chrome/src/extension/utils.js +8 -8
  8. data/chrome/src/rb/lib/selenium/webdriver/chrome.rb +9 -0
  9. data/chrome/src/rb/lib/selenium/webdriver/chrome/bridge.rb +38 -280
  10. data/chrome/src/rb/lib/selenium/webdriver/chrome/command_executor.rb +119 -117
  11. data/chrome/src/rb/lib/selenium/webdriver/chrome/launcher.rb +36 -26
  12. data/common/src/js/abstractcommandprocessor.js +9 -11
  13. data/common/src/js/command.js +159 -83
  14. data/common/src/js/core/RemoteRunner.html +2 -2
  15. data/common/src/js/core/TestRunner-splash.html +3 -3
  16. data/common/src/js/core/TestRunner.html +5 -17
  17. data/common/src/js/core/scripts/htmlutils.js +4208 -2506
  18. data/common/src/js/core/scripts/selenium-api.js +2 -2
  19. data/common/src/js/core/scripts/selenium-browserbot.js +66 -58
  20. data/common/src/js/core/scripts/selenium-version.js +1 -1
  21. data/common/src/js/localcommandprocessor.js +5 -19
  22. data/common/src/js/testcase.js +2 -0
  23. data/common/src/js/webdriver.js +63 -93
  24. data/common/src/js/webelement.js +40 -42
  25. data/common/src/rb/lib/selenium/webdriver.rb +23 -14
  26. data/common/src/rb/lib/selenium/webdriver/bridge_helper.rb +8 -35
  27. data/common/src/rb/lib/selenium/webdriver/child_process.rb +2 -0
  28. data/common/src/rb/lib/selenium/webdriver/core_ext/dir.rb +1 -0
  29. data/common/src/rb/lib/selenium/webdriver/core_ext/string.rb +5 -0
  30. data/common/src/rb/lib/selenium/webdriver/driver.rb +20 -15
  31. data/common/src/rb/lib/selenium/webdriver/driver_extensions/takes_screenshot.rb +7 -2
  32. data/common/src/rb/lib/selenium/webdriver/element.rb +11 -2
  33. data/common/src/rb/lib/selenium/webdriver/error.rb +9 -5
  34. data/common/src/rb/lib/selenium/webdriver/keys.rb +1 -2
  35. data/common/src/rb/lib/selenium/webdriver/navigation.rb +16 -0
  36. data/common/src/rb/lib/selenium/webdriver/options.rb +32 -0
  37. data/common/src/rb/lib/selenium/webdriver/platform.rb +17 -1
  38. data/firefox/prebuilt/Win32/Release/webdriver-firefox.dll +0 -0
  39. data/firefox/src/extension/components/dispatcher.js +492 -0
  40. data/firefox/src/extension/components/driver-component.js +4 -1
  41. data/firefox/src/extension/components/errorcode.js +70 -0
  42. data/firefox/src/extension/components/firefoxDriver.js +173 -154
  43. data/firefox/src/extension/components/nsCommandProcessor.js +171 -132
  44. data/firefox/src/extension/components/promptService.js +5 -5
  45. data/firefox/src/extension/components/request.js +219 -0
  46. data/firefox/src/extension/components/response.js +276 -0
  47. data/firefox/src/extension/components/session.js +281 -0
  48. data/firefox/src/extension/components/sessionstore.js +226 -0
  49. data/firefox/src/extension/components/socketListener.js +350 -100
  50. data/firefox/src/extension/components/utils.js +166 -98
  51. data/firefox/src/extension/components/webdriverserver.js +9 -5
  52. data/firefox/src/extension/components/wrappedElement.js +189 -166
  53. data/firefox/src/extension/install.rdf +1 -1
  54. data/firefox/src/rb/lib/selenium/webdriver/firefox.rb +2 -0
  55. data/firefox/src/rb/lib/selenium/webdriver/firefox/binary.rb +39 -33
  56. data/firefox/src/rb/lib/selenium/webdriver/firefox/bridge.rb +7 -421
  57. data/firefox/src/rb/lib/selenium/webdriver/firefox/extension_connection.rb +7 -64
  58. data/firefox/src/rb/lib/selenium/webdriver/firefox/launcher.rb +2 -3
  59. data/firefox/src/rb/lib/selenium/webdriver/firefox/profile.rb +54 -10
  60. data/firefox/src/rb/lib/selenium/webdriver/firefox/profiles_ini.rb +2 -0
  61. data/firefox/src/rb/lib/selenium/webdriver/firefox/util.rb +6 -0
  62. data/jobbie/prebuilt/Win32/Release/InternetExplorerDriver.dll +0 -0
  63. data/jobbie/prebuilt/x64/Release/InternetExplorerDriver.dll +0 -0
  64. data/jobbie/src/rb/lib/selenium/webdriver/ie.rb +2 -0
  65. data/jobbie/src/rb/lib/selenium/webdriver/ie/bridge.rb +38 -13
  66. data/jobbie/src/rb/lib/selenium/webdriver/ie/lib.rb +9 -2
  67. data/jobbie/src/rb/lib/selenium/webdriver/ie/util.rb +5 -0
  68. data/remote/client/src/rb/lib/selenium/webdriver/remote.rb +2 -0
  69. data/remote/client/src/rb/lib/selenium/webdriver/remote/bridge.rb +42 -38
  70. data/remote/client/src/rb/lib/selenium/webdriver/remote/commands.rb +56 -47
  71. data/remote/client/src/rb/lib/selenium/webdriver/remote/default_http_client.rb +26 -26
  72. data/remote/client/src/rb/lib/selenium/webdriver/remote/patron_http_client.rb +58 -0
  73. data/remote/client/src/rb/lib/selenium/webdriver/remote/response.rb +10 -12
  74. data/remote/client/src/rb/lib/selenium/webdriver/remote/server_error.rb +2 -17
  75. metadata +44 -23
  76. data/common/src/js/context.js +0 -58
  77. data/firefox/src/extension/components/context.js +0 -37
@@ -25,41 +25,41 @@
25
25
 
26
26
  /**
27
27
  * Communicates with a client by reading and writing from a socket.
28
+ * @param {Dispatcher} dispatcher The instance to send all parsed requests to.
28
29
  * @param {nsISocketTransport} transport The connected socket transport.
29
30
  * @constructor
30
31
  * @extends {nsIStreamListener}
31
32
  */
32
- function SocketListener(transport) {
33
- this.outstream = transport.
34
- openOutputStream(Components.interfaces.nsITransport.OPEN_BLOCKING, 0, 0);
33
+ function SocketListener(dispatcher, transport) {
35
34
 
36
- this.stream = transport.openInputStream(0, 0, 0);
37
- var cin = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
38
- createInstance(Components.interfaces.nsIConverterInputStream);
39
- cin.init(this.stream, SocketListener.CHARSET, 0, 0x0000);
40
-
41
- this.inputStream = cin;
42
-
43
- var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"].
44
- createInstance(Components.interfaces.nsIInputStreamPump);
45
- pump.init(this.stream, -1, -1, 0, 0, false);
46
- pump.asyncRead(this, null);
35
+ /**
36
+ * The instance to send all parsed requests to.
37
+ * @type {Dispatcher}
38
+ * @private
39
+ */
40
+ this.dispatcher_ = dispatcher;
47
41
 
48
- this.linesLeft = "";
49
- this.data = "";
50
- this.command = "";
51
- this.step = 0;
52
- this.readLength = false;
42
+ /**
43
+ * Transport for the socket this instance will read/write to.
44
+ * @type {nsISocketTransport}
45
+ * @private
46
+ */
47
+ this.transport_ = transport;
53
48
 
54
49
  /**
55
- * A reference to the command processor service. We grab the reference here
56
- * instead of on the prototype since the component may not be loaded yet.
57
- * @type {nsICommandProcessor}
50
+ * Output stream for the socket transport.
51
+ * @type {nsIOutputStream}
58
52
  * @private
59
53
  */
60
- this.commandProcessor_ = Components.
61
- classes['@googlecode.com/webdriver/command-processor;1'].
62
- getService(Components.interfaces.nsICommandProcessor);
54
+ this.outputStream_ = transport.openOutputStream(
55
+ Components.interfaces.nsITransport.OPEN_BLOCKING,
56
+ /*segmentSize=*/0,
57
+ /*segmentCount=*/0);
58
+
59
+ var socketInputStream = transport.openInputStream(
60
+ /*flags=*/0,
61
+ /*segmentSize=*/0,
62
+ /*segmentCount=*/0).QueryInterface(Components.interfaces.nsIAsyncInputStream);
63
63
 
64
64
  /**
65
65
  * The converter used when writing data back to the socket.
@@ -71,115 +71,365 @@ function SocketListener(transport) {
71
71
  createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
72
72
 
73
73
  this.converter_.charset = SocketListener.CHARSET;
74
+
75
+ /**
76
+ * The HTTP request method.
77
+ * @type {?Request.Method}
78
+ * @private
79
+ */
80
+ this.method_ = null;
81
+
82
+ /**
83
+ * The HTTP request headers as a JSON object.
84
+ * @type {object}
85
+ * @private
86
+ */
87
+ this.headers_ = {};
88
+ this.requestUrl_ = '';
89
+ this.body_ = '';
90
+ this.contentLengthRemaining_ = 0;
91
+
92
+ /**
93
+ * The raw UTF-8 request data that has been read so far.
94
+ * @type {string}
95
+ * @private
96
+ */
97
+ this.rawData_ = '';
98
+
99
+ var threadManager = Components.classes['@mozilla.org/thread-manager;1'].
100
+ getService(Components.interfaces.nsIThreadManager);
101
+
102
+ /**
103
+ * A reference to the main thread.
104
+ * @type {nsIThread}
105
+ * @private
106
+ * @const
107
+ */
108
+ this.mainThread_ = threadManager.mainThread;
109
+
110
+ socketInputStream.asyncWait(this, 0, 0, this.mainThread_);
74
111
  }
75
112
 
76
113
 
114
+ /**
115
+ * Enumeration of states for a SocketListener.
116
+ * @enum {number}
117
+ */
118
+ SocketListener.State = {
119
+ READING_REQUEST_LINE: 0,
120
+ READING_HEADERS: 1,
121
+ READING_BODY: 2,
122
+ FINISHED: 3
123
+ };
124
+
125
+
77
126
  /**
78
127
  * Charset used for socket I/O.
79
128
  * @type {string}
129
+ * @const
80
130
  */
81
131
  SocketListener.CHARSET = 'UTF-8';
82
132
 
83
133
 
84
134
  /**
85
- * Signals the start of a request. Each request lasts for the life of the
86
- * underlying socket connection and represents a session with a FirefoxDriver
87
- * client.
88
- * @see {nsIRequestObserver#onStartRequest}
135
+ * HTTP message sent in response to requests with an Expect:100-continue header.
136
+ * @type {string}
137
+ * @const
138
+ */
139
+ SocketListener.CONTINUE_MESSAGE = 'HTTP/1.1 100 Continue\r\n\r\n';
140
+
141
+
142
+ /**
143
+ * This instance's current state.
144
+ * @type {SocketListener.State}
145
+ * @private
146
+ */
147
+ SocketListener.prototype.state_ = SocketListener.State.READING_REQUEST_LINE;
148
+
149
+
150
+ /**
151
+ * Whether this instance has sent a 100-continue resposne to the client.
152
+ * @type {boolean}
153
+ * @private
154
+ */
155
+ SocketListener.prototype.continueSent_ = false;
156
+
157
+
158
+ /**
159
+ * Called when the underlying input stream has additional data ready to be read.
160
+ * @param {nsIAsyncInputStream} inputStream The stream with data ready.
161
+ * @see {nsIInputStreamCallback#onInputStreamReady}
162
+ */
163
+ SocketListener.prototype.onInputStreamReady = function(inputStream) {
164
+ var binaryInputStream = Components.
165
+ classes['@mozilla.org/binaryinputstream;1'].
166
+ createInstance(Components.interfaces.nsIBinaryInputStream);
167
+ binaryInputStream.setInputStream(inputStream);
168
+
169
+ // Utils.dumpn('onInputStreamReady');
170
+ var available;
171
+ try {
172
+ available = inputStream.available();
173
+ } catch (ex) {
174
+ // TODO: check for ex.result == Components.results.NS_BASE_STREAM_CLOSED?
175
+ this.transport_.close(0);
176
+ return;
177
+ }
178
+
179
+ var byteArray = binaryInputStream.readByteArray(available);
180
+ var converted = this.converter_.convertFromByteArray(byteArray, available);
181
+ this.rawData_ += converted;
182
+
183
+ // Utils.dumpn('...converted:\n' + converted.replace(/\r\n/g, '\\r\\n\n'));
184
+ // Utils.dumpn('...raw data:\n' + this.rawData_);
185
+
186
+ try {
187
+ switch (this.state_) {
188
+ case SocketListener.State.READING_REQUEST_LINE:
189
+ this.readRequestLine_();
190
+ if (this.state_ != SocketListener.State.READING_HEADERS) {
191
+ break;
192
+ }
193
+ // Read the request line, fall through to start reading headers.
194
+
195
+ case SocketListener.State.READING_HEADERS:
196
+ this.readHeaders_();
197
+ if (this.state_ != SocketListener.State.READING_BODY) {
198
+ break;
199
+ }
200
+ // Read all of the headers, fall through to start reading the body.
201
+
202
+ case SocketListener.State.READING_BODY:
203
+ this.readBody_();
204
+ break;
205
+ }
206
+
207
+ if (this.state_ != SocketListener.State.FINISHED) {
208
+ // Utils.dumpn('Waiting for more data...');
209
+ inputStream.asyncWait(this, 0, 0, this.mainThread_);
210
+ } else {
211
+ inputStream.close();
212
+ var clientRequest = new Request(
213
+ this.method_, this.requestUrl_, this.headers_, this.rawData_);
214
+ var clientResponse = new Response(
215
+ clientRequest, this.outputStream_);
216
+ // Utils.dumpn('Dispatching request:\n' + clientRequest.toDebugString());
217
+ this.dispatcher_.dispatch(clientRequest, clientResponse);
218
+ }
219
+ } catch (ex) {
220
+ // Utils.dumpn('Sending error:\n\t' + ex.toString());
221
+ var status = ex.isBadRequest ? ex.status : Response.INTERNAL_ERROR;
222
+ var response = new Response(null, this.outputStream_);
223
+ response.sendError(status, ex.toString(), 'text/plain');
224
+ this.transport_.close(0);
225
+ }
226
+ };
227
+
228
+
229
+ /**
230
+ * Reads a CRLF terminated line from the data that has been read from the socket
231
+ * thus far.
232
+ * @return {?string} The read line, minus the terminating CRLF, or null if none
233
+ * are yet available.
89
234
  */
90
- SocketListener.prototype.onStartRequest = function(request, context) {
235
+ SocketListener.prototype.readLine_ = function() {
236
+ while (true) {
237
+ var crlf = this.rawData_.search('\r\n');
238
+ if (crlf == -1) {
239
+ return null; // Haven't read a full line yet.
240
+ }
241
+
242
+ // Strip out the line from the request data.
243
+ var line = this.rawData_.substring(0, crlf).
244
+ replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
245
+ this.rawData_ = this.rawData_.substring(crlf + '\r\n'.length);
246
+
247
+ return line;
248
+ }
91
249
  };
92
250
 
93
251
 
94
252
  /**
95
- * Signals the end of a request (e.g. the underlying socket connection was
96
- * closed).
97
- * @see {nsIRequestObserver#onStopRequest}
253
+ * Attempt to parse the HTTP request line with the data that has been read from
254
+ * the socket thus far.
255
+ * @private
98
256
  */
99
- SocketListener.prototype.onStopRequest = function(request, context, status) {
257
+ SocketListener.prototype.readRequestLine_ = function() {
258
+ // Utils.dumpn('Reading request line...');
259
+ // Read the first non-blank line in the request. We skip blank lines
260
+ // according to RFC 2616, section 4.1.
261
+ var line = this.readLine_();
262
+ while (line != null && line.length == 0) {
263
+ line = this.readLine();
264
+ }
265
+
266
+ if (null == line) {
267
+ return; // Don't have a line to parse yet.
268
+ }
269
+
270
+ var parts = line.split(/\s+/);
271
+ if (parts.length < 3) {
272
+ throw new SocketListener.BadRequest('Invalid request line.');
273
+ }
274
+
275
+ this.method_ = parts.shift().toUpperCase();
276
+ this.path_ = parts.shift();
277
+ this.protocol_ = parts.shift().toUpperCase();
278
+
279
+ // We only support HTTP/1.1 requests.
280
+ if (this.protocol_ != 'HTTP/1.1') {
281
+ throw new SocketListener.BadRequest(
282
+ 'Not an HTTP/1.1 request: <' + this.protocol_ + '>');
283
+ }
284
+
285
+ // Make sure we were given a valid HTTP method.
286
+ if (typeof Request.Method[this.method_] == 'undefined') {
287
+ throw new SocketListener.BadRequest(
288
+ 'Invalid HTTP method: <' + this.method_ + '>');
289
+ }
290
+ // Utils.dumpn('\t' + this.method_ + ' ' + this.path_ + ' ' + this.protocol_);
291
+ this.state_ = SocketListener.State.READING_HEADERS;
100
292
  };
101
293
 
102
294
 
103
295
  /**
104
- * Called whenever another chunk of data is ready to be read from the socket.
105
- * @param {nsIRequest} request The data's origin.
106
- * @param {nsISupports} context User defined context.
107
- * @param {nsIInputStream} inputStream The input stream containing the data
108
- * chunk.
109
- * @param {number} offset The total number of bytes read by previous calls to
110
- * {@code #onDataAvailable}.
111
- * @param {number} count The number of bytes available in the stream.
112
- * @see {nsIStreamListener#onDataAvailable}
296
+ * Read headers from the data that has been read from the socket.
297
+ * @private
113
298
  */
114
- SocketListener.prototype.onDataAvailable = function(request, context,
115
- inputStream, offset,
116
- count) {
117
- var incoming = {};
118
- var read = this.inputStream.readString(count, incoming);
119
-
120
- var lines = incoming.value.split('\n');
121
- for (var j = 0; j < lines.length; j++) {
122
- if (0 == this.step) {
123
- var head = lines[j].split(": ", 2);
124
- if (head[0] == "Content-Length") {
125
- this.linesLeft = Number(head[1]);
126
- this.readLength = true;
127
- } else if (lines[j].length == 0 && this.readLength) {
128
- this.step++;
299
+ SocketListener.prototype.readHeaders_ = function() {
300
+ // Utils.dumpn('Reading headers...');
301
+ while (true) {
302
+ var line = this.readLine_(false);
303
+ if (null == line) {
304
+ return; // No headers available yet.
305
+ }
306
+
307
+ if (!line.length) {
308
+ // Blank line, end of headers.
309
+
310
+ // Make sure the host was specified.
311
+ if (typeof this.headers_['host'] == 'undefined') {
312
+ throw new SocketListener.BadRequest('No "Host" header specified');
129
313
  }
130
- } else {
131
- this.data += lines[j];
132
- this.linesLeft -= read;
133
314
 
134
- if (this.linesLeft <= 0) {
135
- this.executeCommand_();
136
- j++; // Consume the empty line
315
+ // Check the content-length.
316
+ if (typeof this.headers_['content-length'] == 'undefined') {
317
+ if (this.method_ == Request.Method.POST ||
318
+ this.method_ == Request.Method.PUT) {
319
+ throw new SocketListener.BadRequest(
320
+ 'No "Content-Length" header specified for POST or PUT request',
321
+ '411 Length Required');
322
+ }
323
+ } else {
324
+ var contentLength = parseInt(this.headers_['content-length']);
325
+ if (isNaN(contentLength)) {
326
+ throw new SocketListener.BadRequest(
327
+ 'Content-Length header is not a number: ' +
328
+ this.headers_['content-length']);
329
+ }
330
+ this.headers_['content-length'] = contentLength;
137
331
  }
332
+
333
+ // Reconstitute the original request URL.
334
+ this.requestUrl_ = 'http://' + this.headers_['host'] + this.path_;
335
+ try {
336
+ this.requestUrl_ = Components.
337
+ classes["@mozilla.org/network/io-service;1"].
338
+ getService(Components.interfaces.nsIIOService).
339
+ newURI(this.requestUrl_, null, null).
340
+ QueryInterface(Components.interfaces.nsIURL);
341
+ } catch (ex) {
342
+ throw new SocketListener.BadRequest(
343
+ 'Error parsing request URL: ' + this.requestUrl_);
344
+ }
345
+
346
+ this.state_ = SocketListener.State.READING_BODY;
347
+ return;
138
348
  }
139
- }
140
349
 
141
- if (this.linesLeft <= 0 && this.data) {
142
- this.executeCommand_();
350
+ var parts = line.match(/([^:\s]*)\s*:\s*([^\s].*)/);
351
+ if (!parts) {
352
+ throw new SocketListener.BadRequest(
353
+ 'Error parsing header field <' + line + '>');
354
+ }
355
+ this.headers_[parts[1].toLowerCase()] = parts[2];
356
+ // Utils.dumpn('\t' + parts[1] + ':' + parts[2]);
143
357
  }
144
358
  };
145
359
 
146
360
 
147
361
  /**
148
- * Parses the command data read from the socket into a JSON object and
149
- * dispatches it to the command processesor.
362
+ * Read the request body from the data that has been read thus far.
150
363
  * @private
151
364
  */
152
- SocketListener.prototype.executeCommand_ = function() {
153
- var self = this;
154
- var command = this.data;
155
- var callback = function(response) {
156
- //Utils.dumpn('writing to socket:\n' + response);
157
- var data = self.converter_.convertToByteArray(response, {});
158
- var header = "Length: " + data.length + "\n\n";
159
- self.outstream.write(header, header.length);
160
- self.outstream.flush();
161
-
162
- var stream = self.converter_.convertToInputStream(response);
163
- self.outstream.writeFrom(stream, data.length);
164
- self.outstream.flush();
165
- stream.close();
166
- };
167
-
168
- // Clear data for the next read.
169
- this.data = '';
170
- this.linesLeft = 0;
171
- this.step = 0;
172
- this.readLength = 0;
365
+ SocketListener.prototype.readBody_ = function() {
366
+ // Utils.dumpn('Reading body...');
367
+ if (!this.continueSent_) {
368
+ // Need to send a 100 Continue if it is expected.
369
+ // For more info, see RFC 2616, section 8.2.3
370
+ if (this.headers_['expect'] == '100-continue') {
371
+ // Utils.dumpn('Sending 100-continue...');
372
+ this.continueSent_ = true;
373
+ var continueResp = new Response(null, this.outputStream_);
374
+ continueResp.setStatus(Response.CONTINUE);
375
+ continueResp.commit();
376
+ }
377
+ }
173
378
 
174
- try {
175
- this.commandProcessor_.execute(command, callback);
176
- } catch (e) {
177
- Utils.dump(e);
178
- Utils.dumpn(command);
179
-
180
- // Something has gone seriously wrong. Quit the browser.
181
- this.commandProcessor_.execute(
182
- JSON.stringify({'commandName': 'quit'}),
183
- function() {});
379
+ if (this.method_ != Request.Method.POST &&
380
+ this.method_ != Request.Method.PUT) {
381
+ // No body to read. Technically, if the client sends a body (as indicated
382
+ // by the Content-Length header, we should read it in, even if it's ignored
383
+ // for the request method. But this can lead to indefinite blocking with
384
+ // clients that specify a Content-Length on a GET request and then never
385
+ // send the body.
386
+ this.state_ = SocketListener.State.FINISHED;
387
+ return;
388
+ }
389
+
390
+ // This rigmarole is needed so we know the number of bytes read so we can tell
391
+ // if we've read the entire body.
392
+ var escaped = encodeURIComponent(this.rawData_);
393
+ var escapedCharCount = 0;
394
+ if (escaped.indexOf('%', 0) != -1) {
395
+ escapedCharCount = escaped.split('%').length - 1;
396
+ }
397
+ var bytesRead = escaped.length - (2 * escapedCharCount);
398
+ var bytesRemaining = this.headers_['content-length'] - bytesRead;
399
+
400
+ // If we read more data than the Content-Length header specified, then too
401
+ // much data was sent by the client, and we consider this a malformed
402
+ // request.
403
+ if (bytesRemaining < 0) {
404
+ throw new SocketListener.BadRequest(
405
+ 'Request body is longer than indicated "Content-Length" header;' +
406
+ ' expected <' + this.headers_['content-length'] +'>, ' +
407
+ ' but was <' + bytesRead + '>');
408
+ } else if (bytesRemaining == 0) {
409
+ // Utils.dumpn('Finished reading body!');
410
+ this.state_ = SocketListener.State.FINISHED;
411
+ // } else {
412
+ // Utils.dumpn(
413
+ // 'Read ' + bytesRead + '; ' + bytesRemaining + ' bytes remaining');
184
414
  }
185
415
  };
416
+
417
+
418
+ /**
419
+ * Thrown when a bad request is parsed.
420
+ * @param {string} message The error message to return to the client.
421
+ * @param {number} opt_status The HTTP status code to use when returning the
422
+ * error to the client. Defaults to 400.
423
+ * @constructor
424
+ */
425
+ SocketListener.BadRequest = function(message, opt_status) {
426
+ this.message = message;
427
+ this.status = opt_status || Response.BAD_REQUEST;
428
+ this.isBadRequest = true;
429
+ };
430
+
431
+
432
+ /** @override */
433
+ SocketListener.BadRequest.prototype.toString = function() {
434
+ return this.message;
435
+ };