private static void SendFile(Stream stream, FileInfo fileInfo, string contentType, string contentDisposition) { if (fileInfo.Exists) { var messageBytes = Encoding.UTF8.GetBytes("HTTP/1.1 200 OK\r\n" + $"Date: {DateTime.UtcNow:R}\r\n" + $"Content-Type: {contentType}\r\n" + $"Content-Disposition: {contentDisposition}; filename=\"{fileInfo.Name}\"\r\n" + $"Last-Modified: {fileInfo.LastWriteTimeUtc:R}\r\n" + "Content-Transfer-Encoding: binary\r\n" + $"Content-Length: {fileInfo.Length}\r\n\r\n"); stream.Write(messageBytes, 0, messageBytes.Length); try { WebStats.AddFileDownload(fileInfo.Length); using (var fs = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) { fs.CopyTo(stream); } } catch (Exception e) { Helper.WriteLogEntry($"{fileInfo.FullName}\n{e.Message}"); } } else { WebStats.Increment404Response(); var messageBytes = Encoding.UTF8.GetBytes($"HTTP/1.1 404 Not Found\r\nDate: {DateTime.UtcNow:R}\r\n\r\n"); stream.Write(messageBytes, 0, messageBytes.Length); } }
private static void SendImage(Stream stream, FileInfo fileInfo) { if (fileInfo != null && fileInfo.Exists) { var ext = fileInfo.Extension.ToLower().Substring(1); var messageBytes = Encoding.UTF8.GetBytes("HTTP/1.1 200 OK\r\n" + "Accept-Ranges: bytes\r\n" + $"Content-Length: {fileInfo.Length}\r\n" + $"Content-Type: image/{ext}\r\n" + $"Date: {DateTime.UtcNow:R}\r\n" + $"Last-Modified: {fileInfo.LastWriteTimeUtc:R}\r\n\r\n"); stream.Write(messageBytes, 0, messageBytes.Length); try { using (var fs = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) { fs.CopyTo(stream); } } catch (Exception e) { Helper.WriteLogEntry($"{fileInfo.FullName}\n{e.Message}"); } } else { WebStats.Increment404Response(); var messageBytes = Encoding.UTF8.GetBytes($"HTTP/1.1 404 Not Found\r\nDate: {DateTime.UtcNow:R}\r\n\r\n"); stream.Write(messageBytes, 0, messageBytes.Length); } }
private static bool Send304OrImageFromCache(Stream stream, DateTime ifModifiedSince, FileInfo fileInfo, bool logo = false) { if (ifModifiedSince != DateTime.MinValue && ifModifiedSince.ToUniversalTime() + TimeSpan.FromSeconds(1) > (fileInfo?.LastWriteTimeUtc ?? DateTime.MinValue)) { WebStats.Increment304Response(); var message = "HTTP/1.1 304 Not Modified\r\n" + $"Date: {DateTime.UtcNow:R}\r\n" + $"Last-Modified: {ifModifiedSince.ToUniversalTime() - TimeSpan.FromSeconds(1):R}\r\n"; var messageBytes = Encoding.UTF8.GetBytes($"{message}\r\n"); stream.Write(messageBytes, 0, messageBytes.Length); return(true); } if (fileInfo == null) { return(false); } if (!logo) { WebStats.AddCacheDownload(fileInfo.Length); } else { WebStats.AddLogoDownload(fileInfo.Length); } SendImage(stream, fileInfo); return(true); }
private static void ProcessLogoRequest(Stream stream, string asset, DateTime ifModifiedSince) { WebStats.IncrementLogoRequestReceived(); var fileInfo = new FileInfo($"{Helper.Epg123LogosFolder}\\{asset.Substring(7)}"); if (fileInfo.Exists && Send304OrImageFromCache(stream, ifModifiedSince, fileInfo, true)) { return; } // nothing to give the client WebStats.Increment404Response(); var messageBytes = Encoding.UTF8.GetBytes($"HTTP/1.1 404 Not Found\r\nDate: {DateTime.UtcNow:R}\r\n\r\n"); stream.Write(messageBytes, 0, messageBytes.Length); }
private static void ProcessFileRequest(Stream stream, string asset) { string filepath = null, type = "text/xml", disposition = "attachment"; switch (asset) { case "/trace.log": filepath = Helper.Epg123TraceLogPath; type = "text/plain"; disposition = "inline"; break; case "/server.log": filepath = Helper.Epg123ServerLogPath; type = "text/plain"; disposition = "inline"; break; case "/output/epg123.mxf": filepath = Helper.Epg123MxfPath; break; case "/output/epg123.xmltv": filepath = Config.GetXmltvPath(); break; } if (filepath != null) { WebStats.IncrementFileRequestReceived(); SendFile(stream, new FileInfo(filepath), type, disposition); } else { var messageBytes = Encoding.UTF8.GetBytes($"HTTP/1.1 404 Not Found\r\nDate: {DateTime.UtcNow:R}\r\n\r\n"); stream.Write(messageBytes, 0, messageBytes.Length); } }
public static bool RefreshTokenFromSD() { if (DateTime.Now - lastRefresh < TimeSpan.FromMinutes(1)) { return(true); } // get username and passwordhash var config = Config.GetEpgConfig(); if (config?.UserAccount == null) { goto End; } // create the request with headers var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new TokenRequest { Username = config.UserAccount.LoginName, PasswordHash = config.UserAccount.PasswordHash })); var req = (HttpWebRequest)WebRequest.Create($"{Helper.SdBaseName}/token"); req.UserAgent = "EPG123"; req.Method = "POST"; req.ContentType = "application/json"; req.Accept = "application/json"; req.ContentLength = body.Length; req.AutomaticDecompression = DecompressionMethods.Deflate; req.Timeout = 3000; // write the json username and password hash to the request stream var reqStream = req.GetRequestStream(); reqStream.Write(body, 0, body.Length); reqStream.Close(); // send request and get response try { using (var response = req.GetResponse() as HttpWebResponse) using (var sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8)) { var ret = JsonConvert.DeserializeObject <TokenResponse>(sr.ReadToEnd()); if (ret == null || ret.Code != 0) { goto End; } WebStats.IncrementTokenRefresh(); using (var key = Registry.LocalMachine.CreateSubKey(@"SOFTWARE\GaRyan2\epg123", RegistryKeyPermissionCheck.ReadWriteSubTree)) { if (key != null) { key.SetValue("token", ret.Token); key.SetValue("tokenExpires", $"{ret.Datetime.AddDays(1):R}"); Helper.WriteLogEntry("Refreshed token upon receiving an UNKNOWN_USER (5004) error code."); lastRefresh = DateTime.Now; return(GoodToken = true); } } } } catch (WebException ex) { Helper.WriteLogEntry($"SD API WebException Thrown. Message: {ex.Message} , Status: {ex.Status}"); } End: Helper.WriteLogEntry("Failed to refresh token upon receiving an UNKNOWN_USER (5004) error code."); return(GoodToken = false); }
private void HandleRequestError(Stream stream, string asset, WebResponse webResponse, DateTime ifModifiedSince, MemoryStream memStream, bool retry) { using (var sr = new StreamReader(memStream, Encoding.UTF8)) using (var response = webResponse as HttpWebResponse) { var statusCode = (int)response.StatusCode; var statusDescription = response.StatusDescription; var resp = sr.ReadToEnd(); var err = new BaseResponse(); try { err = JsonConvert.DeserializeObject <BaseResponse>(resp); switch (err.Code) { case 5000: // IMAGE_NOT_FOUND statusCode = 404; statusDescription = "Not Found"; break; case 5002: // MAX_IMAGE_DOWNLOADS lock (_limitLock) _limitExceeded = WebStats.LimitLocked = true; statusCode = 429; statusDescription = "Too Many Requests"; break; case 5004: // UNKNOWN_USER bool refresh; lock (_tokenLock) refresh = TokenService.GoodToken && TokenService.RefreshToken && !retry && TokenService.RefreshTokenFromSD(); if (refresh) { WebStats.AddSdDownload(0); ProcessImageRequest(stream, asset, ifModifiedSince, true); return; } statusCode = 401; statusDescription = "Unauthorized"; break; } } catch { // do nothing } switch (statusCode) { case 401: WebStats.Increment401Response(); break; case 404: WebStats.Increment404Response(); break; case 429: WebStats.Increment429Response(); break; case 502: WebStats.Increment502Response(); break; default: WebStats.IncrementOtherResponse(); break; } // log the error response if (!(err.Code == 5002 && _limitExceeded) && !(err.Code == 5004 && !TokenService.GoodToken)) { Helper.WriteLogEntry($"{asset}: {statusCode} {statusDescription}{(!string.IsNullOrEmpty(resp) ? $"\n{resp}" : "")}"); } // send response header and body to client var message = $"HTTP/1.1 {statusCode} {statusDescription}\r\n"; for (var i = 0; i < response.Headers.Count; ++i) { message += $"{response.Headers.Keys[i]}: {response.Headers[i]}\r\n"; } var messageBytes = Encoding.UTF8.GetBytes($"{message}\r\n"); stream.Write(messageBytes, 0, messageBytes.Length); if (string.IsNullOrEmpty(resp)) { return; } var body = Encoding.UTF8.GetBytes(resp); stream.Write(body, 0, body.Length); } }
private void ProcessImageRequest(Stream stream, string asset, DateTime ifModifiedSince, bool retry = false) { // update web stats if (ifModifiedSince == DateTime.MinValue) { WebStats.IncrementRequestReceived(); } else { WebStats.IncrementConditionalRequestReceived(); } // throttle refreshing images that have already been checked recently if (JsonImageCache.cacheImages && JsonImageCache.IsImageRecent(asset.Substring(7), ifModifiedSince)) { if (Send304OrImageFromCache(stream, ifModifiedSince, JsonImageCache.GetCachedImage(asset))) { return; } } // build url for Schedules Direct with token var url = $"{Helper.SdBaseName}{asset}"; if (!string.IsNullOrEmpty(TokenService.Token)) { url += $"?token={TokenService.Token}"; } // create web request var request = (HttpWebRequest)WebRequest.Create(url); // add if-modified-since as needed var fileInfo = JsonImageCache.cacheImages ? JsonImageCache.GetCachedImage(asset) : null; if (ifModifiedSince != DateTime.MinValue || fileInfo != null) { request.IfModifiedSince = new[] { ifModifiedSince.ToUniversalTime(), fileInfo?.LastWriteTimeUtc ?? DateTime.MinValue }.Max(); } try { // update web stats if (request.IfModifiedSince == DateTime.MinValue) { WebStats.IncrementRequestSent(); } else { WebStats.IncrementConditionalRequestSent(); } // get response using (var response = request.GetResponseWithoutException() as HttpWebResponse) using (var memStream = new MemoryStream()) { response.GetResponseStream().CopyTo(memStream); memStream.Position = 0; // if image is included if (memStream.Length > 0 && response.ContentType.StartsWith("image")) { // update web stats WebStats.AddSdDownload(memStream.Length); // success implies limit has not been exceeded lock (_limitLock) { _limitExceeded = WebStats.LimitLocked = false; } // send response to client var message = $"HTTP/1.1 {(int) response.StatusCode} {response.StatusDescription}\r\n"; for (var i = 0; i < response.Headers.Count; ++i) { message += $"{response.Headers.Keys[i]}: {response.Headers[i]}\r\n"; } var messageBytes = Encoding.UTF8.GetBytes($"{message}\r\n"); stream.Write(messageBytes, 0, messageBytes.Length); memStream.CopyTo(stream); if (!JsonImageCache.cacheImages) { return; } // save image to cache if enabled memStream.Position = 0; var filename = asset.Substring(7); var dirInfo = Directory.CreateDirectory($"{Helper.Epg123ImageCache}\\{filename.Substring(0, 1)}"); var location = $"{dirInfo.FullName}\\{filename}"; using (var fStream = new FileStream(location, FileMode.Create, FileAccess.Write)) { memStream.CopyTo(fStream); fStream.Flush(); } File.SetLastWriteTimeUtc(location, response.LastModified); JsonImageCache.AddImageToCache(filename, response.LastModified); return; } // handle any 304 or 404 responses by providing either a 304 response or the cached image if available if (Send304OrImageFromCache(stream, ifModifiedSince, fileInfo)) { return; } // reject a 200 response that has no content if (response.StatusCode == HttpStatusCode.OK && memStream.Length == 0) { WebStats.Increment502Response(); var message = "HTTP/1.1 502 Bad Gateway\r\n" + $"Date: {DateTime.UtcNow:R}\r\n"; var messageBytes = Encoding.UTF8.GetBytes($"{message}\r\n"); stream.Write(messageBytes, 0, messageBytes.Length); return; } // handle any errors from SD HandleRequestError(stream, asset, response, ifModifiedSince, memStream, retry); // 200 for exceeded image limit or unknown user return; } } catch (WebException e) { Helper.WriteLogEntry($"{asset}\nWebException: {e.Status} - {e.Message}"); } // fallback for failed connection if (Send304OrImageFromCache(stream, ifModifiedSince, fileInfo)) { return; } // nothing to give the client WebStats.Increment404Response(); var messageBytes2 = Encoding.UTF8.GetBytes($"HTTP/1.1 404 Not Found\r\nDate: {DateTime.UtcNow:R}\r\n\r\n"); stream.Write(messageBytes2, 0, messageBytes2.Length); }