Segueixo pulint el tema del programa que controla el bit-rotting i la coherència de la còpia de seguretat. Tinc una tasca que triga força en executar-se i per no deixar l'interface d'usuari sense activitat (que et sorgeix el dubte... s'ha penjat o està fent algo?) aleshores millor mostrar un percentatge de progres.
El procés s'executa en un thread diferent del de l'interface d'usuari i consisteix en un bucle que executa consultes contra la BBDD. Es sap quantes consultes cal fer i dins del bucle que les fa es sap quantes en portes. Aleshores hi ha l'opció de calcular el percentatge a cada volta de bucle, però això és fer el càlcul massa sovint, també vaig fer l'opció de calcular cada "n" voltes de bucle però això depèn de si hi ha moltes consultes o poques. Aquestes dues opcions no em van agradar, jo volia que cada cert temps em digui el progres i aleshores vaig pensar en un timer. Cada n segons, 5 per exemple, que em digui que tal va.
I clar, com es posa un timer en un thread en C#? El cert és que m'ha resultat més fàcil del que hem pensava. Això de programar per que et ve degust està molt bé, perquè fas el que vols amb el temps que vols, es a dir fins que et canses. Podria haver estat un problema més difícil i tindria que haver dedicat més temps, però no, fàcil, fàcil. D'aquí poc com l'Arguiñano, rico, rico :D
Cal posar el using dels Timers, les variables globals que gestionen el càlcul del progres, la variable del Timer, inicialitzar-ho tot plegat, i fer la rutineta que fa el càlcul (UpdateProgress) i presto! Un Timer. No està de més, en catch de quan el Thread està sent abortat aturar el timer.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
namespace Dir
{
class clsBuscaRepes
{
public frmInterface Interface = null;
publicdecimal TantPerCentProgress = 0;
privateint Progress = 0;
privateint Total = 0;
private System.Timers.Timer Tmr = new System.Timers.Timer();
publicvoid BuscaRepes()
{
try
{
clsConfig Config = new clsConfig();
string SQL = "";
DataTable TBLParaules, TBLDuplicats, TBLFitxer, TBLVerificats;
string CadConn = "";
bool UseBBDD = false;
bool Primer = true;
SqlConnection Conn = null;
string IdFitxerActual = "", IdFitxerAnterior = "";
int NumRepes = 0;
string HashActual = "";
string HashAnterior = "";
clsSQLServer SQLSvr = new clsSQLServer();
Tmr.Interval = 1000;
Tmr.Elapsed += new ElapsedEventHandler(UpdateProgress);
Config.Initialize();
UseBBDD = bool.Parse(Config.GetVal("UseBBDD"));
Interface.Log("Començant buscar fitxers repetits");
if (UseBBDD)
{
Conn = newSqlConnection();
CadConn = Config.GetVal("CadConn");
Conn.ConnectionString = CadConn;
Conn.Open();
Interface.Log("Conectat a BBDD: " + Conn.DataSource);
SQLSvr.CadConn = CadConn;
SQLSvr.Conn = Conn;
Progress = 0;
Total = 0;
SQL = "DELETE FROM ResultatsDuplicats";
SQLSvr.ExecNonQuery(SQL);
Interface.Log("Iniciant duplicats per hash");
SQL = "SELECT * FROM Fitxers WHERE Hash IN(SELECT Hash FROM fitxers "
SQL += "GROUP BY Hash HAVING ( COUNT(Hash) > 1 )) ORDER BY Hash";
TBLDuplicats = SQLSvr.Exec(SQL);
foreach (DataRow DR in TBLDuplicats.Rows)
{
HashActual = DR["Hash"].ToString();
if (HashActual == HashAnterior)
{
IdFitxerActual = DR["IdFitxer"].ToString();
SQL = "SELECT * FROM Verificats WHERE IdFitxer=" + IdFitxerAnterior + " ";
SQL += "AND IdFitxerVerificat=" + IdFitxerActual;
TBLVerificats = SQLSvr.Exec(SQL);
if (TBLVerificats.Rows.Count == 0)
{
SQL = "SELECT * FROM Fitxers WHERE IdFitxer=" + IdFitxerAnterior;
TBLFitxer = SQLSvr.Exec(SQL);
Interface.Log("Duplicat, fitxer actual: " + TBLFitxer.Rows[0]["IdFitxer"].ToString());
Interface.Log(", " + TBLFitxer.Rows[0]["Nom"].ToString());
Interface.Log("Duplicat: " + DR["IdFitxer"].ToString() + ", " + DR["Nom"].ToString());
Interface.Log("--------------------------------------------------");
SQL = "INSERT INTO ResultatsDuplicats (IdFitxer, IdFitxerPossibleDuplicat) "
SQL += "VALUES (" + IdFitxerAnterior + ", " + DR["IdFitxer"].ToString() + ");";
SQLSvr.ExecNonQuery(SQL);
NumRepes++;
}
}
else
{
HashAnterior = HashActual;
IdFitxerAnterior = DR["IdFitxer"].ToString();
}
}
SQL = "SELECT Paraula, IdFitxer FROM Paraules ORDER BY IdFitxer";
TBLParaules = SQLSvr.Exec(SQL);
SQL = "";
IdFitxerAnterior = "";
IdFitxerActual = "";
Total = TBLParaules.Rows.Count;
//Començar el timer
Tmr.Start();
foreach (DataRow Paraula in TBLParaules.Rows)
{
IdFitxerActual = Paraula["IdFitxer"].ToString();
if (IdFitxerActual == IdFitxerAnterior || Primer)
{
SQL += " AND Nom LIKE '%" + Paraula["Paraula"].ToString().Replace("'", "''") + "%'";
if (Primer)
{
Primer = false;
IdFitxerAnterior = IdFitxerActual;
}
}
else
{
SQL = "SELECT * FROM Fitxers WHERE IdCarpeta IN ";
SQL += "(SELECT IdCarpeta FROM Carpetes WHERE Duplicats=1) ";
SQL += "AND IdFitxer<>" + IdFitxerAnterior + " " + SQL;
TBLDuplicats = SQLSvr.Exec(SQL);
foreach (DataRow Duplicat in TBLDuplicats.Rows)
{
SQL = "SELECT * FROM Verificats ";
SQL += " WHERE IdFitxer=" + IdFitxerAnterior + " ";
SQL += "AND IdFitxerVerificat=" + Duplicat["IdFitxer"].ToString();
TBLVerificats = SQLSvr.Exec(SQL);
if (TBLVerificats.Rows.Count == 0)
{
SQL = "SELECT * FROM Fitxers WHERE IdFitxer=" + IdFitxerAnterior;
TBLFitxer = SQLSvr.Exec(SQL);
Interface.Log("Duplicat, fitxer actual: " + TBLFitxer.Rows[0]["IdFitxer"].ToString());
Interface.Log(", " + TBLFitxer.Rows[0]["Nom"].ToString());
Interface.Log("Duplicat: " + Duplicat["IdFitxer"].ToString() + ", " + Duplicat["Nom"].ToString());
Interface.Log("--------------------------------------------------");
SQL = "INSERT INTO ResultatsDuplicats (IdFitxer, IdFitxerPossibleDuplicat) "
SQL += " VALUES (" + IdFitxerAnterior + ", " + Duplicat["IdFitxer"].ToString() + ");";
SQLSvr.ExecNonQuery(SQL);
NumRepes++;
}
}
SQL = " AND Nom LIKE '%" + Paraula["Paraula"].ToString().Replace("'", "''") + "%'";
IdFitxerAnterior = IdFitxerActual;
}
Progress++;
}
}
Tmr.Stop();
Interface.Log("Total repetits: " + NumRepes);
Interface.BuscaRepesFinished();
}
catch (ThreadAbortException Ex)
{
if (Tmr.Enabled) Tmr.Stop();
}
catch(Exception Err)
{
Interface.Log("Error: " + Err.Message);
}
}
publicvoid UpdateProgress(object sender, System.Timers.ElapsedEventArgs e)
{
try
{
TantPerCentProgress = (decimal)(Progress * 100) / (decimal)Total;
Interface.Log("Processades " + TantPerCentProgress.ToString("N2") + "% de paraules");
}
catch(Exception Err)
{
}
}
}
}
#13/03/2018 18:35 Programació C# Autor: Alex Canalda
Depenent del projecte on estem a vegades no cal muntar classes d'accés a dades, capes i capes de software inútil per executar una mera consulta. També cal valorar la mantenibilitat, allò de que les capes afavoreixen el manteniment, jo sempre he trobat que entorpeixen el manteniment... Però sí que crec necessari que les consultes, encara que siguin un string en el codi, siguin gestionades de forma centralitzada. És en aquest punt quan va sorgir la clsSQLServer. D'això ja fa 5 o més anys... però no se per que no l'he posat al blog fins ara. Bé de fet, l'he ampliat fa pocs dies, portava des del 2013 sense modificacions.
Aquesta classe com el seu nom indica gestiona les consultes al servidor de base de dades. De fet no se com Microsoft no l'han fet ells mateixos. Vull dir que com programador el que vull és executar una sentència SQL i tenir una taula de retorn, sense preocupar-me de si és un Adapter o un Command o X, però bé, ja que MS no s'inspira ho fet jo. Té uns quants mètodes principals i sobrecarregats:
Exec: Executa un consulta SQL i retorna una taula. Es pot escollir entre no preocupar-se per la connexió, o sí, o afegir-hi una transacció.
ExecInsertWithIdentity: Executa un INSERT contra una taula que tingui una clau primària (PK) de tipus identitat. I un cop fet l'INSERT retorna el valor de la PK que ha creat com un long.
ExecNonQuery: Serveix per executar consultes que no retornen res, tipus UPDATEs, DELETEs o INSERTs que no cal recuperar la PK. Retorna els registres afectats per la consulta.
ExecScalar: Serveix per executar consultes que retornen un valor sencer, com un màxim, un mínim, un COUNT etc... De moment no m'ha fet falta un altra tipus de dada en el retorn.
FDateSQL: Aquesta és una utilitat per treballar amb dates en SQL Server. Retorna la data en un string en format ISO 8601: yyyy-mm-ddThh:mi:ss. Aquest format el bo que té és que no depèn dels regional settings.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Data;
using System.IO;
using System.Configuration;
namespace Viewer
{
publicclass clsSQLServer
{
public clsSQLServer()
{
Conn = null;
}
publicSqlConnection Conn { get; set; }
privatestring _Ver = "07/03/2018";
publicstring Ver
{
get
{
return _Ver;
}
}
publicstring CadConn { get; set; }
publicstring FDateSQL(DateTime D)
{
//ISO 8601: yyyy-mm-ddThh:mi:ssreturn D.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss");
}
/// <summary>/// Executa una SQL que retorna resultats contra una conexió, no modifica l'estat de la conexió ni de la transaccio/// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <param name="SqlConn">Conexió a BBDD, ha d'estar oberta</param>/// <param name="SqlTrann">Transacció ja creada sobre la conexió (admet null)</param>/// <returns>Retorna un NEW DataTable</returns>publicDataTable Exec(string SQL, SqlConnection SqlConn, SqlTransaction SqlTrann)
{
DataTable TBL = newDataTable();
SqlCommand SqlComm = newSqlCommand();
SqlDataReader SqlReader;
SqlComm.Connection = SqlConn;
if (SqlTrann != null) SqlComm.Transaction = SqlTrann;
SqlComm.CommandText = SQL;
SqlComm.CommandType = CommandType.Text;
SqlReader = SqlComm.ExecuteReader();
TBL.Load(SqlReader);
return TBL;
}
/// <summary>/// Executa una SQL que retorna resultats contra una conexió, no modifica l'estat de la conexió/// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <param name="SqlConn">Conexió a BBDD, ha d'estar oberta</param>/// <returns>Retorna un NEW DataTable</returns>publicDataTable Exec(string SQL, SqlConnection SqlConn)
{
return Exec(SQL, SqlConn, null);
}
/// <summary>/// Executa una sentència SQL que retorna resultats, si està informada la variable Conn la fa/// servir de conexio (ha d'estar oberta), si no crea una i la tanca/// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <returns>Retorna un NEW DataTable</returns>publicDataTable Exec(string SQL)
{
DataTable TBL;
SqlConnection SqlConn;
bool Local = false;
if (Conn != null)
{
SqlConn = Conn;
}
else
{
Local = true;
SqlConn = newSqlConnection();
SqlConn.ConnectionString = CadConn;
SqlConn.Open();
}
TBL = Exec(SQL, SqlConn);
if (Local) SqlConn.Close();
return TBL;
}
/// <summary>/// Executa un INSERT en una taula que té PK identitat. Retorna la identitat creada. /// Obre i tanca conexió a BBDD (la que retorna GetConnStr)/// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <returns>Identitat creada</returns>publiclong ExecInsertWithIdentity(string SQL)
{
long Identity = 0;
SqlConnection SqlConn;
bool Local = false;
if (Conn != null)
{
SqlConn = Conn;
}
else
{
Local = true;
SqlConn = newSqlConnection();
SqlConn.ConnectionString = CadConn;
SqlConn.Open();
}
Identity = ExecInsertWithIdentity(SQL, SqlConn);
if (Local) SqlConn.Close();
return Identity;
}
/// <summary>/// Executa un INSERT en una taula que té PK identitat. /// Retorna la identitat creada. No modifica l'estat de la connexió/// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <param name="SqlConn">Conexió a BBDD, ha d'estar oberta</param>/// <returns>Identitat creada</returns>publiclong ExecInsertWithIdentity(string SQL, SqlConnection SqlConn)
{
long Identity = 0;
Identity = ExecInsertWithIdentity(SQL, SqlConn, null);
return Identity;
}
/// <summary>/// Executa un INSERT en una taula que té PK identitat. Retorna la identitat creada/// </summary>/// <param name="SQL">Sentència INSERT SQL a executar</param>/// <param name="SqlConn">Conexió a BBDD, ha d'estar oberta</param>/// <param name="SqlTrann">Transacció ja creada sobre la conexió (admet null)</param>/// <returns>Identitat creada</returns>publiclong ExecInsertWithIdentity(string SQL, SqlConnection SqlConn, SqlTransaction SqlTrann)
{
long Identity = 0;
SqlCommand SqlComm = newSqlCommand();
SqlComm.Connection = SqlConn;
if (SqlTrann != null) SqlComm.Transaction = SqlTrann;
if (!SQL.EndsWith(";")) SQL += ";";
SQL += " SELECT SCOPE_IDENTITY();";
SqlComm.CommandText = SQL;
SqlComm.CommandType = CommandType.Text;
Identity = (long)(decimal)SqlComm.ExecuteScalar();
return Identity;
}
/// <summary>/// Executa una sentència SQL que no retorna resultats, només els registres afectats. /// Obre i tanca conexió a BBDD (la que retorna GetConnStr)/// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <returns>Registres afectats</returns>publicint ExecNonQuery(string SQL)
{
int Results = 0;
SqlConnection SqlConn;
bool Local = false;
if (Conn != null)
{
SqlConn = Conn;
}
else
{
Local = true;
SqlConn = newSqlConnection();
SqlConn.ConnectionString = CadConn;
SqlConn.Open();
}
Results = ExecNonQuery(SQL, SqlConn);
if (Local) SqlConn.Close();
return Results;
}
/// <summary>/// Executa una sentència SQL que no retorna resultats/// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <param name="SqlConn">Conexió a BBDD, ha d'estar oberta</param>/// <returns>Registres afectats</returns>publicint ExecNonQuery(string SQL, SqlConnection SqlConn)
{
return ExecNonQuery(SQL, SqlConn, null);
}
/// <summary>/// Executa una sentència SQL que no retorna resultats/// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <param name="SqlConn">Conexió a BBDD, ha d'estar oberta</param>/// <param name="SqlTrann">Transacció ja creada sobre la conexió (admet null)</param>/// <returns>Registres afectats</returns>publicint ExecNonQuery(string SQL, SqlConnection SqlConn, SqlTransaction SqlTrann)
{
int Results = 0;
SqlCommand SqlComm = newSqlCommand();
SqlComm.Connection = SqlConn;
if (SqlTrann != null) SqlComm.Transaction = SqlTrann;
SqlComm.CommandText = SQL;
SqlComm.CommandType = CommandType.Text;
Results = SqlComm.ExecuteNonQuery();
return Results;
}
/// <summary>/// Executa una sentència SQL que no retorna resultats, només la primera columna del primer registre./// Obre i tanca conexió a BBDD (la que retorna GetConnStr)/// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <returns>Registres afectats</returns>publicint ExecScalar(string SQL)
{
int Results = 0;
SqlConnection SqlConn;
bool Local = false;
if (Conn != null)
{
SqlConn = Conn;
}
else
{
Local = true;
SqlConn = newSqlConnection();
SqlConn.ConnectionString = CadConn;
SqlConn.Open();
}
Results = ExecScalar(SQL, SqlConn);
if (Local) SqlConn.Close();
return Results;
}
/// <summary>/// Executa una sentència SQL que no retorna resultats, només la primera columna del primer registre./// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <param name="SqlConn">Conexió a BBDD, ha d'estar oberta</param>/// <returns>Registres afectats</returns>publicint ExecScalar(string SQL, SqlConnection SqlConn)
{
return ExecScalar(SQL, SqlConn, null);
}
/// <summary>/// Executa una sentència SQL que no retorna resultats, només la primera columna del primer registre./// </summary>/// <param name="SQL">Sentència SQL a executar</param>/// <param name="SqlConn">Conexió a BBDD, ha d'estar oberta</param>/// <param name="SqlTrann">Transacció ja creada sobre la conexió (admet null)</param>/// <returns>Registres afectats</returns>publicint ExecScalar(string SQL, SqlConnection SqlConn, SqlTransaction SqlTrann)
{
int Results = 0;
object objResult;
SqlCommand SqlComm = newSqlCommand();
SqlComm.Connection = SqlConn;
if (SqlTrann != null) SqlComm.Transaction = SqlTrann;
SqlComm.CommandText = SQL;
SqlComm.CommandType = CommandType.Text;
objResult = SqlComm.ExecuteScalar();
if (objResult != null && objResult != DBNull.Value)
{
Results = int.Parse(objResult.ToString());
}
return Results;
}
}
}
#08/03/2018 12:46 Programació C# SQLServer Autor: Alex Canalda
Desenvolupant l'aplicació que verifica el bitrotting dels fitxers m'he trobat en la necessitat d'iniciar diferents threads, un thread per cada disc dur
En total al servidor hi ha 8 discos, 4+4, cal verificar un fitxer i la seva còpia contra el seu hash guardat a la BBDD. Això vol dir 8 threads que fan el mateix, calcular hashs de fitxers. Enlloc d'escriure el mateix 8 cops vaig pensar de fer un bucle per iniciar-los.
Bé? Malament. Els threads són molt tisquimiquis amb les variables que els inicien i el seu valor, ja que si són d'un context comú aleshores es trepitgen. Però millor un exemple, primer un codi que no funciona:
int i = 0;
clsVerificador Verificadors = null;
foreach (DataRow DR in TBL.Rows)
{
Verificadors = newclsVerificador(this, DR.Field<int>("IdUnitat"), DR["Path"].ToString(), i);
threadsVerificadors[i] = new Thread(delegate() { Verificadors.VerificaHashes(); });
threadsVerificadors[i].Start();
i++;
Verificadors = newclsVerificador(this, DR.Field<int>("IdUnitat"), DR["PathBackup"].ToString(), i);
threadsVerificadors[i] = new Thread(delegate() { Verificadors.VerificaHashBackup(); });
threadsVerificadors[i].Start();
i++;
}
Sembla correcte, i de fet si s'executa només amb un thread funciona bé. Però no. Resulta que quan el thread comença a executar-se han passat uns nanosegons des de que va deixar el codi principal, i agafa els valors de les variables en aquell moment. Com el bucle va més ràpid que la creació dels threads aleshores tots els threads tenen el valor de les variables de la última volta del bucle. Quina és la solució? Obligar al compilador a crear noves variables a cada volta del bucle. Així:
Aquest seria el codi que funciona:
clsVerificador[] Verificadors = newclsVerificador[8];
foreach(DataRow DR in TBL.Rows)
{
int j = i;
Verificadors[j] = newclsVerificador(this, DR.Field<int>("IdUnitat"), DR["Path"].ToString(), j);
threadsVerificadors[j] = new Thread(delegate() { Verificadors[j].VerificaHashes(); });
threadsVerificadors[j].Start();
i++;
int k = i;
Verificadors[k] = newclsVerificador(this, DR.Field<int>("IdUnitat"), DR["PathBackup"].ToString(), k);
threadsVerificadors[k] = new Thread(delegate() { Verificadors[k].VerificaHashBackup(); });
threadsVerificadors[k].Start();
i++;
}
Es declaren dins del bucle variables, i així són noves. També cal fer que la variable que el "new" sigui nova cada cop. Es pot fer declarant-la dins del bucle o fent-la un array. Funciona!
#08/01/2018 18:58 Programació C# Autor: Alex Canalda
Últimament estic aficionat a fer aplicacions amb Threads, a menys que tingui molta pressa evito fer servir el Aplication.DoEvents() i faig un nou Thread que fa la funció.
Alehores he estat buscant com passar paràmetres a la funció que ha d'executar el Thread. I això es fa amb un "delegate", ja que el Thread espera un delegate d'aquests.
I un "delegate" que és? Segons la definició és un tipus de dada. Aquest tipus de dada és un punter a una funció, però més segur que un punter ja que verifica els tipus dels paràmetres. Un exemple:
//Es defineix el tipus de dadapublicdelegatevoid Del(string message);
// Aquesta és la funció dummy per al delegate.publicstaticvoid DelegateMethod(string message)
{
System.Console.WriteLine(message);
}
// Definim una variable "handler" que és del tipus Del
(el tipus Del està definit prèviament com un delegat a una funció que rep un string)
Del handler = DelegateMethod;
// Call the delegate.
handler("Hello World");
Aleshores, per invocar a un Thread hi ha 2 formes de fer-ho:
staticvoid ProcessPerson(Person p)
{
Console.WriteLine("Id :{0},Name :{1}", p.Id, p.Name);
}
// C# 2staticvoid Main()
{
Person p = new Person();
p.Id = "cs0001";
p.Name = "William";
Thread th = new Thread(delegate() { ProcessPerson(p); });
th.Start();
}
// C# 3staticvoid Main()
{
Person p = new Person();
p.Id = "cs0001";
p.Name = "William";
Thread th = new Thread(() => ProcessPerson(p));
th.Start();
}
A mi m'agrada més l'opció 2 de C# sense "lambda expressión" ja que queda més clar que està fent.
#12/07/2016 11:34 Programació C# Autor: Alex Canalda
Recentment he tingut que fer un mini-webserver, multithread per tal que faci d'sparring d'una altra aplicació. Algo fàcil de fer servir, ràpid de configurar, i que respongui a les peticions un contingut fàcil de modificar. Així que he fet al Listener. No té suport per SSL, encara que no és difícil afegir-li.
Té un formulari que li fa d'interface d'usuari i dues classes principals.
HttpServer: Aquesta classes es dedica a tenir un Socket que escolta peticions entrants. Per cada petició entrant instancia una HttpProcessor que s'encarrega de servir la petició en el seu propi thread.
HttpProcessor: Aquesta classes s'encarrega d'atendre una petició. La parseja i li serveix el que demana, ja sigui un GET o un POST.
El codi més interessant del HttpServer és:
publicvoid Listen() {
IPAddress localhost = IPAddress.Any;
listener = new TcpListener(localhost, port);
listener.Start();
ThreadPool.SetMaxThreads(NumThreads, NumThreads);
while (is_active) {
if (listener.Pending())
{
TcpClient client = listener.AcceptTcpClient();
HttpProcessor processor = new HttpProcessor(client, this);
ThreadPool.QueueUserWorkItem(processor.process);
}
else
{
Thread.Sleep(100);
}
}
}
El HttpProcessor per la seva banda:
class HttpProcessor : IDisposable
{
public TcpClient socket { get; set; }
public HttpServer server { get; set; }
private BufferedStream inputStream;
private StreamWriter outputStream;
privatebool isDisposing = false;
privatestring http_contenttype { get; set; }
privatestring http_method { get; set; }
privatestring http_url { get; set; }
privatestring http_protocol_versionstring { get; set; }
privateDictionary<string, string> httpHeaders = newDictionary<string, string>();
privateDictionary<string, string> urlParameters = newDictionary<string, string>();
privatestaticint MAX_POST_SIZE = 10 * 1024 * 1024; // 10MBpublicvoid Dispose()
{
if (!isDisposing)
{
isDisposing = true;
if (inputStream != null)
{
inputStream.Dispose();
inputStream = null;
}
if (outputStream != null)
{
if (outputStream.BaseStream != null)
{
try
{
outputStream.Dispose();
}
catch
{
}
}
outputStream = null;
}
if (socket != null)
{
socket.Close();
socket = null;
}
}
}
public HttpProcessor(TcpClient s, HttpServer srv)
{
this.socket = s;
this.server = srv;
}
privatestring streamReadLine(BufferedStream PinputStream)
{
int next_char = 0;
string data = "";
int i = 0;
while (i < 10000 && next_char != -1)
{
next_char = PinputStream.ReadByte();
if (next_char != -1)
{
if (next_char == '\n')
{
break;
}
else
{
if (next_char != '\r')
{
data += Convert.ToChar(next_char);
}
}
}
i++;
}
return data;
}
publicvoid process(Object threadContext)
{
try
{
inputStream = new BufferedStream(socket.GetStream());
outputStream = new StreamWriter(socket.GetStream(), Encoding.Default);
//HttpGetProcessor httpGetProcessor;//HttpPostProcessor httpPostProcessor;
parseRequest();
readHeaders();
server.IFace.SetResultsValue("URL: " + http_url);
server.IFace.SetResultsValue("Method: " + http_method);
server.IFace.SetResultsValue("Headers ---------");
foreach(KeyValuePair<string, string> Item in httpHeaders)
{
server.IFace.SetResultsValue(Item.Key + ": " + Item.Value);
}
switch (http_method)
{
case"GET":
//httpGetProcessor = new HttpGetProcessor
(http_url, httpHeaders, urlParameters, outputStream, server);//httpGetProcessor.ProcessGETRequest();
EscriureResposta();
break;
case"POST":
//Carrega totes les dades del POST//Dictionary<string, string> PostValues = ReadPostContents();//httpPostProcessor = new HttpPostProcessor
(http_url, httpHeaders, urlParameters, outputStream, server, PostValues);//httpPostProcessor.ProcessPOSTRequest();
server.IFace.SetResultsValue("--------------- POST -------------");
server.IFace.SetResultsValue(ReadPostContents());
EscriureResposta();
break;
case"HEAD":
case"PUT":
case"DELETE":
case"TRACE":
case"CONNECT":
server.IFace.SetResultsValue("Verb HTTP no suportat: " + http_method);
break;
case"UNKNOWN":
server.IFace.SetResultsValue("Verb HTTP desconegut: " + http_method);
break;
}
server.IFace.SetResultsValue("CLOSING------");
Dispose();
}
catch (Exception e)
{
server.IFace.SetResultsValue("Exception: " + e.ToString());
Thread.CurrentThread.Join(10000);
}
}
privatevoid EscriureResposta()
{
HttpCodes Codes = new HttpCodes();
string Capcalera = Codes.Success(http_contenttype, "");
string Contingut = server.IFace.GetResponseValue();
Capcalera += Environment.NewLine + "Content-Length: " + Contingut.ToCharArray().Length;
outputStream.Write(Capcalera);
//Aquests writelines separa els headers de la resposta del contigut de la resposta
outputStream.WriteLine("");
outputStream.WriteLine("");
//Envia el contingut
outputStream.Write(Contingut);
outputStream.Flush();
outputStream.Close();
}
privatevoid parseRequest()
{
string request = streamReadLine(inputStream);
request = request.Trim();
if (!string.IsNullOrWhiteSpace(request))
{
string[] tokens = request.Split(' ');
if (tokens.Length != 3)
{
thrownewException("Invalid HTTP request line: ->" + request + "<-");
}
http_method = tokens[0].ToUpper();
http_url = tokens[1];
if (http_url.Contains("?")) parseURLParameters();
http_protocol_versionstring = tokens[2];
}
else
{
http_method = "UNKNOWN";
}
}
privatevoid parseURLParameters()
{
string[] URL = http_url.Split('?');
if(URL.Length != 2)
{
server.IFace.SetResultsValue("ERROR: URL no valida: " + http_url);
}
string[] Parameters = URL[1].Split('&');
string[] Parameter;
http_url = URL[0];
foreach (string item in Parameters)
{
Parameter = item.Split('=');
if(Parameter.Length != 2)
{
server.IFace.SetResultsValue("ERROR: Parametre no valid: " + Parameter);
}
urlParameters.Add(Parameter[0], Parameter[1]);
}
}
privatevoid readHeaders()
{
String line;
while ((line = streamReadLine(inputStream)) != null)
{
line = line.Trim();
if (line.Equals(""))
{
return;
}
int separator = line.IndexOf(':');
if (separator == -1)
{
thrownewException("Invalid HTTP header line: ->" + line + "<-");
}
String name = line.Substring(0, separator);
int pos = separator + 1;
while ((pos < line.Length) && (line[pos] == ' '))
{
pos++; // strip any spaces
}
string value = line.Substring(pos, line.Length - pos);
//Console.WriteLine("header: {0}:{1}",name,value);
httpHeaders.Add(name, value);
}
}
privateconstint BUF_SIZE = 4096;
privatestring ReadPostContents()
{
// this post data processing just reads everything into a memory stream.// this is fine for smallish things, but for large stuff we should really// hand an input stream to the request processor. However, the input stream // we hand him needs to let him see the "end of the stream" at this content // length, because otherwise he won't know when he's seen it all! //Console.WriteLine("get post data start");int content_len = 0;
MemoryStream ms = new MemoryStream();
StreamReader SR = null;
string PostData = "";
//Dictionary<string, string> PostValues;if (httpHeaders.ContainsKey("Content-Length"))
{
content_len = Convert.ToInt32(httpHeaders["Content-Length"]);
if (content_len > MAX_POST_SIZE)
{
thrownewException(String.Format("POST Content-Length({0}) too big for this simple server", content_len));
}
byte[] buf = newbyte[BUF_SIZE];
int to_read = content_len;
while (to_read > 0)
{
//Console.WriteLine("starting Read, to_read={0}",to_read);int numread = this.inputStream.Read(buf, 0, Math.Min(BUF_SIZE, to_read));
//Console.WriteLine("read finished, numread={0}", numread);if (numread == 0)
{
if (to_read == 0)
{
break;
}
else
{
thrownewException("POST Client disconnected during post");
}
}
to_read -= numread;
ms.Write(buf, 0, numread);
}
ms.Seek(0, SeekOrigin.Begin);
}
SR = new StreamReader(ms);
PostData = SR.ReadToEnd();
return PostData;
//PostValues = ProcessPostValues(PostData);//return PostValues;
}
privateDictionary<string, string> ProcessPostValues(string PostData)
{
Dictionary<string, string> PostValues = newDictionary<string, string>();
string EncodedHTTP;
string DecodedString;
string[] Splitter;
string[] ItemSplitter;
Splitter = PostData.Split('&');
for (int i = 0; i < Splitter.Length; i++)
{
ItemSplitter = Splitter[i].Split('=');
if (ItemSplitter.Length == 2)
{
EncodedHTTP = ItemSplitter[1];
DecodedString = WebUtility.UrlDecode(EncodedHTTP);
PostValues.Add(ItemSplitter[0], DecodedString);
}
}
return PostValues;
}
}
També hi ha una classe de support que encapsula els codis HTTP, i el formulari principal fa servir la classe de configuració.
class HttpCodes
{
publicstring SuccessJSON()
{
string Result = "";
Result = "HTTP/1.0 200 OK";
Result += Environment.NewLine + "Content-Type: application/json; charset=iso-8859-1";
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
publicstring SuccessHTML()
{
string Result = "";
Result = "HTTP/1.0 200 OK";
Result += Environment.NewLine + "Content-Type: text/html; charset=iso-8859-1";
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
/// <summary>/// Resposta amb 200 OK/// </summary>/// <param name="ContentType">application/xml, text/xml, text/html</param>/// <param name="Charset">Valors posibles "iso-8859-1" o "" per no enviar cap</param>/// <returns></returns>publicstring Success(string ContentType, string Charset)
{
string Result = "";
Result = "HTTP/1.0 200 OK";
if(string.IsNullOrWhiteSpace(Charset))
{
Result += Environment.NewLine + "Content-Type: " + ContentType;
}
else
{
Result += Environment.NewLine + "Content-Type: " + ContentType + "; charset=" + Charset;
}
//Result += Environment.NewLine + "Content-Type: " + ContentType + "; charset=iso-8859-1";//Result += Environment.NewLine + "Content-Type: application/xml";//Result += Environment.NewLine + "Content-Type: text/xml";
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
publicstring SuccessText()
{
string Result = "";
Result = "HTTP/1.0 200 OK";
Result += Environment.NewLine + "Content-Type: text/plain; charset=iso-8859-1";
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
publicstring Error500()
{
string Result = "";
Result = "HTTP/1.0 500 Internal Server Error";
Result += Environment.NewLine + "Content-Type: text/plain; charset=iso-8859-1";
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
/// <summary>/// Escriu un 200 OK amb el content type que toca/// </summary>/// <param name="IMG">jpg, gif, png</param>publicstring SuccessIMG(string IMG)
{
string Result = "";
Result = "HTTP/1.0 200 OK";
Result += Environment.NewLine + "Content-Type: image/" + IMG;
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
publicstring SuccessCSS()
{
string Result = "";
Result = "HTTP/1.0 200 OK";
Result += Environment.NewLine + "Content-Type: text/css";
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
publicstring SuccessJS()
{
string Result = "";
Result = "HTTP/1.0 200 OK";
Result += Environment.NewLine + "Content-Type: application/x-javascript";
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
publicstring FileNotFound404()
{
string Result = "";
Result = "HTTP/1.0 404 File not found";
Result += Environment.NewLine + "Content-Type: text/plain";
Result += Environment.NewLine + "Cache-Control: no-cache, no-store, must-revalidate";
Result += Environment.NewLine + "Pragma: no-cache";
Result += Environment.NewLine + "Expires: 0";
Result += Environment.NewLine + "Connection: close";
return Result;
}
publicstring Redirect(string URL)
{
string Result = "";
Result = "HTTP/1.1 302 Found";
Result += Environment.NewLine + "Location: " + URL;
Result += Environment.NewLine + "Connection: close";
return Result;
}
}
El Listener té un germà gran que fa més coses, si algun dia l'acabo ja el publicaré
#31/05/2016 12:30 Programació C# Autor: Alex Canalda
Recentment m'ha tocat barallar-me amb aquest tipus de criptografia. I realment és interessant com encaixen les diferents peces del puzle.
La motivació del PKCS#7 és que els algoritmes d'encriptació asimètrics (els que fan servir clau pública i clau privada) no poden xifrar quantitats grans de dades. Al contrari els algoritmes simètrics (la mateix clau es fa servir per encriptar que per desencriptar) són "bons" xifrant quantitats grans de dades.
Per això el que es fa és el següent: les dades es xifren amb un algoritme simètric fent servir una clau generada aleatòriament que només es fa servir en un missatge. Aquesta clau simètrica és petita i es xifra amb la clau pública d'una algoritme asimètric.
La informació arriba en Base64, que és un tipus de codificació, per la que C# ja té una forma fàcil de gestionar. Es fa amb el Convert, que retorna un array de bytes.
Aquests bytes no són ASCII, són ASN.1 (Abstract Syntax Notation 1) que és un format binari d'encapsular informació. De fet és molt similar al XML, però amb binari. Té TAGs, longitud del TAG i valor del TAG. Apart del tema del BER i el DER (però no vull explicar aquí tot l'estàndard ASN.1). El ASN.1 és antic i mig internet el fa servir (i jo sense saber-ho).
Per donar estructura a un missatge ASN.1 hi ha un llenguatge que defineix les estructures el formen. És el Encoding Control Notation (ECN), seria l'equivalent a un XSD/DTD del XML. Aleshores per rebre un missatge ASN.1 que segueixi un estàndard ha de complir una estructura ECN determinada, concretament el PKCS#7 fa servir el Cryptographic Message Syntax (RFC 5652).
Aleshores, el que defineix aquest RFC 5652 és el format en que han de posar-se les parts del missatge, es a dir:
Les dades xifrades amb l'algoritme simètric.
La clau simètrica feta servir per xifrar les dades.
El vector d'inicialització del xifrat simètric (aquest està en clar, sense xifrar).
Dades de la clau pública emprada per xifrar la clau simètrica, normalment aquesta clau està guardada en un certificat X509, aleshores en les dades s'inclou la informació típica sobre el certificat, email, dades de l'emissor del certificat (país, adreça,...), etc...
Igual que el XML posa noms a les marques, el ASN.1 té OID (Object Identifier), que identifiquen el contingut d'un tag. Aleshores cada un dels punts anteriors queda identificat dins del missatge. En format ASCII i PrettyPrint un missatge PKCS#7 pot quedar:
Per implementar tot això en C# he fet servir la llibreria BouncyCastle en la versió C#, et simplifica la vida el no tenir que batallar amb el ASN.1 ja que la llibreria m'ha permès transformar el ASN.1 a un string normal. I un cop ja tinc l'string m'he buscat la vida per desencriptar-lo amb llibreries .NET normals. He trobat altres llibreries que permeten processar el ASN.1 però cap m'ha permès obtenir el string normal i que posteriorment no hagi de parsejar-lo. Per exemple hi ha una implementació del projecte Mono que et fa un PrettyPrint del text ASN.1. També cal comentar que durant l'encriptació depenent del format en la que tenim la clau pública el ASN.1 generat té un format o un altra, no és el mateix disposar del certificat X509 o de la clau pública en format PEM.
El codi d'encriptació és força senzill. Únicament cal veure si encriptem fent servir un certificat X509 amb totes les dades o bé un fitxer PEM que conté la clau pública.
CmsEnvelopedDataGenerator gen = new CmsEnvelopedDataGenerator();
X509CertificateParser Parser = new X509CertificateParser();
StreamReader sr = new StreamReader(FitxerCertificat);
X509Certificate Cert = Parser.ReadCertificate(sr.BaseStream);
gen.AddKeyTransRecipient(Cert);
/*
byte[] KeyId = new byte[] { 1, 2, 3, 4, 5 };
TextReader TxtRead = File.OpenText(@"c:\Ruta_al_fitxer\Public.txt");
Org.BouncyCastle.OpenSsl.PemReader PubCert =
new Org.BouncyCastle.OpenSsl.PemReader(TxtRead);
Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters RSAParams =
(Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters)PubCert.ReadObject();
gen.AddKeyTransRecipient(RSAParams, KeyId);
*/
CmsProcessableByteArray ByteArr = new CmsProcessableByteArray(OrigenBytes);
CmsEnvelopedData Data2 = gen.Generate(ByteArr, AES256_OID_STR);
txtEncrypted.Text = Convert.ToBase64String(Data2.GetEncoded());
Per desencriptar el codi és el següent:
byte[] Base64 = Convert.FromBase64String(Origen);
/*
Codi pel Pretty Parser
ASN1Element ASN = new ASN1Element(Base64, 0);
StringBuilder SB = new StringBuilder();
ASN.BuildString(SB, 3);
string Resultat2 = SB.ToString();
PrettyPrinter P = new PrettyPrinter(ASN);
string Resultat = P.ToString();
*/
CmsEnvelopedData Data = new CmsEnvelopedData(Base64);
Content = Data.ContentInfo.Content.ToString();
string[] Splitted = Content.Split('#');
int Posicio = 0;
string ClauAESEncriptadaRSA = "";
string strIV = "";
string DadesEncriptadesAES = "";
switch(Splitted.Length)
{
//Si s'encripta amb una clau PEM sense les dades d'un certificat aleshores hi 5 parts, per que s'especifica el keyID//Queda algo com://[2, [[2, [0]#0102030405, [1.2.840.113549.1.1.1, NULL],
#88ed9a2dc3f6f520a955dda3b3460c2e72b232f0cebb20aeeb5e9a7d27e7df3d1a026b017414c61483484fb7877
d466fc8bf47b20a1a6a9b4caf314f7862ea9cdcd4c7fdea168b07748d6bd13866df1c2319c07650a4a14c697b215
982007804b49a6c7f673b928f0417d44900bedb3f97bd5c4f6a08efce136e88f7b2fa714b2c03baad24ce51a3ad0
96649393ac71af2d6bcba3d42b108b5ec9b106028fd782b29162144c01f1fbca81200006b6c1c1513929b1d8b82d
ae2ec4bd0b37d2aaa08a4f1c8901ec23ad7e4d4eda522137fec9a00d34ef25c8f5f00fbe9a3cdce83bcdede83c9d
dff7b91c0c46d6394f4150b98782c12380cccd06d06858548c872]], [1.2.840.113549.1.7.1, [2.16.840.1.101.3.4.1.42,
#dd65197a345a16990dba5e3b60653a23],
[0]#1af48e832434616b6d16e235a28690fb8b506753525e91eb0674e20cd6224e1ebe13346b6cc01cc10ecf8442
12291077750569e09986dea001beec448fb66b69c878eebae45e1303f05518705c0a0626]]case 5:
{
Posicio = Splitted[2].IndexOf(']');
ClauAESEncriptadaRSA = Splitted[2].Substring(0, Posicio);
Posicio = Splitted[3].IndexOf(']');
strIV = Splitted[3].Substring(0, Posicio);
Posicio = Splitted[4].IndexOf(']');
DadesEncriptadesAES = Splitted[4].Substring(0, Posicio);
break;
}
//Si s'encripta amb un fitxer CRT que només té la clau publica hi ha menys trossos binaris//Queda algo com://[0, [[0, [[[[1.2.840.113549.1.9.1, email@email.es]],
[[2.5.4.6, ES]], [[2.5.4.8, Barcelona]], [[2.5.4.7, Carrer A 33]],
[[2.5.4.10, La meva empresa]], [[2.5.4.11, El meu departament]], [[2.5.4.3, Projecte]]], 1444401454],
[1.2.840.113549.1.1.1, NULL],
#9a5aff081265f8a5ec74a5ec10173758c8520a74c6373455f74ce5fa35699bd036028b6999b9a5303486e95fa39da
87215e14e273639c8ad76854e9d9b3b1ef6c4d00a8f0c9b6be1c0d0f92a5298c7fcd0c4ae782ce08545d3693810655
bc7fcbfea2dd2093b8de1c7af7b930e27d72b2b4e0bb45b3624923c791bc429160b0717cc7867698f4ee991f175812
58dfd8a53b6207e482f8840ff832a721188043f849d561d4115195a664f8baacdab2d75ccb486a4107e94c8d4d3c3f
f542cb997a8c9d444a53ceb21434b48c85bbf18d45131b9d5998cbb06911537bc1f8413cf3e669d3289a42fd699cc8
8e1b0d2d5b399bf40c2b4c2aefa5967c66516cfba0c]], [1.2.840.113549.1.7.1, [2.16.840.1.101.3.4.1.42,
#6ab63f859a84a8422708ceed3a4d3244],
[0]#242d87bbf418ab8773945fedc0e38761e111827e298a72998a5e52e241b723df86e9804ef373118d4ba951b2e0
bea96f41bc6da80a5131dc8fc6d98d97d378cc25d5b6f8c2b0baf42506dd1a7bf9f9de]]case 4:
{
Posicio = Splitted[1].IndexOf(']');
ClauAESEncriptadaRSA = Splitted[1].Substring(0, Posicio);
Posicio = Splitted[2].IndexOf(']');
strIV = Splitted[2].Substring(0, Posicio);
Posicio = Splitted[3].IndexOf(']');
DadesEncriptadesAES = Splitted[3].Substring(0, Posicio);
break;
}
}
byte[] ClauAESEncriptadaRSA_bytes = ConvertHexStringToByteArray(ClauAESEncriptadaRSA);
OrigenBytes = ConvertHexStringToByteArray(DadesEncriptadesAES);
//Desencriptador de la Clau AES
RSACryptoServiceProvider KeyImporter = new RSACryptoServiceProvider();
//Carreguem la clau
KeyImporter.LoadPrivateKeyPEM(PrivateKeyPEM);
//Obtenim la clau AESbyte[] PrivateKey = KeyImporter.Decrypt(ClauAESEncriptadaRSA_bytes, false);
AesCryptoServiceProvider Decrypter = new AesCryptoServiceProvider();
byte[] IV = ConvertHexStringToByteArray(strIV);
ICryptoTransform EncDec = null;
Decrypter.Key = PrivateKey;
Decrypter.IV = IV;
Decrypter.Mode = CipherMode.CBC;
Decrypter.Padding = PaddingMode.None;
EncDec = Decrypter.CreateDecryptor();
DecryptedBytes = newbyte[OrigenBytes.Length];
for (int i = 0; i < OrigenBytes.Length; i = i + 16) EncDec.TransformBlock(OrigenBytes, i, 16, DecryptedBytes, i);
if (chkCopyToClipboard.Checked) Clipboard.SetText(txtDecrypt.Text);
txtBase64.Text = Content;
txtDecrypt.Text = ConvertByteArrayASCIIToString(DecryptedBytes);
txtDecrypt.Text = txtDecrypt.Text.Replace("[03]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[04]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[05]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[06]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[07]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[08]", "");
txtDecrypt.Text = txtDecrypt.Text.Replace("[09]", "");
#02/11/2015 10:22 Programació C# Autor: Alex Canalda
Les petites funcions sempre cal tenir-les a mà. Es fan servir un cop, i un altra, i un altra... Aquesta és una d'aquelles funcions que sempre estem repetint.
Donat un string com per exemple: "Lorem ipsum dolor sit amet, 'consectetur' adipisici elit". Es vol obtenir "consectetur", es a dir el text que està entre els '. Per això es pot fer:
string Text = "Lorem ipsum dolor sit amet, 'consectetur' adipisici elit";
string Resultat = SubString(Text, "'", "'");
//Resultat = consecteturpublicstring SubString(string OnBusquem, string CaracterInicial, string CaracterFinal)
{
int Inici = OnBusquem.IndexOf(CaracterInicial) + CaracterInicial.Length;
int Fi = OnBusquem.IndexOf(CaracterFinal, Inici);
return OnBusquem.Substring(Inici, Fi - Inici);
}
També es pot fer d'una altra manera amb el mateix resultat:
Quina és millor? Quina s'entén millor? Per gustos els colors. Encara que a mi m'agrada més la primer opció ja que té algo més de flexibilitat. Per exemple, imaginem que enlloc que el separador sigui un caràcter, que sigui varis. Com quan volem extreure un valor de dins d'un string XML (sense muntar l'objecte XML), quedaria algo com:
Per validar que un string té una pinta com "0F4D3398FFDD", és a dir és un string Hexadecimal. Es pot fer una funció com:
publicbool OnlyHexInString(string test)
{
// For C-style hex notation (0xFF) you can use @"\A\b(0[xX])?[0-9a-fA-F]+\b\Z"returnSystem.Text.RegularExpressions.Regex.IsMatch(test, @"\A\b[0-9a-fA-F]+\b\Z");
}
Fer servir una expressió regular està molt bé, però ho trobo una mica excessiu ja que no cal tanta potència per fer algo senzill. Penso que és millor fer:
privatebool IsHex(IEnumerable<char> chars)
{
bool isHex;
foreach (var c in chars)
{
isHex = ((c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F'));
if (!isHex)
returnfalse;
}
returntrue;
}
#25/03/2015 18:22 Programació C# Autor: Alex Canalda
He tingut que implementar la firma MAC ANSI X9.19 (MAC = Message Authentication Code) i com tots els temes de seguretat és curiós la poca informació que hi ha.
Una firma (MAC) d'aquest tipus serveix per verificar que un missatge no ha estat manipulat i ens arriba tal com es va enviar. Si hi ha un error a la xarxa es detectarà, o si un "algú" dolent modifica el missatge no podrà regenerar la firma per que no coneix la clau a partir de la que es genera la MAC. El receptor com sí té aquesta clau pot verificar el contingut del missatge.
Aquesta firma MAC ANSI X9.19 és un estàndard bancari "made in USA". És una evolució del X9.9 i també es coneix com a Retail-MAC. És un xifrat dels anomenats DES-CBC, on DES vol dir "Data Encryption Standard" i CBC és el "Cipher Block Chaining". Existeix una norma internacional semblant, una mica més permissiva, la ISO 9807 ja que permet fer servir altres algoritmes de xifrat. Diguem que la ANSI X9.19 és una possible implementació de la ISO 9797 quan la mida del bloc a xifrar és de 64 bits, la longitud de la MAC 32 i es fa servir el DES com algoritme de xifrat.
El DES es un algoritme de xifrat ja força antic que fa servir claus de 56 bits però amb la X9.19 també s'aplica un xifrat extra en l'últim pas fent servir una clau de 128bits com si fos un 3DES normal. Els passos a seguir són els següents:
Al missatge d'entrada s'ha de posar padding (relleno). En aquest cas com no cal treure'l posteriorment es posen els bytes a 00 que facin falta fins que la longitud del missatge és múltiple de 8 bytes.
Es divideix la clau de 128bits en dues sub-claus de 64, la part esquerra s'anomena K, la part dreta K'.
Es divideix el missatge en blocs de 8 bytes.
Es xifra amb DES el primer bloc amb la clau K.
Al resultat es fa una XOR byte a byte amb el següent bloc (és el CBC).
Es xifra el resultat de la XOR amb la clau K. Si NO és l'últim bloc tornem al pas anterior. Si és l'últim bloc anem al pas següent.
Aquest últim resultat es desxifra amb la clau K'. Òbviament encara que sigui un desxifrat no s'obté res coherent.
Es xifra altra cop amb la clau K. És com si fos un 3DES en aquest últim pas. El resultat és la MAC.
Però millor una imatge:
He fet dues implementacions en C#, una fent les coses manuals i l'altra amb llibreries de sistema, ambdues ofereixen el mateix resultat. Al codi hi ha un exemple que he trobat amb els valors calculats.
publicbyte[] SubArray(byte[] data, int index, int length)
{
byte[] result = newbyte[length];
Array.Copy(data, index, result, 0, length);
return result;
}
privatevoid btnFirma_Click(object sender, EventArgs e)
{
try
{
if (!ValidarClau()) return;
NetejaTXTs();
byte[] IV = newbyte[8]; //empty byte arraybyte[] key = ConvertHexStringToByteArray(txtKey.Text);
byte[] LeftKey = SubArray(key, 0, 8);
byte[] RightKey = SubArray(key, 8, 8);
byte[] data;
if (chkHexString.Checked)
{
data = ConvertHexStringToByteArray(Neteja(txtOriginal.Text));
}
else
{
data = Encoding.ASCII.GetBytes(Neteja(txtOriginal.Text));
}
//Exemple//Dades = 4E6F77206973207468652074696D6520666F7220616C6C20//Clau = 0123456789ABCDEFFEDCBA9876543210//Firma = A1C72E74EA3FA9B6
DES DESalg = DES.Create();
DESalg.Mode = CipherMode.CBC;
DESalg.Padding = PaddingMode.None;
ICryptoTransform SimpleDES_Encriptador = DESalg.CreateEncryptor(LeftKey, IV);
ICryptoTransform SimpleDES_Desencriptador = DESalg.CreateDecryptor(RightKey, IV);
byte[] result = newbyte[8];
byte[] datablock = newbyte[8];
int remain = data.Length % 8;
int LoopCount = data.Length / 8;
/*
//Padding a 0
if (remain != 0)
{
int extra = 8 - (data.Length % 8);
int newLength = data.Length + extra;
byte[] newData = new byte[newLength];
Array.Copy(data, newData, data.Length);
data = newData;
}
result = SimpleDES_Encriptador.TransformFinalBlock(data, 0, data.Length);
byte[] block = new byte[8];
// Agafem l'ultim block
Array.Copy(result, result.Length - 8, block, 0, 8);
// Desencriptar l'ultim bloc a la K'
block = SimpleDES_Desencriptador.TransformFinalBlock(block, 0, 8);
// Encriptar altra cop el resultat amb K
block = SimpleDES_Encriptador.TransformFinalBlock(block, 0, 8);
result = block;
*///Si el que s'ha de firmar és multiple de 8...if (remain == 0)
{
LoopCount--;
remain = 8;
}
//Primer block
Array.Copy(data, 0, datablock, 0, 8);
result = EncryptBlock(SimpleDES_Encriptador, datablock);
for (int i = 1; i < LoopCount; i++)
{
datablock = newbyte[8];
Array.Copy(data, i * 8, datablock, 0, 8);
//Això fa el CBC
datablock = XorArray(datablock, result);
result = EncryptBlock(SimpleDES_Encriptador, datablock);
}
//Ultim blockbyte[] LastBlock = newbyte[8];
//Això fa padding a zeros
Array.Copy(data, data.Length - remain, LastBlock, 0, remain);
LastBlock = XorArray(LastBlock, result);
result = EncryptBlock(SimpleDES_Encriptador, LastBlock);
result = EncryptBlock(SimpleDES_Desencriptador, result);
result = EncryptBlock(SimpleDES_Encriptador, result);
if (!chkHexString.Checked) txtBase64.Text = ConvertByteArrayToHexString(data);
txtEncrypted.Text = ConvertByteArrayToHexString(result);
}
catch(Exception E)
{
MessageBox.Show(E.Message);
}
}
publicbyte[] XorArray(byte[] buffer1, byte[] buffer2)
{
for (int i = 0; i < buffer1.Length; i++)
buffer1[i] ^= buffer2[i];
return buffer1;
}
privatebyte[] EncryptBlock(ICryptoTransform crypt, byte[] toEncrypt)
{
try
{
MemoryStream mStream = new MemoryStream();
CryptoStream cStream = new CryptoStream(mStream,
crypt,
CryptoStreamMode.Write);
cStream.Write(toEncrypt, 0, toEncrypt.Length);
cStream.FlushFinalBlock();
byte[] ret = mStream.ToArray();
cStream.Close();
mStream.Close();
Console.WriteLine("DES OUTPUT : " + ConvertByteArrayToHexString(ret));
return ret;
}
catch (CryptographicException e)
{
Console.WriteLine("A Cryptographic error occurred: {0}", e.Message);
returnnull;
}
}
#17/03/2015 18:28 Programació C# Autor: Alex Canalda
El C# 6 portarà noves formes de fer coses, es modifica la sintaxi del llenguatge afectant a l'estil de codificar de cadascú, a mi particularment aquestes són les que més m'han cridat l'atenció
Interpolació d'strings:
//Abans es feia això:string STRWS = StarWars;
StringBuilder Build = newStringBuilder;
Build.AppendFormat("Ja falta poc per la nova peli de {0}", STRWS);
string Frase = Build.Tostring();
//---- En C# 6 es faràstring Frase = $"Ja falta poc per la nova peli de {STRWS}";
Nou operador nameof: abans es tenia que reflection per obtenir noms de variables, etc... ara amb aquest operador es com si es tingués la reflection ja muntada dins el llenguatge.
Exceptions filtrades, que ja es podia fer llançant exceptions més detallades però bueno...
try
{
thrownewException("Error");
}
catch (Exception ex) if (ex.Message == "ErrorMoltXungo")
{
// Aquest no s'executa.
}
catch (Exception ex) if (ex.Message == "Error")
{
// Aquest sí s'executa
WriteLine("Error");
}