/// <summary> /// Return the content of a cURL handle if <see cref="CURLConstants.CURLOPT_RETURNTRANSFER"/> is set. /// </summary> public static PhpValue curl_multi_getcontent(CURLResource ch) { if (ch.ReturnTransfer && ch.Result?.ExecValue.TypeCode == PhpTypeCode.MutableString) { return(ch.Result.ExecValue); } else { return(PhpValue.Null); } }
/// <summary> /// Return the content of a cURL handle if <see cref="CURLConstants.CURLOPT_RETURNTRANSFER"/> is set. /// </summary> public static PhpValue curl_multi_getcontent(CURLResource ch) { if (ch.ProcessingResponse.Method == ProcessMethodEnum.RETURN && ch.Result != null && ch.Result.ExecValue.IsSet) { return(ch.Result.ExecValue); } else { return(PhpValue.Null); } }
/// <summary> /// Remove a multi handle from a set of cURL handles /// </summary> /// <remarks> /// Removes a given <paramref name="ch"/> handle from the given <paramref name="mh"/> handle. /// When the <paramref name="ch"/> handle has been removed, it is again perfectly legal to run /// <see cref="curl_exec(Context, CURLResource)"/> on this handle. Removing the <paramref name="ch"/> /// handle while being used, will effectively halt the transfer in progress involving that handle. /// </remarks> public static int curl_multi_remove_handle(CURLMultiResource mh, CURLResource ch) { if (mh.Handles.Remove(ch) && ch.ResponseTask != null) { // We will simply remove the only reference to the ongoing request and let the framework either // finish it or cancel it ch.ResponseTask = null; } return(CURLConstants.CURLM_OK); }
internal void AddResultMessage(CURLResource handle) { var msg = new PhpArray { { "msg", CURLConstants.CURLMSG_DONE }, { "result", handle.Result.ErrorCode }, { "handle", handle } }; MessageQueue.Enqueue(msg); }
static void EndRequestExecution(Context ctx, CURLResource ch) { if (ch.ResponseTask != null) { ch.Result = ProcessHttpResponseTask(ctx, ch, ch.ResponseTask); ch.ResponseTask = null; } ch.Result.TotalTime = (DateTime.UtcNow - ch.StartTime); ch.Result.Private = ch.Private; }
static async Task <CURLResponse> ProcessHttpResponseTask(Context ctx, CURLResource ch, Task <WebResponse> responseTask) { try { using (var response = (HttpWebResponse)responseTask.Result) { return(new CURLResponse(await ProcessResponse(ctx, ch, response), response, ch)); } } catch (AggregateException agEx) { var ex = agEx.InnerException; ch.VerboseOutput(ex.ToString()); if (ex is WebException webEx) { // TODO: ch.FailOnError ? var exception = webEx.InnerException ?? webEx; switch (webEx.Status) { case WebExceptionStatus.ProtocolError: // actually ok, 301, 500, etc .. process the response: return(new CURLResponse(await ProcessResponse(ctx, ch, (HttpWebResponse)webEx.Response), (HttpWebResponse)webEx.Response, ch)); case WebExceptionStatus.Timeout: return(CURLResponse.CreateError(CurlErrors.CURLE_OPERATION_TIMEDOUT, exception)); case WebExceptionStatus.TrustFailure: return(CURLResponse.CreateError(CurlErrors.CURLE_SSL_CACERT, exception)); case WebExceptionStatus.ConnectFailure: default: return(CURLResponse.CreateError(CurlErrors.CURLE_COULDNT_CONNECT, exception)); } } else if (ex is ProtocolViolationException) { return(CURLResponse.CreateError(CurlErrors.CURLE_FAILED_INIT, ex)); } else if (ex is CryptographicException) { return(CURLResponse.CreateError(CurlErrors.CURLE_SSL_CERTPROBLEM, ex)); } else { throw ex; } } }
static void ProcessPost(Context ctx, HttpWebRequest req, CURLResource ch) { byte[] bytes; if (ch.PostFields.IsPhpArray(out var arr) && arr != null) { string boundary = "----------" + DateTime.UtcNow.Ticks.ToString(); string contentType = "multipart/form-data; boundary=" + boundary; bytes = GetMultipartFormData(ctx, arr, boundary); req.ContentType = contentType; }
public static string curl_error(CURLResource ch) { if (ch != null && ch.Result != null) { var err = ch.Result.ErrorCode; if (err != CurlErrors.CURLE_OK) { return(ch.Result.ErrorMessage ?? err.ToString()); // TODO: default error messages in resources } } return(string.Empty); }
static void ProcessPut(HttpWebRequest req, CURLResource ch) { var fs = ch.ProcessingRequest.Stream; if (fs != null) { // req.ContentLength = bytes.Length; using (var stream = req.GetRequestStream()) { fs.RawStream.CopyTo(stream); } } }
static void EndRequestExecution(Context ctx, CURLResource ch) { if (ch.ResponseTask != null) { ch.Result = ProcessHttpResponseTask(ctx, ch, ch.ResponseTask).GetAwaiter().GetResult(); ch.ResponseTask = null; } ch.Result.TotalTime = (DateTime.UtcNow - ch.StartTime); if (ch.TryGetOption <CurlOption_Private>(out var opt_private)) { ch.Result.Private = opt_private.OptionValue; } }
public static void curl_close(Context ctx, CURLResource ch) { if (ch != null) { if (ch.TryGetOption <CurlOption_CookieJar>(out var jar)) { jar.PrintCookies(ctx, ch); } // ch.Dispose(); } else { PhpException.ArgumentNull(nameof(ch)); } }
/// <summary> /// Get information regarding a specific transfer. /// </summary> public static PhpValue curl_getinfo(CURLResource ch, int opt = 0) { if (ch != null && ch.Result != null) { var r = ch.Result; switch (opt) { case 0: // array of all information return((PhpValue) new PhpArray() { { "url", r.ResponseUri.AbsoluteUri }, { "content_type", r.ContentType }, { "http_code", r.StatusCode }, { "filetime", DateTimeUtils.UtcToUnixTimeStamp(r.LastModified) }, { "total_time", r.TotalTime.TotalSeconds }, }); case CURLConstants.CURLINFO_EFFECTIVE_URL: return((PhpValue)r.ResponseUri.AbsoluteUri); case CURLConstants.CURLINFO_REDIRECT_URL: return((PhpValue)(ch.FollowLocation ? string.Empty : r.ResponseUri.AbsoluteUri)); case CURLConstants.CURLINFO_HTTP_CODE: return((PhpValue)r.StatusCode); case CURLConstants.CURLINFO_FILETIME: return((PhpValue)DateTimeUtils.UtcToUnixTimeStamp(r.LastModified)); case CURLConstants.CURLINFO_CONTENT_TYPE: return((PhpValue)r.ContentType); case CURLConstants.CURLINFO_TOTAL_TIME: return((PhpValue)r.TotalTime.TotalSeconds); } } // failure: return(PhpValue.False); }
static Uri?TryCreateUri(CURLResource ch) { var url = ch.Url; if (string.IsNullOrEmpty(url)) { return(null); } // if (url.IndexOf("://", StringComparison.Ordinal) == -1) { url = string.Concat(ch.DefaultSheme, "://", url); } // TODO: implicit port // Uri.TryCreate(url, UriKind.Absolute, out Uri result); return(result); }
static CURLResponse ProcessHttpResponseTask(Context ctx, CURLResource ch, Task <WebResponse> responseTask) { try { using (var response = (HttpWebResponse)responseTask.Result) { return(new CURLResponse(ProcessResponse(ctx, ch, response), response)); } } catch (AggregateException agEx) { var ex = agEx.InnerException; if (ex is WebException webEx) { switch (webEx.Status) { case WebExceptionStatus.Timeout: return(CURLResponse.CreateError(CurlErrors.CURLE_OPERATION_TIMEDOUT, webEx)); case WebExceptionStatus.TrustFailure: return(CURLResponse.CreateError(CurlErrors.CURLE_SSL_CACERT, webEx)); default: return(CURLResponse.CreateError(CurlErrors.CURLE_COULDNT_CONNECT, webEx)); } } else if (ex is ProtocolViolationException) { return(CURLResponse.CreateError(CurlErrors.CURLE_FAILED_INIT, ex)); } else if (ex is CryptographicException) { return(CURLResponse.CreateError(CurlErrors.CURLE_SSL_CERTPROBLEM, ex)); } else { throw ex; } } }
/// <summary> /// Set multiple options for a cURL transfer. /// </summary> public static bool curl_setopt_array(CURLResource ch, PhpArray options) { if (ch == null || !ch.IsValid) { return(false); } if (options != null) { var enumerator = options.GetFastEnumerator(); while (enumerator.MoveNext()) { if (!enumerator.CurrentKey.IsInteger || !ch.TrySetOption(enumerator.CurrentKey.Integer, enumerator.CurrentValue)) { return(false); } } } return(true); }
static void StartRequestExecution(Context ctx, CURLResource ch) { ch.StartTime = DateTime.UtcNow; var uri = TryCreateUri(ch); if (uri == null) { ch.Result = CURLResponse.CreateError(CurlErrors.CURLE_URL_MALFORMAT); } else if ( IsProtocol(ch, uri, "http", CURLConstants.CURLPROTO_HTTP) || IsProtocol(ch, uri, "https", CURLConstants.CURLPROTO_HTTPS)) { ch.Result = null; ch.ResponseTask = ExecHttpRequestInternalAsync(ctx, ch, uri); } else { ch.Result = CURLResponse.CreateError(CurlErrors.CURLE_UNSUPPORTED_PROTOCOL); } }
static void StartRequestExecution(Context ctx, CURLResource ch) { ch.StartTime = DateTime.UtcNow; var uri = TryCreateUri(ch); if (uri == null) { ch.Result = CURLResponse.CreateError(CurlErrors.CURLE_URL_MALFORMAT); } else if ( string.Equals(uri.Scheme, "http", StringComparison.OrdinalIgnoreCase) || string.Equals(uri.Scheme, "https", StringComparison.OrdinalIgnoreCase)) { ch.Result = null; ch.ResponseTask = ExecHttpRequestInternalAsync(ctx, ch, uri); } else { ch.Result = CURLResponse.CreateError(CurlErrors.CURLE_UNSUPPORTED_PROTOCOL); } }
internal CurlMultiErrors TryAddHandle(CURLResource handle) { return(LastError = Handles.Add(handle) ? CurlMultiErrors.CURLM_OK : CurlMultiErrors.CURLM_ADDED_ALREADY); }
static void ProcessPut(Context ctx, HttpWebRequest req, CURLResource ch) { WriteRequestStream(ctx, req, ch, ch.ProcessingRequest.Stream); }
static Task <WebResponse> ExecHttpRequestInternalAsync(Context ctx, CURLResource ch, Uri uri) { var req = WebRequest.CreateHttp(uri); // setup request: Debug.Assert(ch.Method != null, "Method == null"); req.Method = ch.Method; req.AllowAutoRedirect = ch.FollowLocation && ch.MaxRedirects != 0; req.Timeout = ch.Timeout <= 0 ? System.Threading.Timeout.Infinite : ch.Timeout; req.ContinueTimeout = ch.ContinueTimeout; req.Accept = "*/*"; // default value if (req.AllowAutoRedirect) { // equal or less than 0 will cause exception req.MaximumAutomaticRedirections = ch.MaxRedirects < 0 ? int.MaxValue : ch.MaxRedirects; } if (ch.CookieContainer != null) { if (ch.Result != null) { // pass cookies from previous response to the request AddCookies(ch.Result.Cookies, ch.CookieContainer); } req.CookieContainer = ch.CookieContainer; } //req.AutomaticDecompression = (DecompressionMethods)~0; // NOTICE: this nullify response Content-Length and Content-Encoding if (ch.CookieHeader != null) { TryAddCookieHeader(req, ch.CookieHeader); } if (ch.Username != null) { req.Credentials = new NetworkCredential(ch.Username, ch.Password ?? string.Empty); } // TODO: certificate if (!string.IsNullOrEmpty(ch.ProxyType) && !string.IsNullOrEmpty(ch.ProxyHost)) { req.Proxy = new WebProxy($"{ch.ProxyType}://{ch.ProxyHost}:{ch.ProxyPort}") { Credentials = string.IsNullOrEmpty(ch.ProxyUsername) ? null : new NetworkCredential(ch.ProxyUsername, ch.ProxyPassword ?? string.Empty) }; } else { // by default, curl does not go through system proxy req.Proxy = s_DefaultProxy; } // ch.ApplyOptions(ctx, req); // make request: // GET, HEAD if (string.Equals(ch.Method, WebRequestMethods.Http.Get, StringComparison.OrdinalIgnoreCase)) { WriteRequestStream(ctx, req, ch, ch.ProcessingRequest.Stream); } else if (string.Equals(ch.Method, WebRequestMethods.Http.Head, StringComparison.OrdinalIgnoreCase)) { // } // POST else if (string.Equals(ch.Method, WebRequestMethods.Http.Post, StringComparison.OrdinalIgnoreCase)) { ProcessPost(ctx, req, ch); } // PUT else if (string.Equals(ch.Method, WebRequestMethods.Http.Put, StringComparison.OrdinalIgnoreCase)) { ProcessPut(ctx, req, ch); } // DELETE, or custom method else { // custom method, nothing to do } // if (ch.StoreRequestHeaders) { ch.RequestHeaders = HttpHeaders.HeaderString(req); // and restore it when constructing CURLResponse } // return(req.GetResponseAsync()); }
/// <summary> /// Close a cURL session. /// </summary> public static void curl_close(CURLResource resource) => resource?.Dispose();
/// <summary> /// Sets an option on the given cURL session handle. /// </summary> public static bool curl_setopt(CURLResource ch, int option, PhpValue value) => ch.TrySetOption(option, value);
public static PhpValue curl_getinfo(CURLResource ch, int option = 0) { if (ch == null) { PhpException.ArgumentNull(nameof(ch)); return(PhpValue.Null); } var r = ch.Result ?? CURLResponse.Empty; switch (option) { case 0: // array of all information var arr = new PhpArray(38) { { "url", ch.Url ?? string.Empty }, { "content_type", r.ContentType }, { "http_code", (long)r.StatusCode }, { "header_size", r.HeaderSize }, { "filetime", r.LastModifiedTimeStamp }, { "total_time", r.TotalTime.TotalSeconds }, { "download_content_length", r.ContentLength }, { "redirect_url", ch.FollowLocation&& r.ResponseUri != null ? r.ResponseUri.AbsoluteUri : string.Empty }, //{ "http_version", CURL_HTTP_VERSION_*** } //{ "protocol", CURLPROTO_*** }, //{ "scheme", STRING }, }; if (ch.RequestHeaders != null) { arr["request_header"] = ch.RequestHeaders; } return(arr); case CURLConstants.CURLINFO_EFFECTIVE_URL: return(ch.Url ?? string.Empty); case CURLConstants.CURLINFO_REDIRECT_URL: return(ch.FollowLocation && r.ResponseUri != null ? r.ResponseUri.AbsoluteUri : string.Empty); case CURLConstants.CURLINFO_HTTP_CODE: return((int)r.StatusCode); case CURLConstants.CURLINFO_FILETIME: return(r.LastModifiedTimeStamp); case CURLConstants.CURLINFO_CONTENT_TYPE: return(r.ContentType); case CURLConstants.CURLINFO_CONTENT_LENGTH_DOWNLOAD: return(r.ContentLength); case CURLConstants.CURLINFO_TOTAL_TIME: return(r.TotalTime.TotalSeconds); case CURLConstants.CURLINFO_PRIVATE: return(Operators.IsSet(r.Private) ? r.Private.DeepCopy() : PhpValue.False); case CURLConstants.CURLINFO_COOKIELIST: return((ch.CookieContainer != null && ch.Result != null) ? CreateCookiePhpArray(ch.Result.Cookies) : PhpArray.Empty); case CURLConstants.CURLINFO_HEADER_SIZE: return(r.HeaderSize); case CURLConstants.CURLINFO_HEADER_OUT: return(r.RequestHeaders ?? PhpValue.False); default: PhpException.ArgumentValueNotSupported(nameof(option), option); return(PhpValue.False); } }
static async Task <PhpValue> ProcessResponse(Context ctx, CURLResource ch, HttpWebResponse response) { // in case we are returning the response value var returnstream = ch.ProcessingResponse.Method == ProcessMethodEnum.RETURN ? new MemoryStream() : null; // handle headers if (!ch.ProcessingHeaders.IsEmpty) { var statusHeaders = HttpHeaders.StatusHeader(response) + HttpHeaders.HeaderSeparator; // HTTP/1.1 xxx xxx\r\n Stream outputHeadersStream = null; switch (ch.ProcessingHeaders.Method) { case ProcessMethodEnum.RETURN: case ProcessMethodEnum.STDOUT: outputHeadersStream = (returnstream ?? ctx.OutputStream); goto default; case ProcessMethodEnum.FILE: outputHeadersStream = ch.ProcessingHeaders.Stream.RawStream; goto default; case ProcessMethodEnum.USER: // pass headers one by one, // in original implementation we should pass them as they are read from socket: ch.ProcessingHeaders.User.Invoke(ctx, new[] { PhpValue.FromClass(ch), PhpValue.Create(statusHeaders) }); for (int i = 0; i < response.Headers.Count; i++) { var key = response.Headers.GetKey(i); var value = response.Headers.Get(i); if (key == null || key.Length != 0) { // header ch.ProcessingHeaders.User.Invoke(ctx, new[] { PhpValue.FromClr(ch), PhpValue.Create(key + ": " + value + HttpHeaders.HeaderSeparator), }); } } // \r\n ch.ProcessingHeaders.User.Invoke(ctx, new[] { PhpValue.FromClr(ch), PhpValue.Create(HttpHeaders.HeaderSeparator) }); break; default: if (outputHeadersStream != null) { await outputHeadersStream.WriteAsync(Encoding.ASCII.GetBytes(statusHeaders)); await outputHeadersStream.WriteAsync(response.Headers.ToByteArray()); } else { Debug.Fail("Unexpected ProcessingHeaders " + ch.ProcessingHeaders.Method); } break; } } var stream = response.GetResponseStream(); // gzip decode if necessary if (response.ContentEncoding == "gzip") // TODO: // && ch.AcceptEncoding.Contains("gzip") ?? { ch.VerboseOutput("Decompressing the output stream using GZipStream."); stream = new GZipStream(stream, CompressionMode.Decompress, leaveOpen: false); } // read into output stream: switch (ch.ProcessingResponse.Method) { case ProcessMethodEnum.STDOUT: await stream.CopyToAsync(ctx.OutputStream); break; case ProcessMethodEnum.RETURN: stream.CopyTo(returnstream); break; case ProcessMethodEnum.FILE: await stream.CopyToAsync(ch.ProcessingResponse.Stream.RawStream); break; case ProcessMethodEnum.USER: if (response.ContentLength != 0) { // preallocate a buffer to read to, // this should be according to PHP's behavior and slightly more effective than memory stream byte[] buffer = new byte[ch.BufferSize > 0 ? ch.BufferSize : 2048]; int bufferread; while ((bufferread = stream.Read(buffer, 0, buffer.Length)) > 0) { ch.ProcessingResponse.User.Invoke(ctx, new[] { PhpValue.FromClr(ch), PhpValue.Create(new PhpString(buffer.AsSpan(0, bufferread).ToArray())), // clone the array and pass to function }); } } break; case ProcessMethodEnum.IGNORE: break; } // stream.Dispose(); stream = null; // return((returnstream != null) ? PhpValue.Create(new PhpString(returnstream.ToArray())) : PhpValue.True); }
public static int curl_errno(CURLResource ch) => (int)(ch?.Result != null ? ch.Result.ErrorCode : CurlErrors.CURLE_OK);
/// <summary> /// Get information regarding a specific transfer. /// </summary> public static PhpValue curl_getinfo(CURLResource ch, int opt = 0) { if (ch != null && ch.Result != null) { var r = ch.Result; switch (opt) { case 0: // array of all information var arr = new PhpArray() { { "url", r.ResponseUri?.AbsoluteUri }, { "content_type", r.ContentType }, { "http_code", (long)r.StatusCode }, { "header_size", r.HeaderSize }, { "filetime", DateTimeUtils.UtcToUnixTimeStamp(r.LastModified) }, { "total_time", r.TotalTime.TotalSeconds }, { "download_content_length", r.ContentLength }, { "redirect_url", ch.FollowLocation&& r.ResponseUri != null ? string.Empty : r.ResponseUri.AbsoluteUri } }; if (ch.RequestHeaders != null) { arr["request_header"] = ch.RequestHeaders; } return(arr); case CURLConstants.CURLINFO_EFFECTIVE_URL: return(r.ResponseUri?.AbsoluteUri); case CURLConstants.CURLINFO_REDIRECT_URL: return(ch.FollowLocation && r.ResponseUri != null ? string.Empty : r.ResponseUri.AbsoluteUri); case CURLConstants.CURLINFO_HTTP_CODE: return((int)r.StatusCode); case CURLConstants.CURLINFO_FILETIME: return(DateTimeUtils.UtcToUnixTimeStamp(r.LastModified)); case CURLConstants.CURLINFO_CONTENT_TYPE: return(r.ContentType); case CURLConstants.CURLINFO_CONTENT_LENGTH_DOWNLOAD: return(r.ContentLength); case CURLConstants.CURLINFO_TOTAL_TIME: return(r.TotalTime.TotalSeconds); case CURLConstants.CURLINFO_PRIVATE: return(r.Private.IsSet ? r.Private.DeepCopy() : PhpValue.False); case CURLConstants.CURLINFO_COOKIELIST: return((ch.CookieFileSet && ch.Result != null) ? CreateCookieArray(ch.Result.Cookies) : PhpArray.Empty); case CURLConstants.CURLINFO_HEADER_SIZE: return(r.HeaderSize); case CURLConstants.CURLINFO_HEADER_OUT: return(r.RequestHeaders ?? PhpValue.False); default: PhpException.ArgumentValueNotSupported(nameof(opt), opt); break; } } // failure: return(PhpValue.False); }
/// <summary> /// Add a normal cURL handle to a cURL multi handle. /// </summary> public static int curl_multi_add_handle(CURLMultiResource mh, CURLResource ch) => (int)mh.TryAddHandle(ch);
static bool IsProtocol(CURLResource ch, Uri uri, string scheme, int proto) { return (string.Equals(uri.Scheme, scheme, StringComparison.OrdinalIgnoreCase) && (ch.Protocols & proto) != 0); }