Add delay-response{} action
authorFabian Keil <fk@fabiankeil.de>
Tue, 9 Oct 2018 12:53:59 +0000 (14:53 +0200)
committerFabian Keil <fk@fabiankeil.de>
Mon, 12 Nov 2018 15:57:43 +0000 (16:57 +0100)
This is useful to tar pit JavaScript requests that
are endlessly retried in case of blocks.

Sponsored by: Robert Klemme

actionlist.h
configure.in
jbsockets.c
jbsockets.h
jcc.c
miscutil.c
miscutil.h
parsers.c
parsers.h
project.h
templates/edit-actions-for-url

index 4011dd7..01d4d37 100644 (file)
@@ -69,6 +69,8 @@ DEFINE_ACTION_STRING     ("crunch-server-header",       ACTION_CRUNCH_SERVER_HEA
 DEFINE_CGI_PARAM_NO_RADIO("crunch-server-header",       ACTION_CRUNCH_SERVER_HEADER, ACTION_STRING_SERVER_HEADER,          "X-Whatever:")
 DEFINE_ACTION_STRING     ("deanimate-gifs",             ACTION_DEANIMATE,       ACTION_STRING_DEANIMATE)
 DEFINE_CGI_PARAM_RADIO   ("deanimate-gifs",             ACTION_DEANIMATE,       ACTION_STRING_DEANIMATE,     "first", 0)
+DEFINE_ACTION_STRING     ("delay-response",             ACTION_DELAY_RESPONSE,  ACTION_STRING_DELAY_RESPONSE)
+DEFINE_CGI_PARAM_NO_RADIO("delay-response",             ACTION_DELAY_RESPONSE,  ACTION_STRING_DELAY_RESPONSE, "100")
 DEFINE_CGI_PARAM_RADIO   ("deanimate-gifs",             ACTION_DEANIMATE,       ACTION_STRING_DEANIMATE,     "last",  1)
 DEFINE_ACTION_BOOL       ("downgrade-http-version",     ACTION_DOWNGRADE)
 #ifdef FEATURE_EXTERNAL_FILTERS
index d476c4f..3324150 100644 (file)
@@ -781,6 +781,7 @@ AC_CHECK_FUNCS([ \
  memchr \
  memmove \
  memset \
+ nanosleep \
  poll \
  putenv \
  random \
index 1fe56e1..625d830 100644 (file)
@@ -673,6 +673,60 @@ int write_socket(jb_socket fd, const char *buf, size_t len)
 }
 
 
+/*********************************************************************
+ *
+ * Function    :  write_socket_delayed
+ *
+ * Description :  Write the contents of buf (for n bytes) to
+ *                socket fd, optionally delaying the operation.
+ *
+ * Parameters  :
+ *          1  :  fd = File descriptor (aka. handle) of socket to write to.
+ *          2  :  buf = Pointer to data to be written.
+ *          3  :  len = Length of data to be written to the socket "fd".
+ *          4  :  delay = Delay in milliseconds.
+ *
+ * Returns     :  0 on success (entire buffer sent).
+ *                nonzero on error.
+ *
+ *********************************************************************/
+int write_socket_delayed(jb_socket fd, const char *buf, size_t len, unsigned int delay)
+{
+   size_t i = 0;
+
+   if (delay == 0)
+   {
+      return write_socket(fd, buf, len);
+   }
+
+   while (i < len)
+   {
+      size_t write_length;
+      enum {MAX_WRITE_LENGTH = 10};
+
+      if ((i + MAX_WRITE_LENGTH) > len)
+      {
+         write_length = len - i;
+      }
+      else
+      {
+         write_length = MAX_WRITE_LENGTH;
+      }
+
+      privoxy_millisleep(delay);
+
+      if (write_socket(fd, buf + i, write_length) != 0)
+      {
+         return 1;
+      }
+      i += write_length;
+   }
+
+   return 0;
+
+}
+
+
 /*********************************************************************
  *
  * Function    :  read_socket
index b7480d3..2a7756a 100644 (file)
@@ -43,6 +43,7 @@ struct client_state;
 
 extern jb_socket connect_to(const char *host, int portnum, struct client_state *csp);
 extern int write_socket(jb_socket fd, const char *buf, size_t n);
+extern int write_socket_delayed(jb_socket fd, const char *buf, size_t len, unsigned int delay);
 extern int read_socket(jb_socket fd, char *buf, int n);
 extern int data_is_available(jb_socket fd, int seconds_to_wait);
 extern void close_socket(jb_socket fd);
diff --git a/jcc.c b/jcc.c
index ba0ecea..92c9431 100644 (file)
--- a/jcc.c
+++ b/jcc.c
@@ -385,6 +385,42 @@ static void sig_handler(int the_signal)
 #endif
 
 
+/*********************************************************************
+ *
+ * Function    :  get_write_delay
+ *
+ * Description :  Parse the delay-response parameter.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  Number of milliseconds to delay writes.
+ *
+ *********************************************************************/
+static unsigned int get_write_delay(const struct client_state *csp)
+{
+   unsigned int delay;
+   char *endptr;
+   char *newval;
+
+   if ((csp->action->flags & ACTION_DELAY_RESPONSE) == 0)
+   {
+      return 0;
+   }
+   newval = csp->action->string[ACTION_STRING_DELAY_RESPONSE];
+
+   delay = (unsigned)strtol(newval, &endptr, 0);
+   if (*endptr != '\0')
+   {
+      log_error(LOG_LEVEL_FATAL,
+         "Invalid delay-response{} parameter: '%s'", newval);
+   }
+
+   return delay;
+
+}
+
+
 /*********************************************************************
  *
  * Function    :  client_protocol_is_unsupported
@@ -437,7 +473,8 @@ static int client_protocol_is_unsupported(const struct client_state *csp, char *
       log_error(LOG_LEVEL_CLF,
          "%s - - [%T] \"%s\" 400 0", csp->ip_addr_str, req);
       freez(req);
-      write_socket(csp->cfd, response, strlen(response));
+      write_socket_delayed(csp->cfd, response, strlen(response),
+         get_write_delay(csp));
 
       return TRUE;
    }
@@ -469,8 +506,10 @@ static int client_has_unsupported_expectations(const struct client_state *csp)
          csp->ip_addr_str);
       log_error(LOG_LEVEL_CLF,
          "%s - - [%T] \"%s\" 417 0", csp->ip_addr_str, csp->http->cmd);
-      write_socket(csp->cfd, UNSUPPORTED_CLIENT_EXPECTATION_ERROR_RESPONSE,
-         strlen(UNSUPPORTED_CLIENT_EXPECTATION_ERROR_RESPONSE));
+      write_socket_delayed(csp->cfd,
+         UNSUPPORTED_CLIENT_EXPECTATION_ERROR_RESPONSE,
+         strlen(UNSUPPORTED_CLIENT_EXPECTATION_ERROR_RESPONSE),
+         get_write_delay(csp));
 
       return TRUE;
    }
@@ -520,7 +559,8 @@ static jb_err get_request_destination_elsewhere(struct client_state *csp, struct
       log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 400 0",
          csp->ip_addr_str, csp->http->cmd);
 
-      write_socket(csp->cfd, CHEADER, strlen(CHEADER));
+      write_socket_delayed(csp->cfd, CHEADER, strlen(CHEADER),
+         get_write_delay(csp));
       destroy_list(headers);
 
       return JB_ERR_PARSE;
@@ -548,7 +588,8 @@ static jb_err get_request_destination_elsewhere(struct client_state *csp, struct
          csp->ip_addr_str, csp->http->cmd, req);
       freez(req);
 
-      write_socket(csp->cfd, MISSING_DESTINATION_RESPONSE, strlen(MISSING_DESTINATION_RESPONSE));
+      write_socket_delayed(csp->cfd, MISSING_DESTINATION_RESPONSE,
+         strlen(MISSING_DESTINATION_RESPONSE), get_write_delay(csp));
       destroy_list(headers);
 
       return JB_ERR_PARSE;
@@ -793,8 +834,8 @@ static void send_crunch_response(const struct client_state *csp, struct http_res
          csp->ip_addr_str, http->ocmd, status_code, rsp->content_length);
 
       /* Write the answer to the client */
-      if (write_socket(csp->cfd, rsp->head, rsp->head_length)
-       || write_socket(csp->cfd, rsp->body, rsp->content_length))
+      if (write_socket_delayed(csp->cfd, rsp->head, rsp->head_length, get_write_delay(csp))
+       || write_socket_delayed(csp->cfd, rsp->body, rsp->content_length, get_write_delay(csp)))
       {
          /* There is nothing we can do about it. */
          log_error(LOG_LEVEL_ERROR,
@@ -1295,8 +1336,9 @@ static char *get_request_line(struct client_state *csp)
             log_error(LOG_LEVEL_CONNECT,
                "No request line on socket %d received in time. Timeout: %d.",
                csp->cfd, csp->config->socket_timeout);
-            write_socket(csp->cfd, CLIENT_CONNECTION_TIMEOUT_RESPONSE,
-               strlen(CLIENT_CONNECTION_TIMEOUT_RESPONSE));
+            write_socket_delayed(csp->cfd, CLIENT_CONNECTION_TIMEOUT_RESPONSE,
+               strlen(CLIENT_CONNECTION_TIMEOUT_RESPONSE),
+               get_write_delay(csp));
          }
          else
          {
@@ -1458,8 +1500,8 @@ static jb_err receive_chunked_client_request_body(struct client_state *csp)
    }
    if (status != CHUNK_STATUS_BODY_COMPLETE)
    {
-      write_socket(csp->cfd, CLIENT_BODY_PARSE_ERROR_RESPONSE,
-         strlen(CLIENT_BODY_PARSE_ERROR_RESPONSE));
+      write_socket_delayed(csp->cfd, CLIENT_BODY_PARSE_ERROR_RESPONSE,
+         strlen(CLIENT_BODY_PARSE_ERROR_RESPONSE), get_write_delay(csp));
       log_error(LOG_LEVEL_CLF,
          "%s - - [%T] \"Failed reading chunked client body\" 400 0", csp->ip_addr_str);
       return JB_ERR_PARSE;
@@ -1667,7 +1709,8 @@ static jb_err receive_client_request(struct client_state *csp)
    freez(req);
    if (JB_ERR_OK != err)
    {
-      write_socket(csp->cfd, CHEADER, strlen(CHEADER));
+      write_socket_delayed(csp->cfd, CHEADER, strlen(CHEADER),
+         get_write_delay(csp));
       /* XXX: Use correct size */
       log_error(LOG_LEVEL_CLF, "%s - - [%T] \"Invalid request\" 400 0", csp->ip_addr_str);
       log_error(LOG_LEVEL_ERROR,
@@ -1857,7 +1900,7 @@ static jb_err parse_client_request(struct client_state *csp)
          csp->ip_addr_str);
       log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 400 0",
          csp->ip_addr_str, csp->http->cmd);
-      write_socket(csp->cfd, CHEADER, strlen(CHEADER));
+      write_socket_delayed(csp->cfd, CHEADER, strlen(CHEADER), get_write_delay(csp));
       return JB_ERR_PARSE;
    }
    csp->flags |= CSP_FLAG_CLIENT_HEADER_PARSING_DONE;
@@ -1870,7 +1913,8 @@ static jb_err parse_client_request(struct client_state *csp)
       /*
        * A header filter broke the request line - bail out.
        */
-      write_socket(csp->cfd, MESSED_UP_REQUEST_RESPONSE, strlen(MESSED_UP_REQUEST_RESPONSE));
+      write_socket_delayed(csp->cfd, MESSED_UP_REQUEST_RESPONSE,
+         strlen(MESSED_UP_REQUEST_RESPONSE), get_write_delay(csp));
       /* XXX: Use correct size */
       log_error(LOG_LEVEL_CLF,
          "%s - - [%T] \"Invalid request generated\" 500 0", csp->ip_addr_str);
@@ -1930,7 +1974,7 @@ static int send_http_request(struct client_state *csp)
          csp->http->hostport);
    }
    else if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) == 0)
