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 Diff line number Diff line
@@ -1404,3 +1404,16 @@ void Curl_conncontrol(struct connectdata *conn,
                                   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 Diff line number Diff line
@@ -142,4 +142,6 @@ void Curl_conncontrol(struct connectdata *conn,
#define connkeep(x,y) Curl_conncontrol(x, CONNCTRL_KEEP)
#endif

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

#endif /* HEADER_CURL_CONNECT_H */
+1 −1
Original line number 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.
       */
    }
    else if(!Curl_ssl_data_pending(conn, FIRSTSOCKET)) {
    else if(!Curl_conn_data_pending(conn, FIRSTSOCKET)) {
      switch(SOCKET_READABLE(sockfd, interval_ms)) {
      case -1: /* select() error, stop reading */
        failf(data, "FTP response aborted due to select/poll error: %d",
+229 −232
Original line number Diff line number Diff line
@@ -285,7 +285,7 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
    }

    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 CURLE_OK;
      else {
@@ -310,7 +310,16 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
      nread = 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);
        if(check <= 0) {
@@ -319,26 +328,22 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
          break;
        }

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

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

@@ -384,15 +391,17 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
              /* we did the full CONNECT treatment, go 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 */
              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 */
        result = Curl_convert_from_network(data, line_start, perline);
@@ -410,8 +419,7 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
        if(data->set.include_header)
          writetype |= CLIENTWRITE_BODY;

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

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

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

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

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

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

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

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

#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)
{
  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 */
/* 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 get_pre_recved(c,n,b,l) 0
#endif /* ! USE_RECV_BEFORE_SEND_WORKAROUND */
Loading