Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Bug 484027 - Add a method providing minimally controlled arbitrary wr…

…ite access to the connection within a response, allowing arbitrary information (even data which is not a syntactically valid HTTP response) to be sent in responses. r=sayrer

--HG--
extra : rebase_source : 2d61ccc
  • Loading branch information...
commit fa4f06df47999afa853c745ebc288e53a3429cbd 1 parent e666178
jswalden authored May 28, 2009
157  netwerk/test/httpserver/httpd.js
@@ -3332,6 +3332,13 @@ function Response(connection)
3332 3332
    * to this may be made.
3333 3333
    */
3334 3334
   this._finished = false;
  3335
+
  3336
+  /**
  3337
+   * True iff powerSeized() has been called on this, signaling that this
  3338
+   * response is to be handled manually by the response handler (which may then
  3339
+   * send arbitrary data in response, even non-HTTP responses).
  3340
+   */
  3341
+  this._powerSeized = false;
3335 3342
 }
3336 3343
 Response.prototype =
3337 3344
 {
@@ -3351,7 +3358,7 @@ Response.prototype =
3351 3358
                           null);
3352 3359
       this._bodyOutputStream = pipe.outputStream;
3353 3360
       this._bodyInputStream = pipe.inputStream;
3354  
-      if (this._processAsync)
  3361
+      if (this._processAsync || this._powerSeized)
3355 3362
         this._startAsyncProcessor();
3356 3363
     }
3357 3364
 
@@ -3375,7 +3382,7 @@ Response.prototype =
3375 3382
   //
3376 3383
   setStatusLine: function(httpVersion, code, description)