-      && (flush_socket(csp->server_connection.sfd, csp->client_iob) < 0))
+      && (flush_socket(csp->server_connection.sfd, csp->client_iob, 0) < 0))
    {
       write_failure = 1;
       log_error(LOG_LEVEL_CONNECT, "Failed sending request body to: %s: %E",
@@ -1973,6 +2017,7 @@ static void handle_established_connection(struct client_state *csp)
    struct http_request *http;
    long len = 0; /* for buffer sizes (and negative error codes) */
    int buffer_and_filter_content = 0;
+   unsigned int write_delay;
 
    /* Skeleton for HTTP response, if we should intercept the request */
    struct http_response *rsp;
@@ -2007,6 +2052,7 @@ static void handle_established_connection(struct client_state *csp)
 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
    watch_client_socket = 0 == (csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING);
 #endif
+   write_delay = get_write_delay(csp);
 
    for (;;)
    {
@@ -2404,9 +2450,10 @@ static void handle_established_connection(struct client_state *csp)
                      log_error(LOG_LEVEL_FATAL, "Out of memory parsing server header");
                   }
 
-                  if (write_socket(csp->cfd, hdr, strlen(hdr))
-                   || write_socket(csp->cfd,
-                         ((p != NULL) ? p : csp->iob->cur), (size_t)csp->content_length))
+                  if (write_socket_delayed(csp->cfd, hdr, strlen(hdr), write_delay)
+                   || write_socket_delayed(csp->cfd,
+                      ((p != NULL) ? p : csp->iob->cur),
+                      (size_t)csp->content_length, write_delay))
                   {
                      log_error(LOG_LEVEL_ERROR, "write modified content to client failed: %E");
                      freez(hdr);
@@ -2474,9 +2521,11 @@ static void handle_established_connection(struct client_state *csp)
                   }
                   hdrlen = strlen(hdr);
 
-                  if (write_socket(csp->cfd, hdr, hdrlen)
-                   || ((flushed = flush_socket(csp->cfd, csp->iob)) < 0)
-                   || (write_socket(csp->cfd, csp->receive_buffer, (size_t)len)))
+                  if (write_socket_delayed(csp->cfd, hdr, hdrlen, write_delay)
+                   || ((flushed = flush_socket(csp->cfd, csp->iob,
+                         write_delay) < 0)
+                   || (write_socket_delayed(csp->cfd, csp->receive_buffer,
+                         (size_t)len, write_delay))))
                   {
                      log_error(LOG_LEVEL_CONNECT,
                         "Flush header and buffers to client failed: %E");
@@ -2498,7 +2547,8 @@ static void handle_established_connection(struct client_state *csp)
             }
             else
             {
-               if (write_socket(csp->cfd, csp->receive_buffer, (size_t)len))
+               if (write_socket_delayed(csp->cfd, csp->receive_buffer,
+                     (size_t)len, write_delay))
                {
                   log_error(LOG_LEVEL_ERROR, "write to client failed: %E");
                   mark_server_socket_tainted(csp);
@@ -2538,8 +2588,9 @@ static void handle_established_connection(struct client_state *csp)
                      "Applying the MS IIS5 hack didn't help.");
                   log_error(LOG_LEVEL_CLF,
                      "%s - - [%T] \"%s\" 502 0", csp->ip_addr_str, http->cmd);
-                  write_socket(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE,
-                     strlen(INVALID_SERVER_HEADERS_RESPONSE));
+                  write_socket_delayed(csp->cfd,
+                     INVALID_SERVER_HEADERS_RESPONSE,
+                     strlen(INVALID_SERVER_HEADERS_RESPONSE), write_delay);
                   mark_server_socket_tainted(csp);
                   return;
                }
@@ -2607,8 +2658,8 @@ static void handle_established_connection(struct client_state *csp)
                   csp->headers->first->str);
                log_error(LOG_LEVEL_CLF,
                   "%s - - [%T] \"%s\" 502 0", csp->ip_addr_str, http->cmd);
-               write_socket(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE,
-                  strlen(INVALID_SERVER_HEADERS_RESPONSE));
+               write_socket_delayed(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE,
+                  strlen(INVALID_SERVER_HEADERS_RESPONSE), write_delay);
                free_http_request(http);
                mark_server_socket_tainted(csp);
                return;
@@ -2622,8 +2673,8 @@ static void handle_established_connection(struct client_state *csp)
             {
                log_error(LOG_LEVEL_CLF,
                   "%s - - [%T] \"%s\" 502 0", csp->ip_addr_str, http->cmd);
-               write_socket(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE,
-                  strlen(INVALID_SERVER_HEADERS_RESPONSE));
+               write_socket_delayed(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE,
+                  strlen(INVALID_SERVER_HEADERS_RESPONSE), write_delay);
                free_http_request(http);
                mark_server_socket_tainted(csp);
                return;
@@ -2679,8 +2730,8 @@ static void handle_established_connection(struct client_state *csp)
                 * may be in the buffer)
                 */
 
-               if (write_socket(csp->cfd, hdr, strlen(hdr))
-                || ((len = flush_socket(csp->cfd, csp->iob)) < 0))
+               if (write_socket_delayed(csp->cfd, hdr, strlen(hdr), write_delay)
+                  || ((len = flush_socket(csp->cfd, csp->iob, write_delay)) < 0))
                {
                   log_error(LOG_LEVEL_CONNECT, "write header to client failed: %E");
 
@@ -2711,8 +2762,8 @@ static void handle_established_connection(struct client_state *csp)
                   "Applying the MS IIS5 hack didn't help.");
                log_error(LOG_LEVEL_CLF,
                   "%s - - [%T] \"%s\" 502 0", csp->ip_addr_str, http->cmd);
-               write_socket(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE,
-                  strlen(INVALID_SERVER_HEADERS_RESPONSE));
+               write_socket_delayed(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE,
+                  strlen(INVALID_SERVER_HEADERS_RESPONSE), write_delay);
                mark_server_socket_tainted(csp);
                return;
             }
