200 lines
6.7 KiB
C#
200 lines
6.7 KiB
C#
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
|
|
{
|
|
/// <param name="hrmsEmployeeId">employees.id used in the portal photo path. Not the device serial.</param>
|
|
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";
|
|
}
|
|
|
|
/// <param name="hrmsEmployeeId">employees.id for EmployeePhotoBaseUrl + id + ".jpeg".</param>
|
|
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;
|
|
}
|
|
}
|
|
}
|