/// <summary> /// Execute is modified Curl_http() which in Curl gets called from the generic Curl_do() function when a HTTP /// request is to be performed. This creates and sends a properly constructed /// HTTP request. /// </summary> /// <param name="curl"></param> /// <param name="result"></param> /// <returns></returns> internal override object Execute(PhpCurlResource curl, ref CURLcode result) { UserDefined data = curl.Data; HttpBitsUploader uploader; bool terminatedCorrectly = false; int redirectAttempts = 0; bool keepVerb = false; result = CURLcode.CURLE_OK; if (data.Str[(int)DupString.SET_URL] == null) { result = CURLcode.CURLE_COULDNT_CONNECT; return false; } Uri uri = Utils.CompleteUri(PhpVariable.AsString(data.Str[(int)DupString.SET_URL]), Scheme, data.UsePort); for (; ; ) { request = (HttpWebRequest)HttpWebRequest.Create(uri); Curl_HttpReq httpreq = (redirectAttempts == 0) || keepVerb ? setRequestMethod(data) : Curl_HttpReq.GET; setTimeOut(data); setHttpVersion(data); request.AllowAutoRedirect = data.FollowLocation; request.MaximumAutomaticRedirections = data.MaxRedirects; if (data.Str[(int)DupString.USERAGENT] != null) request.UserAgent = PhpVariable.AsString(data.Str[(int)DupString.USERAGENT]); if (data.Str[(int)DupString.SET_REFERER] != null) request.Referer = PhpVariable.AsString(data.Str[(int)DupString.SET_REFERER]); if (data.Headers != null) request.SetHttpHeaders(data.Headers); setProxy(data); setCredentials(data); setCookies(data); //ssl.VerifyPeer && ssl.VerifyHost == 2 is supported by default .NET // other values are currently unsupported if (data.Str[(int)DupString.CERT] != null) { X509Certificate cert; string certPath; try { certPath = Path.Combine(ScriptContext.CurrentContext.WorkingDirectory, PhpVariable.AsString(data.Str[(int)DupString.SSL_CAFILE])); if (data.Str[(int)DupString.KEY_PASSWD] == null) cert = new X509Certificate(certPath); else cert = new X509Certificate(certPath, PhpVariable.AsString(data.Str[(int)DupString.KEY_PASSWD])); request.ClientCertificates.Add(cert); } catch (CryptographicException) { //TODO: here are more caises to differentiate result = CURLcode.CURLE_SSL_CACERT_BADFILE; return false; } } switch (httpreq) { case Curl_HttpReq.POST_FORM: //same as POST but we can send multiple items asform-data if (data.HttpPostForm != null) { try { HttpFormDataUploader formUploader = new HttpFormDataUploader(request); formUploader.UploadForm(data.HttpPostForm); } catch (WebException ex) { switch (ex.Status) { case WebExceptionStatus.Timeout: result = CURLcode.CURLE_OPERATION_TIMEOUTED; break; default: result = CURLcode.CURLE_COULDNT_CONNECT;// for now just this break; } return false; } } break; case Curl_HttpReq.PUT: /* Let's PUT the data to the server! */ //INFILE & INFILESIZE has to be set NativeStream nativeStream = data.Infile as NativeStream; if (nativeStream == null) return false; FileStream fs = nativeStream.RawStream as FileStream; if (fs == null) return false; try { uploader = new HttpBitsUploader(request); uploader.UploadFile(fs); } catch (WebException ex) { switch (ex.Status) { case WebExceptionStatus.Timeout: result = CURLcode.CURLE_OPERATION_TIMEOUTED; break; default: result = CURLcode.CURLE_COULDNT_CONNECT;// for now just this break; } return false; } break; case Curl_HttpReq.POST: /* this is the simple POST, using x-www-form-urlencoded style */ if (String.IsNullOrEmpty(request.ContentType))// if Content-type isn't set set the default request.ContentType = "application/x-www-form-urlencoded"; if (data.Postfields != null) { try { uploader = new HttpBitsUploader(request); uploader.UploadData(data.Postfields); } catch (WebException ex) { switch (ex.Status) { case WebExceptionStatus.Timeout: result = CURLcode.CURLE_OPERATION_TIMEOUTED; break; default: result = CURLcode.CURLE_COULDNT_CONNECT;// for now just this break; } return false; } } break; } try { // if we got this far, we will turn off AutoRedirect (assuming it was on), since // we are ready to handle manually following certain responses. this is needed // to harvest cookies that are set on any intermediate response (i.e. anything // other than the last one followed), since the .NET HTTP class will use, but // NOT return, cookies set on anything but the last request. request.AllowAutoRedirect = false; response = (HttpWebResponse)request.GetResponse(); } catch (WebException ex) { switch (ex.Status) { case WebExceptionStatus.Timeout: result = CURLcode.CURLE_OPERATION_TIMEOUTED; break; case WebExceptionStatus.ConnectFailure: result = CURLcode.CURLE_COULDNT_CONNECT; break; case WebExceptionStatus.TrustFailure: result = CURLcode.CURLE_SSL_CACERT; break; case WebExceptionStatus.ProtocolError: //Response from server was complete, but indicated protocol error as 404, 401 etc. break; default: result = CURLcode.CURLE_COULDNT_CONNECT;// for now just this break; } //TODO: other errorCodes response = (HttpWebResponse)ex.Response; //return false; //error = true; } if (response == null)// just to make sure I have the response object return false; if (data.FollowLocation) { // see if we need to follow a redirect. switch (response.StatusCode) { case HttpStatusCode.MovedPermanently: case HttpStatusCode.Found: case HttpStatusCode.SeeOther: case HttpStatusCode.RedirectKeepVerb: if (redirectAttempts++ >= data.MaxRedirects) { result = CURLcode.CURLE_TOO_MANY_REDIRECTS; return false; } string location = response.Headers["Location"]; if (!string.IsNullOrWhiteSpace(location)) { try { keepVerb = response.StatusCode == HttpStatusCode.RedirectKeepVerb; data.Cookies.Add(response.Cookies); response.Close(); uri = new Uri(uri, location); continue; } catch (Exception) { // closest error code though could be confusing as it's not the user- // submitted URL that's the problem result = CURLcode.CURLE_URL_MALFORMAT; return false; } } break; } } //Save cookies data.Cookies.Add(response.Cookies); // break out of the for loop as we aren't following a redirect break; } byte[] headers = null; byte[] content = null; int headersLength = 0; if (data.IncludeHeader) { //It's necessary to put HTTP header into the result //first we need to create it since there isn't anywhere headers = Encoding.ASCII.GetBytes(response.GetHttpHeaderAsString()); headersLength = headers.Length; } if (data.FunctionWriteHeader != null)// TODO: probably invoke before { response.InvokeHeaderFunction(curl, data.FunctionWriteHeader); } Stream writeStream = null; if (data.WriteFunction != null) { writeStream = new WriteFunctionStream(curl, data.WriteFunction); } else if (data.OutFile != null) { var outStream = data.OutFile as PhpStream; if (outStream == null) return false; Stream fs = outStream.RawStream as Stream; if (fs == null) return false; writeStream = fs; } else if (data.ReturnTransfer == false) // Output to standart output { writeStream = ScriptContext.CurrentContext.OutputStream; } if (writeStream != null) { if (headers != null) //there is http header to copy to the result { writeStream.Write(headers, 0, headersLength); } HttpBitsDownloader reader = new HttpBitsDownloader(response); try { reader.ReadToStream(writeStream, out terminatedCorrectly); } catch (WebException ex) { switch (ex.Status) { case WebExceptionStatus.Timeout: result = CURLcode.CURLE_OPERATION_TIMEOUTED; break; default: result = CURLcode.CURLE_COULDNT_CONNECT;// for now just this break; } } if (!terminatedCorrectly) result = CURLcode.CURLE_PARTIAL_FILE; return true; } else { // Read the response HttpBitsDownloader reader = new HttpBitsDownloader(response); try { content = reader.ReadToEnd(headersLength, out terminatedCorrectly); } catch(WebException ex) { switch (ex.Status) { case WebExceptionStatus.Timeout: result = CURLcode.CURLE_OPERATION_TIMEOUTED; break; default: result = CURLcode.CURLE_COULDNT_CONNECT;// for now just this break; } } if (!terminatedCorrectly) result = CURLcode.CURLE_PARTIAL_FILE; if (headers != null) //there is http header to copy to the result { if (content != null) Buffer.BlockCopy(headers, 0, content, 0, headersLength); else content = headers; } if (content == null) return PhpBytes.Empty; else return new PhpBytes(content); } }