@@ -2990,7 +3041,8 @@ static void chat(struct client_state *csp)
        * message to the client, flush the rest, and get out of the way.
        */
       list_remove_all(csp->headers);
-      if (write_socket(csp->cfd, CSUCCEED, strlen(CSUCCEED)))
+      if (write_socket_delayed(csp->cfd, CSUCCEED,
+            strlen(CSUCCEED), get_write_delay(csp)))
       {
          return;
       }
@@ -4395,8 +4447,8 @@ static void listen_loop(void)
          log_error(LOG_LEVEL_CONNECT,
             "Rejecting connection from %s. Maximum number of connections reached.",
             csp->ip_addr_str);
-         write_socket(csp->cfd, TOO_MANY_CONNECTIONS_RESPONSE,
-            strlen(TOO_MANY_CONNECTIONS_RESPONSE));
+         write_socket_delayed(csp->cfd, TOO_MANY_CONNECTIONS_RESPONSE,
+            strlen(TOO_MANY_CONNECTIONS_RESPONSE), get_write_delay(csp));
          close_socket(csp->cfd);
          freez(csp->ip_addr_str);
          freez(csp->listen_addr_str);
@@ -4548,8 +4600,8 @@ static void listen_loop(void)
             log_error(LOG_LEVEL_ERROR,
                "Unable to take any additional connections: %E. Active threads: %d",
                active_threads);
