Commit d00f2a8f authored by Jay Satiro's avatar Jay Satiro
Browse files

http_proxy: Fix proxy CONNECT hang on pending data

- Check for pending data before waiting on the socket.

Bug: https://github.com/curl/curl/issues/1156
Reported-by: Adam Langley
parent afb57f7b
Loading
Loading
Loading
Loading
+13 −0
Original line number Original line Diff line number Diff line
@@ -1404,3 +1404,16 @@ void Curl_conncontrol(struct connectdata *conn,
                                   should assign this bit */
                                   should assign this bit */
  }
  }
}
}

/* Data received can be cached at various levels, so check them all here. */
bool Curl_conn_data_pending(struct connectdata *conn, int sockindex)
{
  int readable;

  if(Curl_ssl_data_pending(conn, sockindex) ||
     Curl_recv_has_postponed_data(conn, sockindex))
    return true;

  readable = SOCKET_READABLE(conn->sock[sockindex], 0);
  return (readable > 0 && (readable & CURL_CSELECT_IN));
}
+2 −0
Original line number Original line Diff line number Diff line
@@ -142,4 +142,6 @@ void Curl_conncontrol(struct connectdata *conn,
#define connkeep(x,y) Curl_conncontrol(x, CONNCTRL_KEEP)
#define connkeep(x,y) Curl_conncontrol(x, CONNCTRL_KEEP)
#endif
#endif


bool Curl_conn_data_pending(struct connectdata *conn, int sockindex);

#endif /* HEADER_CURL_CONNECT_H */
#endif /* HEADER_CURL_CONNECT_H */
+1 −1
Original line number Original line Diff line number Diff line
@@ -740,7 +740,7 @@ CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */
       * wait for more data anyway.
       * wait for more data anyway.
       */
       */
    }
    }
    else if(!Curl_ssl_data_pending(conn, FIRSTSOCKET)) {
    else if(!Curl_conn_data_pending(conn, FIRSTSOCKET)) {
      switch(SOCKET_READABLE(sockfd, interval_ms)) {
      switch(SOCKET_READABLE(sockfd, interval_ms)) {
      case -1: /* select() error, stop reading */
      case -1: /* select() error, stop reading */
        failf(data, "FTP response aborted due to select/poll error: %d",
        failf(data, "FTP response aborted due to select/poll error: %d",
+229 −232
Original line number Original line Diff line number Diff line
@@ -285,7 +285,7 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
    }
    }


    if(!blocking) {
    if(!blocking) {
      if(0 == SOCKET_READABLE(tunnelsocket, 0))
      if(!Curl_conn_data_pending(conn, sockindex))
        /* return so we'll be called again polling-style */
        /* return so we'll be called again polling-style */
        return CURLE_OK;
        return CURLE_OK;
      else {
      else {
@@ -310,7 +310,16 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
      nread = 0;
      nread = 0;
      perline = 0;
      perline = 0;


      while((nread<BUFSIZE) && (keepon && !error)) {
      while(nread < BUFSIZE && keepon && !error) {
        int writetype;

        if(Curl_pgrsUpdate(conn))
          return CURLE_ABORTED_BY_CALLBACK;

        if(ptr >= &data->state.buffer[BUFSIZE]) {
          failf(data, "CONNECT response too large!");
          return CURLE_RECV_ERROR;
        }


        check = Curl_timeleft(data, NULL, TRUE);
        check = Curl_timeleft(data, NULL, TRUE);
        if(check <= 0) {
        if(check <= 0) {
@@ -319,26 +328,22 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
          break;
          break;
        }
        }


        /* loop every second at least, less if the timeout is near */
        /* Read one byte at a time to avoid a race condition. Wait at most one
        switch(SOCKET_READABLE(tunnelsocket, check<1000L?check:1000)) {
           second before looping to ensure continuous pgrsUpdates. */
        case -1: /* select() error, stop reading */
        result = Curl_read(conn, tunnelsocket, ptr, 1, &gotbytes);
        if(result == CURLE_AGAIN) {
          if(SOCKET_READABLE(tunnelsocket, check<1000L?check:1000) == -1) {
            error = SELECT_ERROR;
            error = SELECT_ERROR;
            failf(data, "Proxy CONNECT aborted due to select/poll error");
            failf(data, "Proxy CONNECT aborted due to select/poll error");
            break;
            break;
        case 0: /* timeout */
          break;
        default:
          if(ptr >= &data->state.buffer[BUFSIZE]) {
            failf(data, "CONNECT response too large!");
            return CURLE_RECV_ERROR;
          }
          }
          result = Curl_read(conn, tunnelsocket, ptr, 1, &gotbytes);
          continue;
          if(result==CURLE_AGAIN)
        }
            continue; /* go loop yourself */
        else if(result) {
          else if(result)
          keepon = FALSE;
          keepon = FALSE;
          break;
        }
        else if(gotbytes <= 0) {
        else if(gotbytes <= 0) {
            keepon = FALSE;
          if(data->set.proxyauth && data->state.authproxy.avail) {
          if(data->set.proxyauth && data->state.authproxy.avail) {
            /* proxy auth was requested and there was proxy auth available,
            /* proxy auth was requested and there was proxy auth available,
               then deem this as "mere" proxy disconnect */
               then deem this as "mere" proxy disconnect */
@@ -349,8 +354,10 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
            error = SELECT_ERROR;
            error = SELECT_ERROR;
            failf(data, "Proxy CONNECT aborted");
            failf(data, "Proxy CONNECT aborted");
          }
          }
          keepon = FALSE;
          break;
        }
        }
          else {

        /* We got a byte of data */
        /* We got a byte of data */
        nread++;
        nread++;


@@ -384,15 +391,17 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
              /* we did the full CONNECT treatment, go COMPLETE */
              /* we did the full CONNECT treatment, go COMPLETE */
              conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
              conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
            }
            }
                else
                  infof(data, "Read %zd bytes of chunk, continue\n",
                        tookcareof);
          }
          }
          continue;
        }
        }
            else {

        perline++; /* amount of bytes in this line so far */
        perline++; /* amount of bytes in this line so far */
              if(*ptr == 0x0a) {

                int writetype;
        /* if this is not the end of a header line then continue */
        if(*ptr != 0x0a) {
          ptr++;
          continue;
        }


        /* convert from the network encoding */
        /* convert from the network encoding */
        result = Curl_convert_from_network(data, line_start, perline);
        result = Curl_convert_from_network(data, line_start, perline);
@@ -410,8 +419,7 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
        if(data->set.include_header)
        if(data->set.include_header)
          writetype |= CLIENTWRITE_BODY;
          writetype |= CLIENTWRITE_BODY;


                result = Curl_client_write(conn, writetype, line_start,
        result = Curl_client_write(conn, writetype, line_start, perline);
                                           perline);


        data->info.header_size += (long)perline;
        data->info.header_size += (long)perline;
        data->req.headerbytecount += (long)perline;
        data->req.headerbytecount += (long)perline;
@@ -441,6 +449,9 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
            }
            }
            else if(chunked_encoding) {
            else if(chunked_encoding) {
              CHUNKcode r;
              CHUNKcode r;

              infof(data, "Ignore chunked response-body\n");

              /* We set ignorebody true here since the chunked
              /* We set ignorebody true here since the chunked
                 decoder function will acknowledge that. Pay
                 decoder function will acknowledge that. Pay
                 attention so that this is cleared again when this
                 attention so that this is cleared again when this
@@ -455,8 +466,7 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,


              /* now parse the chunked piece of data so that we can
              /* now parse the chunked piece of data so that we can
                 properly tell when the stream ends */
                 properly tell when the stream ends */
                      r = Curl_httpchunk_read(conn, line_start+1, 1,
              r = Curl_httpchunk_read(conn, line_start + 1, 1, &gotbytes);
                                              &gotbytes);
              if(r == CHUNKE_STOP) {
              if(r == CHUNKE_STOP) {
                /* we're done reading chunks! */
                /* we're done reading chunks! */
                infof(data, "chunk reading DONE\n");
                infof(data, "chunk reading DONE\n");
@@ -465,9 +475,6 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
                   COMPLETE */
                   COMPLETE */
                conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
                conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
              }
              }
                      else
                        infof(data, "Read %zd bytes of chunk, continue\n",
                              gotbytes);
            }
            }
            else {
            else {
              /* without content-length or chunked encoding, we
              /* without content-length or chunked encoding, we
@@ -480,7 +487,7 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
            keepon = FALSE;
            keepon = FALSE;
          /* we did the full CONNECT treatment, go to COMPLETE */
          /* we did the full CONNECT treatment, go to COMPLETE */
          conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
          conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
                  break; /* breaks out of for-loop, not switch() */
          continue;
        }
        }


        line_start[perline] = 0; /* zero terminate the buffer */
        line_start[perline] = 0; /* zero terminate the buffer */
@@ -514,8 +521,7 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
          cl = curlx_strtoofft(line_start +
          cl = curlx_strtoofft(line_start +
                               strlen("Content-Length:"), NULL, 10);
                               strlen("Content-Length:"), NULL, 10);
        }
        }
                else if(Curl_compareheader(line_start,
        else if(Curl_compareheader(line_start, "Connection:", "close"))
                                           "Connection:", "close"))
          closeConnection = TRUE;
          closeConnection = TRUE;
        else if(Curl_compareheader(line_start,
        else if(Curl_compareheader(line_start,
                                   "Transfer-Encoding:",
                                   "Transfer-Encoding:",
@@ -524,8 +530,7 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
            /* A server MUST NOT send any Transfer-Encoding or
            /* A server MUST NOT send any Transfer-Encoding or
               Content-Length header fields in a 2xx (Successful)
               Content-Length header fields in a 2xx (Successful)
               response to CONNECT. (RFC 7231 section 4.3.6) */
               response to CONNECT. (RFC 7231 section 4.3.6) */
                    failf(data, "Transfer-Encoding: in %03d response",
            failf(data, "Transfer-Encoding: in %03d response", k->httpcode);
                          k->httpcode);
            return CURLE_RECV_ERROR;
            return CURLE_RECV_ERROR;
          }
          }
          infof(data, "CONNECT responded chunked\n");
          infof(data, "CONNECT responded chunked\n");
@@ -533,8 +538,7 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
          /* init our chunky engine */
          /* init our chunky engine */
          Curl_httpchunk_init(conn);
          Curl_httpchunk_init(conn);
        }
        }
                else if(Curl_compareheader(line_start,
        else if(Curl_compareheader(line_start, "Proxy-Connection:", "close"))
                                           "Proxy-Connection:", "close"))
          closeConnection = TRUE;
          closeConnection = TRUE;
        else if(2 == sscanf(line_start, "HTTP/1.%d %d",
        else if(2 == sscanf(line_start, "HTTP/1.%d %d",
                            &subversion,
                            &subversion,
@@ -546,16 +550,10 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
        perline = 0; /* line starts over here */
        perline = 0; /* line starts over here */
        ptr = data->state.buffer;
        ptr = data->state.buffer;
        line_start = ptr;
        line_start = ptr;
              }
      } /* while there's buffer left and loop is requested */
              else /* not end of header line */

                ptr++;
            }
          }
          break;
        } /* switch */
      if(Curl_pgrsUpdate(conn))
      if(Curl_pgrsUpdate(conn))
        return CURLE_ABORTED_BY_CALLBACK;
        return CURLE_ABORTED_BY_CALLBACK;
      } /* while there's buffer left and loop is requested */


      if(error)
      if(error)
        return CURLE_RECV_ERROR;
        return CURLE_RECV_ERROR;
@@ -570,8 +568,7 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
        if(conn->bits.close)
        if(conn->bits.close)
          /* the connection has been marked for closure, most likely in the
          /* the connection has been marked for closure, most likely in the
             Curl_http_auth_act() function and thus we can kill it at once
             Curl_http_auth_act() function and thus we can kill it at once
             below
             below */
          */
          closeConnection = TRUE;
          closeConnection = TRUE;
      }
      }


