using System; using System.Drawing; using System.IO; using System.Net; namespace HanvonF710XAttendanceService { internal sealed class HrmsEmployeePhotoDownloadResult { public bool Success { get; set; } public int StatusCode { get; set; } public string ContentType { get; set; } public string Url { get; set; } public byte[] ImageBytes { get; set; } public int Width { get; set; } public int Height { get; set; } public int Base64Length { get; set; } public string Error { get; set; } } internal static class HrmsEmployeePhotoClient { /// employees.id used in the portal photo path. Not the device serial. public static string BuildPhotoUrl(string baseUrl, string hrmsEmployeeId) { if (string.IsNullOrWhiteSpace(baseUrl)) { throw new ArgumentException("Employee photo base URL is required."); } if (string.IsNullOrWhiteSpace(hrmsEmployeeId)) { throw new ArgumentException("HRMS employee ID (employees.id) is required."); } string trimmedBase = baseUrl.Trim(); if (!trimmedBase.EndsWith("/", StringComparison.Ordinal)) { trimmedBase += "/"; } return trimmedBase + hrmsEmployeeId.Trim() + ".jpeg"; } /// employees.id for EmployeePhotoBaseUrl + id + ".jpeg". public static bool TryDownloadEmployeePhoto(string hrmsEmployeeId, out HrmsEmployeePhotoDownloadResult result) { result = new HrmsEmployeePhotoDownloadResult(); if (!EmployeePhotoSourceSettings.IsEnabled()) { result.Error = "employee_photo_source_disabled"; return false; } string baseUrl = EmployeePhotoSourceSettings.GetBaseUrl(); if (string.IsNullOrWhiteSpace(baseUrl)) { result.Error = "employee_photo_base_url_missing"; return false; } if (string.IsNullOrWhiteSpace(hrmsEmployeeId)) { result.Error = "hrms_employee_id_missing"; return false; } result.Url = BuildPhotoUrl(baseUrl, hrmsEmployeeId); int timeoutMs = EmployeePhotoSourceSettings.GetTimeoutMs(); try { var request = (HttpWebRequest)WebRequest.Create(result.Url); request.Method = "GET"; request.Timeout = timeoutMs; request.ReadWriteTimeout = timeoutMs; request.AllowAutoRedirect = true; request.UserAgent = "HanvonF710XAttendanceService/1.0"; using (var response = (HttpWebResponse)request.GetResponse()) using (var stream = response.GetResponseStream()) using (var memory = new MemoryStream()) { result.StatusCode = (int)response.StatusCode; result.ContentType = response.ContentType; if (response.StatusCode != HttpStatusCode.OK) { result.Error = "http_status_" + result.StatusCode; return false; } if (stream == null) { result.Error = "empty_response_stream"; return false; } stream.CopyTo(memory); result.ImageBytes = memory.ToArray(); } } catch (WebException wex) { if (wex.Response is HttpWebResponse errResp) { result.StatusCode = (int)errResp.StatusCode; result.ContentType = errResp.ContentType; } result.Error = wex.Message; return false; } catch (Exception ex) { result.Error = ex.Message; return false; } if (!TryValidateDownloadedImage(result, out string validationError)) { result.Error = validationError; result.ImageBytes = null; return false; } result.Base64Length = Convert.ToBase64String(result.ImageBytes).Length; result.Success = true; return true; } public static bool TryValidateDownloadedImage(HrmsEmployeePhotoDownloadResult result, out string error) { error = null; byte[] bytes = result?.ImageBytes; if (bytes == null || bytes.Length == 0) { error = "empty_image"; return false; } if (bytes.Length < 4 || bytes[0] != 0xFF || bytes[1] != 0xD8) { error = "not_jpeg"; return false; } if (!NedoPhotoPreprocessor.IsCompleteJpeg(bytes)) { error = "incomplete_jpeg"; return false; } string contentType = result.ContentType ?? ""; if (!string.IsNullOrWhiteSpace(contentType) && contentType.IndexOf("image", StringComparison.OrdinalIgnoreCase) < 0 && contentType.IndexOf("jpeg", StringComparison.OrdinalIgnoreCase) < 0 && contentType.IndexOf("octet-stream", StringComparison.OrdinalIgnoreCase) < 0) { error = "invalid_content_type"; return false; } try { using (var input = new MemoryStream(bytes)) using (var image = Image.FromStream(input)) { result.Width = image.Width; result.Height = image.Height; } } catch (Exception ex) { error = "invalid_image_data:" + ex.Message; return false; } int minShort = EmployeePhotoSourceSettings.GetMinShortSide(); int maxShort = EmployeePhotoSourceSettings.GetMaxShortSide(); int shortSide = Math.Min(result.Width, result.Height); int longSide = Math.Max(result.Width, result.Height); if (shortSide < minShort) { error = "image_too_small"; return false; } if (longSide > maxShort) { error = "image_too_large"; return false; } return true; } } }