-            write_socket(csp->cfd, TOO_MANY_CONNECTIONS_RESPONSE,
-               strlen(TOO_MANY_CONNECTIONS_RESPONSE));
+            write_socket_delayed(csp->cfd, TOO_MANY_CONNECTIONS_RESPONSE,
+               strlen(TOO_MANY_CONNECTIONS_RESPONSE), get_write_delay(csp));
             close_socket(csp->cfd);
             csp->flags &= ~CSP_FLAG_ACTIVE;
          }
index fb8938b..649eecf 100644 (file)
@@ -815,6 +815,45 @@ size_t privoxy_strlcat(char *destination, const char *source, const size_t size)
 #endif /* ndef HAVE_STRLCAT */
 
 
+/*********************************************************************
+ *
+ * Function    :  privoxy_millisleep
+ *
+ * Description :  Sleep a number of milliseconds
+ *
+ * Parameters  :
+ *          1  :  delay: Number of milliseconds to sleep
+ *
+ * Returns     :  -1 on error, 0 otherwise
+ *
+ *********************************************************************/
+int privoxy_millisleep(unsigned milliseconds)
+{
+#ifdef HAVE_NANOSLEEP
+   struct timespec rqtp = {0};
+   struct timespec rmtp = {0};
+
+   rqtp.tv_sec = milliseconds / 1000;
+   rqtp.tv_nsec = (milliseconds % 1000) * 1000 * 1000;
+
+   return nanosleep(&rqtp, &rmtp);
+#elif defined (_WIN32)
+   Sleep(milliseconds);
+
+   return 0;
+#elif defined(__OS2__)
+   DosSleep(milliseconds * 10);
+
+   return 0;
+#else
+#warning Missing privoxy_milisleep() implementation. delay-response{} will not work.
+
+   return -1;
+#endif /* def HAVE_NANOSLEEP */
+
+}
+
+
 #if !defined(HAVE_TIMEGM) && defined(HAVE_TZSET) && defined(HAVE_PUTENV)
 /*********************************************************************
  *
index e7c150a..008b762 100644 (file)
@@ -91,6 +91,8 @@ size_t privoxy_strlcat(char *destination, const char *source, size_t size);
 #define strlcat privoxy_strlcat
 #endif /* ndef HAVE_STRLCAT */
 