+11 −0
Original line number Original line Diff line number Diff line
@@ -122,6 +122,13 @@ static size_t convert_lineends(struct Curl_easy *data,
#endif /* CURL_DO_LINEEND_CONV */
#endif /* CURL_DO_LINEEND_CONV */


#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
bool Curl_recv_has_postponed_data(struct connectdata *conn, int sockindex)
{
  struct postponed_data * const psnd = &(conn->postponed[sockindex]);
  return psnd->buffer && psnd->allocated_size &&
         psnd->recv_size > psnd->recv_processed;
}

static void pre_receive_plain(struct connectdata *conn, int num)
static void pre_receive_plain(struct connectdata *conn, int num)
{
{
  const curl_socket_t sockfd = conn->sock[num];
  const curl_socket_t sockfd = conn->sock[num];
@@ -201,6 +208,10 @@ static ssize_t get_pre_recved(struct connectdata *conn, int num, char *buf,
}
}
#else  /* ! USE_RECV_BEFORE_SEND_WORKAROUND */
#else  /* ! USE_RECV_BEFORE_SEND_WORKAROUND */
/* Use "do-nothing" macros instead of functions when workaround not used */
/* Use "do-nothing" macros instead of functions when workaround not used */
bool Curl_recv_has_postponed_data(struct connectdata *conn, int sockindex)
{
  return false;
}
#define pre_receive_plain(c,n) do {} WHILE_FALSE
#define pre_receive_plain(c,n) do {} WHILE_FALSE
#define get_pre_recved(c,n,b,l) 0
#define get_pre_recved(c,n,b,l) 0
#endif /* ! USE_RECV_BEFORE_SEND_WORKAROUND */
#endif /* ! USE_RECV_BEFORE_SEND_WORKAROUND */
Loading