3377 3384
   {
3378  
-    if (!this._headers || this._finished)
  3385
+    if (!this._headers || this._finished || this._powerSeized)
3379 3386
       throw Cr.NS_ERROR_NOT_AVAILABLE;
3380 3387
     this._ensureAlive();
3381 3388
 
@@ -3420,7 +3427,7 @@ Response.prototype =
3420 3427
   //
3421 3428
   setHeader: function(name, value, merge)
3422 3429
   {
3423  
-    if (!this._headers || this._finished)
  3430
+    if (!this._headers || this._finished || this._powerSeized)
3424 3431
       throw Cr.NS_ERROR_NOT_AVAILABLE;
3425 3432
     this._ensureAlive();
3426 3433
 
@@ -3434,8 +3441,11 @@ Response.prototype =
3434 3441
   {
3435 3442
     if (this._finished)
3436 3443
       throw Cr.NS_ERROR_UNEXPECTED;
  3444
+    if (this._powerSeized)
  3445
+      throw Cr.NS_ERROR_NOT_AVAILABLE;
3437 3446
     if (this._processAsync)
3438 3447
       return;
  3448
+    this._ensureAlive();
3439 3449
 
3440 3450
     dumpn("*** processing connection " + this._connection.number + " async");
3441 3451
     this._processAsync = true;
@@ -3458,22 +3468,59 @@ Response.prototype =
3458 3468
   },
3459 3469
 
3460 3470
   //
  3471
+  // see nsIHttpResponse.seizePower
  3472
+  //
  3473
+  seizePower: function()
  3474
+  {
  3475
+    if (this._processAsync)
  3476
+      throw Cr.NS_ERROR_NOT_AVAILABLE;
  3477
+    if (this._finished)
  3478
+      throw Cr.NS_ERROR_UNEXPECTED;
  3479
+    if (this._powerSeized)
  3480
+      return;
  3481
+    this._ensureAlive();
  3482
+
  3483
+    dumpn("*** forcefully seizing power over connection " +
  3484
+          this._connection.number + "...");
  3485
+
  3486
+    // Purge any already-written data without sending it.  We could as easily
  3487
+    // swap out the streams entirely, but that makes it possible to acquire and
  3488
+    // unknowingly use a stale reference, so we require there only be one of
  3489
+    // each stream ever for any response to avoid this complication.
  3490
+    if (this._asyncCopier)
  3491
+      this._asyncCopier.cancel(Cr.NS_BINDING_ABORTED);
  3492
+    this._asyncCopier = null;
  3493
+    if (this._bodyOutputStream)
  3494
+    {
  3495
+      var input = new BinaryInputStream(this._bodyInputStream);
  3496
+      var avail;
  3497
+      while ((avail = input.available()) > 0)
  3498
+        input.readByteArray(avail);
  3499
+    }
  3500
+
  3501
+    this._powerSeized = true;
  3502
+    if (this._bodyOutputStream)
  3503
+      this._startAsyncProcessor();
  3504
+  },
  3505
+
  3506
+  //
3461 3507
   // see nsIHttpResponse.finish
3462 3508
   //
3463 3509
   finish: function()
3464 3510
   {
3465  
-    if (!this._processAsync)
  3511
+    if (!this._processAsync && !this._powerSeized)
3466 3512
       throw Cr.NS_ERROR_UNEXPECTED;
3467 3513
     if (this._finished)
3468 3514
       return;
3469 3515
 
3470  
-    dumpn("*** finishing async connection " + this._connection.number);
  3516
+    dumpn("*** finishing connection " + this._connection.number);
3471 3517
     this._startAsyncProcessor(); // in case bodyOutputStream was never accessed
3472 3518
     if (this._bodyOutputStream)
3473 3519
       this._bodyOutputStream.close();
3474 3520
     this._finished = true;
3475 3521
   },
3476 3522
 
  3523
+
3477 3524
   // POST-CONSTRUCTION API (not exposed externally)
3478 3525
 
3479 3526
   /**
@@ -3532,8 +3579,9 @@ Response.prototype =
3532 3579
 
3533 3580
   /**
3534 3581
    * Determines whether this response may be abandoned in favor of a newly
3535  
-   * constructed response, as determined by whether any of this response's data
3536  
-   * has been written to the network.
  3582
+   * constructed response.  A response may be abandoned only if it is not being
  3583
+   * sent asynchronously and if raw control over it has not been taken from the
  3584
+   * server.
3537 3585
    *
3538 3586
    * @returns boolean
3539 3587
    *   true iff no data has been written to the network
@@ -3541,7 +3589,7 @@ Response.prototype =
3541 3589
   partiallySent: function()
3542 3590
   {
3543 3591
     dumpn("*** partiallySent()");
3544  
-    return this._headers === null;
  3592
+    return this._processAsync || this._powerSeized;
3545 3593
   },
3546 3594
 
3547 3595
   /**
@@ -3551,8 +3599,12 @@ Response.prototype =
3551 3599
   complete: function()
3552 3600
   {
3553 3601
     dumpn("*** complete()");
3554  
-    if (this._processAsync)
  3602
+    if (this._processAsync || this._powerSeized)
  3603
+    {
  3604
+      NS_ASSERT(this._processAsync ^ this._powerSeized,
  3605
+                "can't both send async and relinquish power");
3555 3606
       return;
  3607
+    }
3556 3608
 
3557 3609
     NS_ASSERT(!this.partiallySent(), "completing a partially-sent response?");
3558 3610
 
@@ -3566,9 +3618,11 @@ Response.prototype =
3566 3618
   /**
3567 3619
    * Abruptly ends processing of this response, usually due to an error in an
3568 3620
    * incoming request but potentially due to a bad error handler.  Since we
3569  
-   * cannot handle the error in the usual way (giving an HTTP error page in response)
3570  
-   * because data may already have been sent, we stop processing this response
3571  
-   * and abruptly close the connection.
  3621
+   * cannot handle the error in the usual way (giving an HTTP error page in
  3622
+   * response) because data may already have been sent (or because the response
  3623
+   * might be expected to have been generated asynchronously or completely from
  3624
+   * scratch by the handler), we stop processing this response and abruptly
  3625
+   * close the connection.
3572 3626
    *
3573 3627
    * @param e : Error
3574 3628
    *   the exception which precipitated this abort, or null if no such exception
@@ -3579,11 +3633,34 @@ Response.prototype =
3579 3633
     dumpn("*** abort(<" + e + ">)");
3580 3634
 
3581 3635
     // This response will be ended by the processor if one was created.
3582  
-    var processor = this._asyncCopier;
3583  
-    if (processor)
3584  
-      processor.cancel(Cr.NS_BINDING_ABORTED);
  3636
+    var copier = this._asyncCopier;
  3637
+    if (copier)
  3638
+    {
  3639
+      // We dispatch asynchronously here so that any pending writes of data to
  3640
+      // the connection will be deterministically written.  This makes it easier
  3641
+      // to specify exact behavior, and it makes observable behavior more
  3642
+      // predictable for clients.  Note that the correctness of this depends on
  3643
+      // callbacks in response to _waitForData in WriteThroughCopier happening
  3644
+      // asynchronously with respect to the actual writing of data to
  3645
+      // bodyOutputStream, as they currently do; if they happened synchronously,
  3646
+      // an event which ran before this one could write more data to the
  3647
+      // response body before we get around to canceling the copier.  We have
  3648
+      // tests for this in test_seizepower.js, however, and I can't think of a
  3649
+      // way to handle both cases without removing bodyOutputStream access and
  3650
+      // moving its effective write(data, length) method onto Response, which
  3651
+      // would be slower and require more code than this anyway.
  3652
+      gThreadManager.currentThread.dispatch({
  3653
+        run: function()
  3654
+        {
  3655
+          dumpn("*** canceling copy asynchronously...");
  3656
+          copier.cancel(Cr.NS_ERROR_UNEXPECTED);
  3657
+        }
  3658
+      }, Ci.nsIThreadManager.DISPATCH_NORMAL);
  3659
+    }
3585 3660
     else
  3661
+    {
3586 3662
       this.end();
  3663
+    }
3587 3664
   },
3588 3665
 
3589 3666
   /**
@@ -3616,6 +3693,7 @@ Response.prototype =
3616 3693
     dumpn("*** _sendHeaders()");
3617 3694
 
3618 3695
     NS_ASSERT(this._headers);
  3696
+    NS_ASSERT(!this._powerSeized);
3619 3697
 
3620 3698
     // request-line
3621 3699
     var statusLine = "HTTP/" + this.httpVersion + " " +
@@ -3709,8 +3787,13 @@ Response.prototype =
3709 3787
 
3710 3788
     // Send headers if they haven't been sent already.
3711 3789
     if (this._headers)
3712  
-      this._sendHeaders();
3713  
-    NS_ASSERT(this._headers === null, "flushHeaders() failed?");
  3790
+    {
  3791
+      if (this._powerSeized)
  3792
+        this._headers = null;
  3793
+      else
  3794
+        this._sendHeaders();
  3795
+      NS_ASSERT(this._headers === null, "_sendHeaders() failed?");
  3796
+    }
3714 3797
 
3715 3798
     var response = this;
3716 3799
     var connection = this._connection;
@@ -3732,15 +3815,19 @@ Response.prototype =
3732 3815
 
3733 3816
         onStopRequest: function(request, cx, statusCode)
3734 3817
         {
3735  
-          dumpn("*** onStopRequest [status=" + statusCode.toString(16) + "]");
  3818
+          dumpn("*** onStopRequest [status=0x" + statusCode.toString(16) + "]");
3736 3819
 
3737  
-          if (!Components.isSuccessCode(statusCode))
  3820
+          if (statusCode === Cr.NS_BINDING_ABORTED)
3738 3821
           {
3739  
-            dumpn("*** WARNING: non-success statusCode in onStopRequest: " +
3740  
-                  statusCode);
  3822
+            dumpn("*** terminating copy observer without ending the response");
3741 3823
           }
  3824
+          else
  3825
+          {
  3826
+            if (!Components.isSuccessCode(statusCode))
  3827
+              dumpn("*** WARNING: non-success statusCode in onStopRequest");
3742 3828
 
3743  
-          response.end();
  3829
+            response.end();
  3830
+          }
3744 3831
         },
3745 3832
 
3746 3833
         QueryInterface: function(aIID)
@@ -3784,8 +3871,9 @@ function notImplemented()
3784 3871
  * @param input : nsIAsyncInputStream
3785 3872
  *   the stream from which data is to be read
3786 3873
  * @param output : nsIOutputStream
  3874
+ *   the stream to which data is to be copied
3787 3875
  * @param observer : nsIRequestObserver
3788  
- *   an observer which will be notified when
  3876
+ *   an observer which will be notified when the copy starts and finishes
3789 3877
  * @param context : nsISupports
3790 3878
  *   context passed to observer when notified of start/stop
3791 3879
  * @throws NS_ERROR_NULL_POINTER
@@ -3847,7 +3935,10 @@ WriteThroughCopier.prototype =
3847 3935
     dumpn("*** cancel(" + status.toString(16) + ")");
3848 3936
 
3849 3937
     if (this._completed)
  3938
+    {
  3939
+      dumpn("*** ignoring cancel on already-canceled copier...");
3850 3940
       return;
  3941
+    }
3851 3942
 
3852 3943
     this._completed = true;
3853 3944
     this.status = status;
@@ -3890,13 +3981,16 @@ WriteThroughCopier.prototype =
3890 3981
    * Receives a more-data-in-input notification and writes the corresponding
3891 3982
    * data to the output.
3892 3983
    */
3893  
-  onInputStreamReady: function()
  3984
+  onInputStreamReady: function(input)
3894 3985
   {
3895 3986
     dumpn("*** onInputStreamReady");
3896 3987
     if (this._completed)
  3988
+    {
  3989
+      dumpn("*** ignoring stream-ready callback on a canceled copier...");
3897 3990
       return;
  3991
+    }
3898 3992
 
3899  
-    var input = new BinaryInputStream(this._input);
  3993
+    input = new BinaryInputStream(input);
3900 3994
     try
3901 3995
     {
3902 3996
       var avail = input.available();
@@ -3931,6 +4025,19 @@ WriteThroughCopier.prototype =
3931 4025
   {
3932 4026
     dumpn("*** _waitForData");
3933 4027
     this._input.asyncWait(this, 0, 1, gThreadManager.mainThread);
  4028
+  },
  4029
+
  4030
+  /** nsISupports implementation */
  4031
+  QueryInterface: function(iid)
  4032
+  {
  4033
+    if (iid.equals(Ci.nsIRequest) ||
  4034
+        iid.equals(Ci.nsISupports) ||
  4035
+        iid.equals(Ci.nsIInputStreamCallback))
  4036
+    {
  4037
+      return this;
  4038
+    }
  4039
+
  4040
+    throw Cr.NS_ERROR_NO_INTERFACE;
3934 4041
   }
3935 4042
 };
3936 4043
 
78  netwerk/test/httpserver/nsIHttpServer.idl
@@ -365,9 +365,17 @@ interface nsIHttpRequestHandler : nsISupports
365 365
    * Processes the HTTP request represented by metadata and initializes the
366 366
    * passed-in response to reflect the correct HTTP response.
367 367
    *
368  
-   * Note that in some uses of nsIHttpRequestHandler, this method is required to
369  
-   * not throw an exception; in the general case, however, this method may throw
370  
-   * an exception (causing an HTTP 500 response to occur).
  368
+   * If this method throws an exception, externally observable behavior depends
  369
+   * upon whether is being processed asynchronously and the connection has had
  370
+   * any data written to it (even an explicit zero bytes of data being written)
  371
+   * or whether seizePower() has been called on it.  If such has happened, sent
  372
+   * data will be exactly that data written at the time the exception was
  373
+   * thrown.  If no data has been written, the response has not had seizePower()
  374
+   * called on it, and it is not being asynchronously created, an error handler
  375
+   * will be invoked (usually 500 unless otherwise specified).  Note that some
  376
+   * uses of nsIHttpRequestHandler may require this method to never throw an
  377
+   * exception; in the general case, however, this method may throw an exception
  378
+   * (causing an HTTP 500 response to occur).
371 379
    *
372 380
    * @param metadata
373 381
    *   data representing an HTTP request
@@ -504,7 +512,8 @@ interface nsIHttpResponse : nsISupports
504 512
    *   than 999, or description contains invalid characters
505 513
    * @throws NS_ERROR_NOT_AVAILABLE
506 514
    *   if this response is being processed asynchronously and data has been
507  
-   *   written to this response's body
  515
+   *   written to this response's body, or if seizePower() has been called on
  516
+   *   this
508 517
    */
509 518
   void setStatusLine(in string httpVersion,
510 519
                      in unsigned short statusCode,
@@ -530,23 +539,29 @@ interface nsIHttpResponse : nsISupports
530 539
    *   if name or value is not a valid header component
531 540
    * @throws NS_ERROR_NOT_AVAILABLE
532 541
    *   if this response is being processed asynchronously and data has been
533  
-   *   written to this response's body
  542
+   *   written to this response's body, or if seizePower() has been called on
  543
+   *   this
534 544
    */
535 545
   void setHeader(in string name, in string value, in boolean merge);
536 546
 
537 547
   /**
538  
-   * A stream to which data appearing in the body of this response should be
539  
-   * written.  After this response has been designated as being processed
540  
-   * asynchronously, subsequent writes will be synchronously written to the
541  
-   * underlying transport.  However, immediate write-through visible to the HTTP
542  
-   * client cannot be guaranteed, as intermediate buffers both in the server
543  
-   * socket and in the client may delay written data; be prepared for potential
544  
-   * delays.
  548
+   * A stream to which data appearing in the body of this response (or in the
  549
+   * totality of the response if seizePower() is called) should be written.
  550
+   * After this response has been designated as being processed asynchronously,
  551
+   * or after seizePower() has been called on this, subsequent writes will no
  552
+   * longer be buffered and will be written to the underlying transport without
  553
+   * delaying until the entire response is constructed.  Write-through may or
  554
+   * may not be synchronous in the implementation, and in any case particular
  555
+   * behavior may not be observable to the HTTP client as intermediate buffers
  556
+   * both in the server socket and in the client may delay written data; be
  557
+   * prepared for delays at any time.
545 558
    *
546 559
    * @note
547  
-   *   As writes to the underlying transport are synchronous, care must be taken
548  
-   *   not to block on these writes; it is even possible for deadlock to occur
549  
-   *   in the case that the server and the client reside in the same process.
  560
+   *   Although in the asynchronous cases writes to the underlying transport
  561
+   *   are not buffered, care must still be taken not to block for too long on
  562
+   *   any such writes; it is even possible for deadlock to occur in the case
  563
+   *   that the server and the client reside in the same process.  Write data in
  564
+   *   small chunks if necessary to avoid this problem.
550 565
    * @throws NS_ERROR_NOT_AVAILABLE
551 566
    *   if accessed after this response is fully constructed
552 567
    */
@@ -578,16 +593,43 @@ interface nsIHttpResponse : nsISupports
578 593
    * @throws NS_ERROR_UNEXPECTED
579 594
    *   if not initially called within a nsIHttpRequestHandler.handle call or if
580 595
    *   called after this response has been finished
  596
+   * @throws NS_ERROR_NOT_AVAILABLE
  597
+   *   if seizePower() has been called on this
581 598
    */
582 599
   void processAsync();
583 600
 
584 601
   /**
  602
+   * Seizes complete control of this response (and its connection) from the
  603
+   * server, allowing raw and unfettered access to data being sent in the HTTP
  604
+   * response.  Once this method has been called the only property which may be
  605
+   * accessed without an exception being thrown is bodyOutputStream, and the
  606
+   * only methods which may be accessed without an exception being thrown are
  607
+   * write(), finish(), and seizePower() (which may be called multiple times
  608
+   * without ill effect so long as all calls are otherwise allowed).
  609
+   *
  610
+   * After a successful call, all data subsequently written to the body of this
  611
+   * response is written directly to the corresponding connection.  (Previously-
  612
+   * written data is silently discarded.)  No status line or headers are sent
  613
+   * before doing so; if the response handler wishes to write such data, it must
  614
+   * do so manually.  Data generation completes only when finish() is called; it
  615
+   * is not enough to simply call close() on bodyOutputStream.
  616
+   *
  617
+   * @throws NS_ERROR_NOT_AVAILABLE
  618
+   *   if processAsync() has been called on this
  619
+   * @throws NS_ERROR_UNEXPECTED
  620
+   *   if finish() has been called on this
  621
+   */
  622
+  void seizePower();
  623
+
  624
+  /**
585 625
    * Signals that construction of this response is complete and that it may be
586  
-   * sent over the network to the client.  This method may only be called after
587  
-   * processAsync() has been called.  This method is idempotent.
  626
+   * sent over the network to the client, or if seizePower() has been called
  627
+   * signals that all data has been written and that the underlying connection
  628
+   * may be closed.  This method may only be called after processAsync() or
  629
+   * seizePower() has been called.  This method is idempotent.
588 630
    *
589 631
    * @throws NS_ERROR_UNEXPECTED
590  
-   *   if processAsync() has not already been properly called
  632
+   *   if processAsync() or seizePower() has not already been properly called
591 633
    */
592 634
   void finish();
593 635
 };
8  netwerk/test/httpserver/test/test_processasync.js
@@ -299,12 +299,8 @@ function stop_handleAsyncError(ch, cx, status, data)
299 299
   // Lies!  But not really!
300 300
   do_check_true(ch.requestSucceeded);
301 301
 
302  
-  // There's no way server APIs will ever guarantee exactly what data will show
303  
-  // up here, but they will guarantee sending a (not necessarily strict) prefix
304  
-  // of what was written.
305  
-  do_check_true(data.length <= ASYNC_ERROR_BODY.length);
306  
-  for (var i = 0, sz = data.length; i < sz; i++)
307  
-    do_check_eq(data[i] == ASYNC_ERROR_BODY.charCodeAt(i));
  302
+  do_check_eq(data.length, ASYNC_ERROR_BODY.length);
  303
+  do_check_eq(String.fromCharCode.apply(null, data), ASYNC_ERROR_BODY);
308 304
 }
309 305
 
310 306
 test = new Test(PREPATH + "/handleAsyncError",
309  netwerk/test/httpserver/test/test_seizepower.js
... ...
@@ -0,0 +1,309 @@
  1
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2
+/* vim:set ts=2 sw=2 sts=2 et: */
  3
+/* ***** BEGIN LICENSE BLOCK *****
  4
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  5
+ *
  6
+ * The contents of this file are subject to the Mozilla Public License Version
  7
+ * 1.1 (the "License"); you may not use this file except in compliance with
  8
+ * the License. You may obtain a copy of the License at
  9
+ * http://www.mozilla.org/MPL/
  10
+ *
  11
+ * Software distributed under the License is distributed on an "AS IS" basis,
  12
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  13
+ * for the specific language governing rights and limitations under the
  14
+ * License.
  15
+ *
  16
+ * The Original Code is httpd.js code.
  17
+ *
  18
+ * The Initial Developer of the Original Code is
  19
+ * the Mozilla Corporation.
  20
+ * Portions created by the Initial Developer are Copyright (C) 2009
  21
+ * the Initial Developer. All Rights Reserved.
  22
+ *
  23
+ * Contributor(s):
  24
+ *   Jeff Walden <jwalden+code@mit.edu> (original author)
  25
+ *
  26
+ * Alternatively, the contents of this file may be used under the terms of
  27
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
  28
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  29
+ * in which case the provisions of the GPL or the LGPL are applicable instead
  30
+ * of those above. If you wish to allow use of your version of this file only
  31
+ * under the terms of either the GPL or the LGPL, and not to allow others to
  32
+ * use your version of this file under the terms of the MPL, indicate your
  33
+ * decision by deleting the provisions above and replace them with the notice
  34
+ * and other provisions required by the GPL or the LGPL. If you do not delete
  35
+ * the provisions above, a recipient may use your version of this file under
  36
+ * the terms of any one of the MPL, the GPL or the LGPL.
  37
+ *
  38
+ * ***** END LICENSE BLOCK ***** */
  39
+
  40
+/*
  41
+ * Tests that the seizePower API works correctly.
  42
+ */
  43
+
  44
+const PORT = 4444;
  45
+
  46
+var srv;
  47
+
  48
+function run_test()
  49
+{
  50
+  srv = createServer();
  51
+
  52
+  srv.registerPathHandler("/raw-data", handleRawData);
  53
+  srv.registerPathHandler("/called-too-late", handleTooLate);
  54
+  srv.registerPathHandler("/exceptions", handleExceptions);
  55
+  srv.registerPathHandler("/async-seizure", handleAsyncSeizure);
  56
+  srv.registerPathHandler("/seize-after-async", handleSeizeAfterAsync);
  57
+  srv.registerPathHandler("/thrown-exception", handleThrownException);
  58
+  srv.registerPathHandler("/asap-later-write", handleASAPLaterWrite);
  59
+  srv.registerPathHandler("/asap-later-finish", handleASAPLaterFinish);
  60
+
  61
+  srv.start(PORT);
  62
+
  63
+  runRawTests(tests, testComplete(srv));
  64
+}
  65
+
  66
+
  67
+function checkException(fun, err, msg)
  68
+{
  69
+  try
  70
+  {
  71
+    fun();
  72
+  }
  73
+  catch (e)
  74
+  {
  75
+    if (e !== err && e.result !== err)
  76
+      do_throw(msg);
  77
+    return;
  78
+  }
  79
+  do_throw(msg);
  80
+}
  81
+
  82
+function callASAPLater(fun)
  83
+{
  84
+  gThreadManager.currentThread.dispatch({
  85
+    run: function()
  86
+    {
  87
+      fun();
  88
+    }
  89
+  }, Ci.nsIThreadManager.DISPATCH_NORMAL);
  90
+}
  91
+
  92
+
  93
+/*****************
  94
+ * PATH HANDLERS *
  95
+ *****************/
  96
+
  97
+function handleRawData(request, response)
  98
+{
  99
+  response.seizePower();
  100
+  response.write("Raw data!");
  101
+  response.finish();
  102
+}
  103
+
  104
+function handleTooLate(request, response)
  105
+{
  106
+  response.write("DO NOT WANT");
  107
+  var output = response.bodyOutputStream;
  108
+
  109
+  response.seizePower();
  110
+
  111
+  if (response.bodyOutputStream !== output)
  112
+    response.write("bodyOutputStream changed!");
  113
+  else
  114
+    response.write("too-late passed");
  115
+  response.finish();
  116
+}
  117
+
  118
+function handleExceptions(request, response)
  119
+{
  120
+  response.seizePower();
  121
+  checkException(function() { response.setStatusLine("1.0", 500, "ISE"); },
  122
+                 Cr.NS_ERROR_NOT_AVAILABLE,
  123
+                 "setStatusLine should throw not-available after seizePower");
  124
+  checkException(function() { response.setHeader("X-Fail", "FAIL", false); },
  125
+                 Cr.NS_ERROR_NOT_AVAILABLE,
  126
+                 "setHeader should throw not-available after seizePower");
  127
+  checkException(function() { response.processAsync(); },
  128
+                 Cr.NS_ERROR_NOT_AVAILABLE,
  129
+                 "processAsync should throw not-available after seizePower");
  130
+  var out = response.bodyOutputStream;
  131
+  var data = "exceptions test passed";
  132
+  out.write(data, data.length);
  133
+  response.seizePower(); // idempotency test of seizePower
  134
+  response.finish();
  135
+  response.finish(); // idempotency test of finish after seizePower
  136
+  checkException(function() { response.seizePower(); },
  137
+                 Cr.NS_ERROR_UNEXPECTED,
  138
+                 "seizePower should throw unexpected after finish");
  139
+}
  140
+
  141
+function handleAsyncSeizure(request, response)
  142
+{
  143
+  response.seizePower();
  144
+  callLater(1, function()
  145
+  {
  146
+    response.write("async seizure passed");
  147
+    response.bodyOutputStream.close();
  148
+    callLater(1, function()
  149
+    {
  150
+      response.finish();
  151
+    });
  152
+  });
  153
+}
  154
+
  155
+function handleSeizeAfterAsync(request, response)
  156
+{
  157
+  response.setStatusLine(request.httpVersion, 200, "async seizure pass");
  158
+  response.processAsync();
  159
+  checkException(function() { response.seizePower(); },
  160
+                 Cr.NS_ERROR_NOT_AVAILABLE,
  161
+                 "seizePower should throw not-available after processAsync");
  162
+  callLater(1, function()
  163
+  {
  164
+    response.finish();
  165
+  });
  166
+}
  167
+
  168
+function handleThrownException(request, response)
  169
+{
  170
+  if (request.queryString === "writeBefore")
  171
+    response.write("ignore this");
  172
+  else if (request.queryString === "writeBeforeEmpty")
  173
+    response.write("");
  174
+  else if (request.queryString !== "")
  175
+    throw "query string FAIL";
  176
+  response.seizePower();
  177
+  response.write("preparing to throw...");
  178
+  throw "badness 10000";
  179
+}
  180
+
  181
+function handleASAPLaterWrite(request, response)
  182
+{
  183
+  response.seizePower();
  184
+  response.write("should only ");
  185
+  response.write("see this");
  186
+
  187
+  callASAPLater(function()
  188
+  {
  189
+    response.write("...and not this");
  190
+    callASAPLater(function()
  191
+    {
  192
+      response.write("...or this");
  193
+      response.finish();
  194
+    });
  195
+  });
  196
+
  197
+  throw "opening pitch of the ballgame";
  198
+}
  199
+
  200
+function handleASAPLaterFinish(request, response)
  201
+{
  202
+  response.seizePower();
  203
+  response.write("should only see this");
  204
+
  205
+  callASAPLater(function()
  206
+  {
  207
+    response.finish();
  208
+  });
  209
+
  210
+  throw "out the bum!";
  211
+}
  212
+
  213
+
  214
+/***************
  215
+ * BEGIN TESTS *
  216
+ ***************/
  217
+
  218
+var test, data;
  219
+var tests = [];
  220
+
  221
+data = "GET /raw-data HTTP/1.0\r\n" +
  222
+       "\r\n";
  223
+function checkRawData(data)
  224
+{
  225
+  do_check_eq(data, "Raw data!");
  226
+}
  227
+test = new RawTest("localhost", PORT, data, checkRawData),
  228
+tests.push(test);
  229
+
  230
+data = "GET /called-too-late HTTP/1.0\r\n" +
  231
+       "\r\n";
  232
+function checkTooLate(data)
  233
+{
  234
+  do_check_eq(LineIterator(data).next(), "too-late passed");
  235
+}
  236
+test = new RawTest("localhost", PORT, data, checkTooLate),
  237
+tests.push(test);
  238
+
  239
+data = "GET /exceptions HTTP/1.0\r\n" +
  240
+       "\r\n";
  241
+function checkExceptions(data)
  242
+{
  243
+  do_check_eq("exceptions test passed", data);
  244
+}
  245
+test = new RawTest("localhost", PORT, data, checkExceptions),
  246
+tests.push(test);
  247
+
  248
+data = "GET /async-seizure HTTP/1.0\r\n" +
  249
+       "\r\n";
  250
+function checkAsyncSeizure(data)
  251
+{
  252
+  do_check_eq(data, "async seizure passed");
  253
+}
  254
+test = new RawTest("localhost", PORT, data, checkAsyncSeizure),
  255
+tests.push(test);
  256
+
  257
+data = "GET /seize-after-async HTTP/1.0\r\n" +
  258
+       "\r\n";
  259
+function checkSeizeAfterAsync(data)
  260
+{
  261
+  do_check_eq(LineIterator(data).next(), "HTTP/1.0 200 async seizure pass");
  262
+}
  263
+test = new RawTest("localhost", PORT, data, checkSeizeAfterAsync),
  264
+tests.push(test);
  265
+
  266
+data = "GET /thrown-exception?writeBefore HTTP/1.0\r\n" +
  267
+       "\r\n";
  268
+function checkThrownExceptionWriteBefore(data)
  269
+{
  270
+  do_check_eq(data, "preparing to throw...");
  271
+}
  272
+test = new RawTest("localhost", PORT, data, checkThrownExceptionWriteBefore),
  273
+tests.push(test);
  274
+
  275
+data = "GET /thrown-exception?writeBeforeEmpty HTTP/1.0\r\n" +
  276
+       "\r\n";
  277
+function checkThrownExceptionWriteBefore(data)
  278
+{
  279
+  do_check_eq(data, "preparing to throw...");
  280
+}
  281
+test = new RawTest("localhost", PORT, data, checkThrownExceptionWriteBefore),
  282
+tests.push(test);
  283
+
  284
+data = "GET /thrown-exception HTTP/1.0\r\n" +
  285
+       "\r\n";
  286
+function checkThrownException(data)
  287
+{
  288
+  do_check_eq(data, "preparing to throw...");
  289
+}
  290
+test = new RawTest("localhost", PORT, data, checkThrownException),
  291
+tests.push(test);
  292
+
  293
+data = "GET /asap-later-write HTTP/1.0\r\n" +
  294
+       "\r\n";
  295
+function checkASAPLaterWrite(data)
  296
+{
  297
+  do_check_eq(data, "should only see this");
  298
+}
  299
+test = new RawTest("localhost", PORT, data, checkASAPLaterWrite),
  300
+tests.push(test);
  301
+
  302
+data = "GET /asap-later-finish HTTP/1.0\r\n" +
  303
+       "\r\n";
  304
+function checkASAPLaterFinish(data)
  305
+{
  306
+  do_check_eq(data, "should only see this");
  307
+}
  308
+test = new RawTest("localhost", PORT, data, checkASAPLaterFinish),
  309
+tests.push(test);

0 notes on commit fa4f06d

Please sign in to comment.
Something went wrong with that request. Please try again.