+extern int privoxy_millisleep(unsigned milliseconds);
+
 #if defined(__cplusplus)
 }
 #endif
index c1d4d42..f313f4f 100644 (file)
--- a/parsers.c
+++ b/parsers.c
@@ -256,6 +256,7 @@ static const add_header_func_ptr add_server_headers[] = {
  * Parameters  :
  *          1  :  fd = file descriptor of the socket to read
  *          2  :  iob = The I/O buffer to flush, usually csp->iob.
+ *          3  :  delay = Number of milliseconds to delay the writes
  *
  * Returns     :  On success, the number of bytes written are returned (zero
  *                indicates nothing was written).  On error, -1 is returned,
@@ -265,7 +266,7 @@ static const add_header_func_ptr add_server_headers[] = {
  *                file, the results are not portable.
  *
  *********************************************************************/
-long flush_socket(jb_socket fd, struct iob *iob)
+long flush_socket(jb_socket fd, struct iob *iob, unsigned int delay)
 {
    long len = iob->eod - iob->cur;
 
@@ -274,7 +275,7 @@ long flush_socket(jb_socket fd, struct iob *iob)
       return(0);
    }
 
-   if (write_socket(fd, iob->cur, (size_t)len))
+   if (write_socket_delayed(fd, iob->cur, (size_t)len, delay))
    {
       return(-1);
    }
index 00de286..64bd2cf 100644 (file)
--- a/parsers.h
+++ b/parsers.h
@@ -49,7 +49,7 @@
 #define FILTER_CLIENT_HEADERS 0
 #define FILTER_SERVER_HEADERS 1
 
-extern long flush_socket(jb_socket fd, struct iob *iob);
+extern long flush_socket(jb_socket fd, struct iob *iob, unsigned int delay);
 extern jb_err add_to_iob(struct iob *iob, const size_t buffer_limit, char *src, long n);
 extern void clear_iob(struct iob *iob);
 extern jb_err decompress_iob(struct client_state *csp);
index a283772..779c5c7 100644 (file)
--- a/project.h
+++ b/project.h
@@ -501,6 +501,8 @@ struct iob
 #define ACTION_HIDE_ACCEPT_LANGUAGE                  0x04000000UL
 /** Action bitmap: Limit the cookie lifetime */
 #define ACTION_LIMIT_COOKIE_LIFETIME                 0x08000000UL
+/** Action bitmap: Delay writes */
+#define ACTION_DELAY_RESPONSE                        0x10000000UL
 
 
 /** Action string index: How to deanimate GIFs */
@@ -541,8 +543,10 @@ struct iob
 #define ACTION_STRING_CHANGE_X_FORWARDED_FOR 17
 /** Action string index: how many minutes cookies should be valid. */
 #define ACTION_STRING_LIMIT_COOKIE_LIFETIME 18
+/** Action string index: how many milliseconds writes should be delayed. */
+#define ACTION_STRING_DELAY_RESPONSE       19
 /** Number of string actions. */
-#define ACTION_STRING_COUNT                19
+#define ACTION_STRING_COUNT                20
 
 
 /* To make the ugly hack in sed easier to understand */
index 2d84af8..5a10639 100644 (file)
@@ -515,6 +515,28 @@ function show_limit_connect_opts(tf)
         id="deanimate_last" @deanimate-gifs-param-last@><label
         for="deanimate_last">last frame</label></td>
     </tr>
+    <tr class="bg1" align="left" valign="top">
+      <td class="en1" align="center" valign="middle"><input type="radio"
+        name="delay_response" value="Y" @delay-response-y@
+        ></td>
+      <td class="dis1" align="center" valign="middle"><input type="radio"
+        name="delay_response" value="N" @delay-response-n@
+        ></td>
+      <td class="noc1" align="center" valign="middle"><input type="radio"
+        name="delay_response" value="X" @delay-response-x@
+        ></td>
+      <td class="action"><a href="@user-manual@@actions-help-prefix@DELAY-RESPONSE">delay-response</a></td>
+      <td>Send the response in ca. 10 byte chunks and delay each chunk.</td>
+    </tr>
+    <tr class="bg1" align="left" valign="top" id="delay-response_opts">
+      <td class="en1">&nbsp;</td>
+      <td class="dis1">&nbsp;</td>
+      <td class="noc1">&nbsp;</td>
+      <td>&nbsp;</td>
+      <td>Number of milliseconds to delay chunks:<br>
+        <input type="text" name="delay_response_mode" size="40" value="@delay-response-param@">
+      </td>
+    </tr>
     <tr class="bg1" align="left" valign="top">
       <td class="en1" align="center" valign="middle"><input type="radio"
         name="downgrade_http_version" value="Y" @downgrade-http-version-y@></td>