
Любимый плакат из серии.
Остальное здесь

Любимый плакат из серии.
Остальное здесь
Нашел интересный тест, позволяющий проверить, не «сливает» ли браузер локальный IP через WebRTC В принципе, в этой дыре ничего особо страшного нет. У меня браузер слил локальный IP, но это, как и ожидалось, был частный IP-адрес. Даже не частный IP-адрес локальной сети провайдера, а частный IP-адрес за роутером, сервером, и виртуальной машиной, т.е. что-то вида 172.16.5.100. Ничего, без глубокого анализа и совсем уж целенаправленной атаки не дающий. Нет, он может дать злоумышленнику ваш реальный IP, если у вас «белый» IP, сетевой кабель от провайдера воткнут прямо в сетевую карту, и у провайдера кривые настройки его локальной сети. Но, как говорится, ложечки целы, а осадочек остался. Так что если кому попараноить — тому сюда. Там и инструкция есть, как задушить гадину, если кому надо.
Да, без включенного жабаскрипта оно не работает, так что NoScript наш первый друг, товарищ и самый человечный человек!
И главное — лучше перебдеть, чем недобдеть! (L) КГБ/ВЧК/МГБ/ФСБ/ЦРУ/SCP/НЛО
С уважением, Курильщик, специально для Tolik-punkoff.com
Давно посматривал на всякие приложения типа Process Explorer или Aida, которые могли создавать в трее иконки, что-нибудь динамически отображающие. Например, Process Explorer может показывать график загрузки ЦП:

Решил попробовать сделать что-то подобное. Оказывается, ничего сверхъестественного не было. Не стал пытаться изобразить график, сделал динамическое отображение заданного текста на иконке.
1. В приложение Windows Forms надо добавить, собственно, NotifyIcon. Пусть будет с именем niMain.
2. Иконка в трее должна быть размером 16×16, заведем соответствующие переменные:
int iwidth = 16; int iheight = 16;
3. Потребуется строковая переменная, хранящая отрисовываемый текст, объект Font и объект Bitmap, который будет хранить динамически отрисовываемое изображение
string DSt = "";
Font fnt = null;
Bitmap bitm = null;
4. В MSDN настоятельно рекомендуют после программного создания иконки, удалять ее с помощью функции DestroyIcon из user32.dll, для предотвращения утечки памяти. Не буду отступать от рекомендаций MSDN, и функцию экспортирую:
[System.Runtime.InteropServices.DllImport("user32.dll",
CharSet = System.Runtime.InteropServices.CharSet.Auto)]
extern static bool DestroyIcon(IntPtr handle);
5. Создаем нужный Font и пустой Bitmap необходимого размера:
private void frmTest_Load(object sender, EventArgs e)
{
fnt = new Font("Courier new", 8, FontStyle.Bold);
bitm = new Bitmap(iwidth, iheight);
}
6. Отрисовываем изображение:
— Создаем объект Graphics, который будет заниматься отрисовкой. Объект Graphics можно получить для определенного ранее Bitmap'а:
Graphics graph = Graphics.FromImage(bitm);
— Рисуем фон (черный квадрат):
graph.FillRectangle(Brushes.Black, 0, 0, iwidth, iheight);
— И текст:
graph.DrawString(DSt,fnt,Brushes.Lime, new Point(0,2));
7. Осталось сделать из объекта Bitmap объект Icon и отдать его контролу NotifyIcon
— Получаем handle иконки:
IntPtr hIcon = bitm.GetHicon();
— Получаем иконку, и отдаем ее NotifyIcon:
System.Drawing.Icon niicon = System.Drawing.Icon.FromHandle(hIcon);
niTest.Icon = niicon;
— Теперь можно уничтожить иконку, т.к в объекте NotifyIcon будет отдельная копия иконки:
DestroyIcon(niicon.Handle);
ФАНФАРЫ!


Похоже, самый надежный способ проверить соединение с Интернетом, это сделать запрос к какому-нибудь редко падающему сайту, например google.com или microsoft.com. Хотя в ГОРФ хрен знает, что завтра заблокируют :).
Как я понимаю, седьмая и десятая винда примерно так и поступают, периодически обращаясь к каким-то майкрософтовским серверам.
В сети рекомендуют способ с NetworkInterface.GetIsNetworkAvailable() или экспортировать функцию InternetGetConnectedState из wininet.dll, однако у меня оба способа нагло врали, показывая наличие интернета при его отсутствии, но при наличии подключения к локальной сети или VPN. Сделать Ping тоже не всегда возможно, ICMP могут быть вырублены на стороне провайдера (или сервера). Так что пока способа лучше, чем сообразить запрос с помощью HttpWebRequest не нашел.
При этом способе, правда, есть два небольших подводных камня. Первый ВНЕЗАПНЫЙ, оказывается, ответ на запрос все-таки надо прочесть, хотя сам ответ в данном случае и не особо интересен, для проверки достаточно отловить код ошибки в try/catch примерно так:
request = (HttpWebRequest)HttpWebRequest.Create(URL);
//[...]
try
{
resp = (HttpWebResponse)request.GetResponse();
//не вывалились в ошибку, значит все OK
Stream temp = resp.GetResponseStream(); //если не прочитать поток ответа
//случается потеря соединения при повторном запросе (сам в шоке)
StreamReader sr = new StreamReader(temp);
sr.ReadToEnd();
//Обрабатываем случай когда все ОК
}
catch (WebException ex)
{
//Обрабатываем ошибку соединения
}
Ну и второй подводный камень довольно очевидный — операция запроса к сетевому ресурсу довольно долгая, так что желательно делать все это в отдельном потоке, дабы не грузить основной поток, в котором обычно интерфейс своими делами занимается, а то программа будет выглядеть «зависшей».
Есть у меня постоянная клиентша, скажем, Юля, которой периодически надо восстанавливать систему на ноуте, тому ще также периодически ее грохает мелкий Юлин сын. У Юли есть еще и гражданский муж, скажем Вова, работающий прорабом на стройке.
Сыну, пусть будет Леша, около 5 лет, такой шустрый почемучка с шилом в пигидии, от которого прячутся коты, программисты и любая другая живность, попавшая в зону поражения. Детей я терпеть не могу, но зато очень люблю виски и коньяк, которым меня Юля с Вовой усердно потчуют, так что я к ним всегда лечу, аки муха на мед, и даже денег беру только на такси.
Для них, как и почти для любых юзверей, я «программист» и даже «хакер» (могу сломать пароль на винде, значит хакер). И сыну Юля всегда говорит, что вот, дядя Панкарь программист, сейчас все починит, и Леша опять будет смотреть своих смешариков. Единственное мое достижение в этой семье — я их подсадил на великолепного Глебыча и на «Магазинчик Бо». Ну вот, дело не в этом.
Сижу я после очередного восстановления системы, употребляю армянский коньяк. Тут в кухню заходит мелкий:
— Дядя Панкарь, а папа Вова тоже программист, как и ты!
От неожиданности я подавился коньяком, и вся жизнь, буквально, пронеслась перед глазами. Уж чего только не подумалось, от «если коровы будут летать, то нам в космосе делать нечего», до того, какой чудовищный размер взятки должен быть, чтобы Вова получил диплом, и что Вова скурил, чтобы переквалифицироваться из прорабов в программисты.
Пока я сползал под стол, Вова и Юля ржали в голос. Отсмеявшись, привели меня в чувство и объяснили. Оказывается, мелкий сходил на стройку с папой, увидел, как тот командует бригадой, и расшифровал папину должность ПРОРАБ, как ПРОграммист РАБочих!
Так и живем.
Итак, есть у нас некий набор параметров программы, который надо сохранить при задании его пользователем, и восстановить при запросе из основной программы, т.е. файл конфигурации.
Обычно под управление конфигурацией делается отдельный класс, задача которого сохранить/загрузить конфиг и в нужный момент выдать запрашиваемый параметр. В качестве хранилища данных можно использовать DataSet. Во-первых, потому что все параметры можно представить в удобном виде типа таблицы базы данных, а во-вторых, DataSet умеет сохранять свое содержимое в XML и загружать его в автоматическом режиме. Но вот с заполнением DataSet возникают некоторые проблемы. Обычно я заполнял его почти вручную, что приводило к появлению некрасивых простыней кода, в которых, к тому же, легко допустить опечатку. А добавление нового параметра, приводило к необходимости добавлять его в несколько мест в коде.
Как оказалось, можно все сделать гораздо проще — обойти в автоматическом режиме все поля класса, собрать или загрузить в них все нужные значения, а также построить таблицу в DataSet, содержащую все необходимые поля нужного типа.
Надо подключить пространства имен System.Data и System.Reflection
using System.Data;
using System.Reflection;
и завести приватные переменные, собственно DataSet, переменную под имя конфиг-файла и переменную под имя таблицы:
private string configFile = "";
private string TableName = "";
private DataSet dsNetConfig = new DataSet();
Далее в конструкторе класса запрашиваем имя файла конфигурации, формируем имя таблицы и вызываем функцию, создающую таблицу DataSet со всеми необходимыми полями, которые будут хранить значения свойств класса.
public NetSettings(string filename)
{
configFile = filename;
TableName = this.GetType().Name;
CreateDataSet();
}
private void CreateDataSet()
{
dsNetConfig.Tables.Add(TableName);
PropertyInfo[] properties = this.GetType().GetProperties();
foreach (PropertyInfo pr in properties)
{
dsNetConfig.Tables[TableName].Columns.Add(pr.Name,
pr.PropertyType);
}
}
— Добавляем в DataSet таблицу
— Получаем список свойств класса в виде массива PropertyInfo:
PropertyInfo[] properties = this.GetType().GetProperties();
— В цикле foreach создаем колонки в таблице DataSet, задавая имя и тип данных:
dsNetConfig.Tables[TableName].Columns.Add(pr.Name, pr.PropertyType);
public bool SaveConfig()
{
// [...]
ConfigError = null;
dsNetConfig.Tables[TableName].Rows.Clear();
DataRow dr = dsNetConfig.Tables[TableName].NewRow();
PropertyInfo[] properties = this.GetType().GetProperties();
foreach (PropertyInfo pr in properties)
{
string propName = pr.Name;
object propValue = pr.GetValue(this,null);
dr[propName] = propValue;
}
dsNetConfig.Tables[TableName].Rows.Add(dr);
try
{
dsNetConfig.WriteXml(configFile);
}
catch (Exception ex)
{
ConfigError = ex.Message;
return false;
}
return true;
}
— Добавляем в таблицу новый DataRow (у меня строка должна быть всего одна, потому для начала очищаю содержимое таблицы на всякий случай)
dsNetConfig.Tables[TableName].Rows.Clear();
DataRow dr = dsNetConfig.Tables[TableName].NewRow();
— Далее опять же получаю массив PropertyInfo и обрабатываю его в цикле foreach
— Получаю имя поля:
string propName = pr.Name;
— И его значение:
object propValue = pr.GetValue(this,null);
Второй параметр в функции GetValue — индекс для свойств, имеющих индексацию. Например, если свойство является массивом, то нужно будет задавать индекс элемента для получения конкретного значения. В данном случае таких свойств в классе нет, посему в качестве второго параметра указывается null.
— Записываю значение на свое место в DataSet:
dr[propName] = propValue;
— Добавляю в таблицу сформированную строку:
dsNetConfig.Tables[TableName].Rows.Add(dr);
— Сохраняю содержимое DataSet в XML:
dsNetConfig.WriteXml(configFile);
В разбираемом примере есть такое свойство public string ConfigError, хранящее сообщение об ошибке при загрузке/сохранении конфига, но в самом конфигурационном файле не нужное.
Тут три пути:
1. Самый простой. Забить и плюнуть, ненужное свойство будет сохраняться в конфиге, загружаться из него, да и пусть.
2. Компромиссный. Обнулить свойство перед сохранением. Тогда оно будет как поле в таблице DataSet, но в конфиге его не будет. Так сделано в разбираемом классе Способ хорош, если таких ненужных свойств мало. И место в файле оно занимать не будет, и не нужны дополнительные проверки.
3. Составить список, хоть в виде строковой переменной, где перечислить «лишние» поля, и проверять список перед сохранением и созданием таблицы.
public NetConfigStatus LoadConfig()
{
//[...]
try
{
dsNetConfig.ReadXml(configFile);
}
catch (Exception ex)
{
ConfigError = ex.Message;
return NetConfigStatus.Error;
}
//загрузка свойств класса из DataSet
if (dsNetConfig.Tables[TableName].Rows.Count > 0)
{
PropertyInfo[] properties = this.GetType().GetProperties();
foreach (PropertyInfo pr in properties)
{
string propName = pr.Name;
object propValue = dsNetConfig.Tables[TableName].Rows[0][propName];
if (propValue.GetType() != typeof(System.DBNull))
{
pr.SetValue(this, propValue, null);
}
}
//[...]
}
return NetConfigStatus.OK;
}
Все делается точно также, только в обратном порядке.
— Загружаем XML
— Получаем список свойств
— Устанавливаем значения в цикле с помощью SetValue
Необходимы только две проверки — на количество записей в таблице DataSet и на то, не является ли значение ячейки DBNull
На самом деле, первое правило безопасного хранения паролей — никогда не хранить пароли. Пусть за безопасность паролей отвечает сервер, например. Если это клиент-серверное приложение (каких очень много, и мы практически не замечаем, что ими пользуемся).
Но бывают ситуации, когда пароли (не хэши паролей, не контрольные суммы) все-таки надо хранить локально, как это делают, например, браузеры. И вот тут их надежно хранить не получается, или получается, но с диким геморроем. Для всяких крутых бизнес-систем, типа подписей на вашем контракте с Роскомпозором, это дело берут на себя криптопровайдеры, с их якобы крутыми разработчиками и стандартами.
А обычные клиентские приложения, типа браузера, могут предложить вам задать «пароль для паролей» (от базы хранящей пароли, запароленной главным паролем, который хранится в хранилище главных п… увлекся я). То есть, попросту предложить вам задать некий мастер-пароль, который вы будете держать в голове, и без этого пароля, браузер сам не расшифрует все остальные пароли. Так делают не только браузеры, но и всякие хранилища паролей типа keepass, и даже практически промышленные системы шифрования, типа Truecrypt. Они могут вам предложить сохранить мастер-пароль на «электронный ключ» или флешку и сгенерировать, за приемлемое время, неподбираемый пароль, размером, например 1Мб случайных символов.
Не всегда такая серьезная секретность нужна, некоторые пароли все-таки можно хранить, не заморачиваясь с параноидальной безопасностью. Например, пароль от условного прокси-сервера, который надо передать нашей программе, чтобы она через этот прокси соединилась с Интернетом. Или базой данных на сервере. Но, в любом случае, если вы разрабатываете приложение, использующее хранение паролей, в первую очередь, надо позволить пользователю его не сохранять, а вводить по требованию. Ну, так как же быть, если пароль не очень важный, и не хочется морочить голову пользователю постоянным его введением? Попробую ответить на этот вопрос.
А давайте, нагенерируем какой-нибудь мастер-пароль сами? Зависящий, например, от серийного номера материнской платы, потом, зашифруем хранимый пароль этим паролем с помощью любимого алгоритма шифрования.
А давайте!
Например, вот так
Минусы:
Практически выяснено, оказывается, запросы к WMI иногда глючат, и не всегда работают корректно. Иногда создается неотлаживаемый глюк на ровном месте.
Называется это дело DPAPI (ссылки на источники смотрите в конце заметки).
Итак, шифрование, примем для простоты, что нам нужно сохранить пароль для прокси-сервера:
1. У нас есть пароль в виде строки, который надо зашифровать и сохранить потом где-нибудь в конфиге программы. Преобразуем строку в массив байт:
byte[] pass = Encoding.UTF8.GetBytes(ProxyPassword);
2. Далее, нам нужно вычислить энтропию, хотя она опциональна, и не всегда нужна, но все-таки не будем отступать от рекомендаций, и вычислим ее. Как и соль, должна быть одинакова при шифровании и дешифровании.
Для объекта ProtectedData, который в .NET Framework и занимается нашей задачей, энтропия должна быть передана ему в качестве массива байт (опционально).
Раз мы занялись энтропией, напишем функцию для ее получения. Пусть энтропией будет MD5-хэш от заданной строки.
private byte[] GetEntropy(string EntropyString)
{
MD5 md5 = MD5.Create();
return md5.ComputeHash(Encoding.UTF8.GetBytes(EntropyString));
}
В качестве строки для энтропии, используем что-нибудь не меняющееся, например адрес прокси-сервера и имя пользователя:
byte[] entropy = GetEntropy(ProxyAddress + ProxyUser);
3. Итак, пароль есть, энтропия тоже, осталось зашифровать:
byte[] crypted=ProtectedData.Protect(pass, entropy, DataProtectionScope.LocalMachine);
DataProtectionScope, если объяснить простым языком, то это параметр, который позволяет DPAPI привязать шифрование либо к пользователю данной системы (надо использовать DataProtectionScope.CurrentUser).
Либо к компьютеру (в смысле установленной ОС), тогда используется DataProtectionScope.LocalMachine. В последнем случае, сохраненный пароль могут расшифровать все пользователи данного компьютера, тогда как в предыдущем — только пользователь, сохранивший пароль.
4. Далее преобразуем зашифрованный массив байтов в что-нибудь, что можно хранить, например в конфиге формата XML. В данном примере в строку BASE64:
ProxyPassword = Convert.ToBase64String(crypted);
Расшифровка производится также, только в обратном порядке
-Раскодируем зашифрованную строку из BASE64
-Генерируем энтропию по тому же алгоритму, что использовался для шифрования
-Расшифровываем зашифрованный текст
-Используем расшифрованное где надо
Вот пример кода под катом
Пример на PasteBin (шифрование, дешифрование, генерация энтропии)
Для работы с gzip-архивами есть класс GZipStream из пространства имен System.IO.Compression, доступный в .NET Framework 2.0, однако в MSDN почему-то получилось, как в анекдоте про Вовочку, класс есть, а слова такого нету примера под второй фреймворк нет. В том, который есть, используется отсутствующий метод CopyTo() Пришлось действовать без него:
1. Заводим три потока, собственно GZipStream два FileStream для чтения сжатого и записи распакованного файла.
GZipStream gzip = null;
FileStream readStream = null;
FileStream writeStream = null;
Все дальнейшее лучше делать в try/catch, чтобы отловить возможные ошибки
2. Открываем оригинальный файл на чтение, распакованный на запись:
readStream = new FileStream(originalFile, FileMode.Open);
writeStream = new FileStream(unpackedFile, FileMode.Create);
3. Создаем поток GZipStream, подсовываем ему поток, откуда читать данные, и устанавливаем CompressionMode в Decompress
gzip = new GZipStream(readStream,CompressionMode.Decompress);
А теперь делаем CopyTo, только без самой CopyTo:
1. Заводим переменную с размером буфера, сам буфер, и переменную для хранения фактического количества прочитанных из потока GZipStream байт:
int size = 1024; //размер буфера для обмена между потоками
byte[] unpackbuf = new byte[size]; //буфер
int count = 0; //для хранения фактически прочитанных байт
Чем больше размер буфера, тем быстрее пойдет процесс.
2. Читаем данные кусками размером size из GZipStream и пишем их в поток выходного файла:
//пишем распакованные данные по кускам
do
{
count = gzip.Read(unpackbuf, 0, size); //читаем кусками размером size
if (count > 0) //если данные есть
{
writeStream.Write(unpackbuf, 0, count); //пишем
//фактически прочитанное кол-во байт
}
} while (count > 0);
3. Закрываем потоки:
gzip.Close();
readStream.Close();
writeStream.Close();
И ТЕЛЕМАРКЕТ!
Функция целиком под катом
На самом деле, архив формата gzip не хранит исходное имя сжатого файла, и вообще хранит один единственный файл. Однако принято, что распакованный файл носит то же имя, что и имя архива, но без расширения .gz, если оно присутствует. Вот функция, которая «отрезает» расширение:
public string GetUnpackedFilename(string fileName)
{
FileInfo fi = new FileInfo(fileName);
string unpackedFile = fileName.Substring(0, fileName.Length -
fi.Extension.Length);
return unpackedFile;
}
Возникла задача работать с данными, которые могут быть сжаты в gzip-архив. Соответственно, необходимо было определить, является ли файл gzip-архивом. Это довольно просто, gzip-архив можно определить по сигнатуре 1f 8b 08 00. Проверку целостности оставим на функцию распаковки, и если вышеуказанная сигнатура обнаружена в начале файла, то будем считать, что перед нами архив gzip.
Вот функция проверки:
public bool IsGZip(string filename)
{
byte[] buf = null;
try
{
buf = File.ReadAllBytes(filename);
}
catch
{
return false;
}
if (buf.Length < 4) return false;
if ((buf[0] == 0x1F) && (buf[1] == 0x8B) &&
(buf[2] == 0x08) && (buf[3] == 0x00))
return true;
return false;
}
Если файл очень большой, то File.ReadAllBytes не подойдет. Надо будет через StreamReader или FileStream прочесть только первые 4 байта:
public bool IsGZip(string filename)
{
int signlen = 4;
int count = 0;
byte[] buf = new byte[signlen];
FileStream readStream = null;
try
{
readStream = new FileStream(filename, FileMode.Open);
count = readStream.Read(buf, 0, signlen);
}
catch
{
return false;
}
readStream.Close();
if (count < 4) return false;
if ((buf[0] == 0x1F) && (buf[1] == 0x8B) &&
(buf[2] == 0x08) && (buf[3] == 0x00))
return true;
return false;
}
Напоролся на тривиальный, но неприятный подводный камень. Функция Convert.ToDouble() по умолчанию смотрит на разделитель целой и дробной части, который указан в системных настройках. И если в строке разделитель другой, то генерирует exception, например, если конвертировать число 3.14, а в системных настройках в качестве разделителя указана не . (точка), а , (запятая), то программа вывалится с ошибкой.
Решение простое, с помощью второго параметра функции Convert.ToDouble() задать разделитель целой и дробной части. Вот пример функции-обертки над Convert.ToDouble(), с возможностью указания разделителя:
public static double ConvertToDouble(string Value, string DecimalSeparator)
{
NumberFormatInfo format = new NumberFormatInfo();
format.NumberDecimalSeparator=DecimalSeparator;
return Convert.ToDouble(Value, format);
}
На самом деле, с помощью NumberFormatInfo можно задать еще кучу параметров, например, разделитель разрядов, буде такой понадобится.
Кстати, началась весна, а я случайно обнаружил О-о-очень древнюю запись. Эмо-панк группа с характерным названием «Выхода нет». Трэк «Весна в Гонконге». Доложили, что тема петрозаводская. Не гуглил, не проверял, с авторами не связывался
Слушайте. Текст — охуенный.
Расшифровка полей данных SYNOP и METAR в формате CSV
Взято с RP5.RU, и собственно, для работы с архивами погоды оттуда и предназначается. Описание полей выдирал L.S. из кода страничек.
Кстати, не сочтите за рекламу, но, ИМХО, это самый классный погодный сайт во всем интернете, не только в рунете, а во всем вообще! Потому что единственный, где можно взять наблюдения непосредственно с нужной метеостанции. Обычно показывают ближайшую к выбранному населенному пункту, но можно, немного изъебнувшись, найти нужную по WMO ID (кто не знает, это такой уникальный типа «айпишник» конкретной станции). Единственный минус — какая-то автоматизация получения данных, только за бабки, запросами выдрать не получается, сайт заскриптован наглухо, но за бабки дают XML, который можно крутить-вертеть, как хочешь.

Возобновляем мысленный проект Димы
Итак, что такое N-ART, это типа арт-журнал, где типа эксперты выбирают типа произведения искусства для одной цели — опубликовать их на нашем сайте. Критериев отбора всего 2:
1. Нам понравилось
2. Произведение нигде не было опубликовано.
Политика участия. Можно присылать свои работы каким угодно образом, лучше всего по почте или лично, тогда мы их отсканируем (сфотографируем) и отдадим оригинал обратно (вышлем письмом).
Политика редакции. Мы не получаем с публикации денег. Если кто-то из авторов хочет деньги, то он может приписать, что ему нужен донат. Весь донат для автора будет отправлен автору. Мы не берем процентов.
На остальные заебы похуй, весь цифровой контент, попавший к нам распространяется по лицензии Хекса
Сегодня у нас произведение Длиннопанка из города К.

В первой части я показал, как просто обнаружить ошибки HTTP при работе с curl. Но на самом деле, подход, использованный в скрипте, немного неправильный:
Клиент может не знать все коды состояния, но он обязан отреагировать в соответствии с классом кода. В настоящее время выделено пять классов кодов состояния.
Т.е. объясняя по рабоче-крестьянски, не только код 200 может свидетельствовать об успешном завершении запроса, а другие коды, кроме явных клиентских или серверных ошибок, могут требовать от нас каких-либо действий.
Вообще, коды ответа HTTP разделены на 5 классов:
1xx — информационные
2xx — успешное завершение
3xx — требуется переопределение
4xx — ошибка, допущенная со стороны клиента
5xx — ошибка, допущенная со стороны сервера
Вот и модифицируем наш скрипт в соответствии со стандартом.
В общем, надо из кода ответа HTTP извлечь первую цифру, и, соответственно прореагировать. Привожу часть скрипта, ответственного за более подробную реакцию на код ответа HTTP:
curl -o $SAVEFILE $1 -D $HEADERDUMP >/dev/null 2>/dev/null
EXITCODE=$?
if [ $EXITCODE -ne 0 ]; then
echo "CURL error $EXITCODE"
else
HTTPSTATUS=`cat $HEADERDUMP|head -1|awk '{print $2}'` #get HTTP status code
HTTPSTATUSMESS=`cat $HEADERDUMP|head -1|cut -d ' ' -f 3-
HTTPSTATUSID=`echo $HTTPSTATUS|cut -c 1` #get first char status code
case "$HTTPSTATUSID" in
1 ) echo -n "Informational: ";;
2 ) echo -n "Success: ";;
3 ) echo -n "Redirection: ";;
4 ) echo -n "Client Error: ";;
5 ) echo -n "Server Error: ";;
* ) echo -n "Unknow status: ";;
esac
echo "$HTTPSTATUS $HTTPSTATUSMESS"
fi
Жирным шрифтом выделена строка, в которой мы получаем этот самый класс кода возврата.
Курсивом — строка, где мы получаем пояснения текстовые пояснения к коду ответа.
Используемая в процессе анализа утилита cut, в первом случае, позволяет нам вывести в переменную все символы после с третьего пробела.
Перед первым у нас версия протокола http, потом код ошибки, а потом пояснение, которое может содержать несколько пробелов, поэтому awk тут не подходит.
cut так же может работать с текстовыми данными, в которых есть разделители, например пробелы, или же с отдельными символами, как в той строке кода, что выделена жирным — так мы можем извлечь первую цифру из кода ответа.
Дальнейший анализ осуществляется с помощью оператора case

Расширенный анализ кодов состояния HTTP:
На Pastebin
На Github
PHP скрипт для проверки:
На Pastebin
На Github
Краткий справочник по командам curl и wget Копия
Утилита cut
Коды статуса HTTP
Оператор case
Итак, возникла следующая задача — определить, когда curl получил вменяемые данные от http-сервера, а когда нет. В общем, и самом распространенном случае, задача сводится к тому, чтобы определить, отдал ли нам сервер запрашиваемый файл, или отобразил «страница не найдена».
Впрочем, это работает и для других возможных кодов ответа.
Ясно, что сервер, на наш запрос GET может ответить, как возвратив нам запрашиваемую страницу, так и отправив код, например, 404 (не найдено), и возвратив страницу-заглушку для этого случая. Так вот, на уровне скрипта bash или скрипта php необходимо проанализировать ответ сервера, чтобы не выдать пользователю вместо ожидаемых данных, всяческую лабуду. Далее я рассматриваю исключительно консольную Linux-утилиту curl. Для php, синтаксис, по-моему, отличается.
Есть страница, которую мы хотим получить: http://example.org/page.html, сохранить ее в файл page.html и есть curl, команда будет такая:
curl -o "page.html" http://example.org/page.html
Если страница на месте, то мы получим ее, и ничего анализировать не требуется.
Если же нет, то от большинства web-серверов мы получим страницу-заглушку, например такую, но никакой ошибки при этом.
Как же быть:
Проверяем код ошибки самого curl, если нет связи с сетью, URL неправильный, или задан неподдерживаемый curl протокол, если так, то мы получим соответствующий код возврата, наподобие:
curl: (1) Protocol htt not supported or disabled in libcurl
А вот если адрес правильный, и никакой ошибки в адресе или соединении нет, то код возврата не сработает, нам самим придется думать дальше.
Надо получить код ответа HTTP!
Сделать это можно, если получить отдельно заголовки ответа сервера, и после их проанализировать. Благо, для этого никакого страшного колдунства не требуется, достаточно указать утилите curl опцию -D (т.е. dump) и файл, куда выгружать заголовки.
Покажу это на примере конкретного скрипта:
#!/bin/bash
HEADERDUMP="/tmp/headerdump.txt"
SAVEFILE="/tmp/httpfile"
HTTPSTATUS=""
curl -o $SAVEFILE $1 -D $HEADERDUMP >/dev/null 2>/dev/null
EXITCODE=$?
if [ $EXITCODE -ne 0 ]; then
echo "CURL error $EXITCODE"
else
HTTPSTATUS=`cat $HEADERDUMP|head -1|awk '{print $2}'`
if [ "$HTTPSTATUS" == "200" ];then
echo "OK"
else
echo "HTTP error $HTTPSTATUS"
fi
fi
заводим 3 переменные
HEADERDUMP="/tmp/headerdump.txt" — файл, в который будем получать заголовки
SAVEFILE="/tmp/httpfile" — файл с сервера, который мы хотим получить
HTTPSTATUS="" — переменная для статуса (состояния) HTTP
curl -o $SAVEFILE $1 -D $HEADERDUM
пытаемся скачать нужный файл (получить страницу):
-o «имя_файла» — куда сохранять результат
$1 — внутренняя переменная, первый параметр скрипта, при вызове его с командной строки. В своем скрипте надо заменить на свой случай.
можно добавить >/dev/null 2>/dev/null — для красоты, чтоб не вылезали сообщения о процессе и ошибках.
-D <файл> — файл, куда будем копировать заголовки
Файл headerdump.txt выглядит примерно так:
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Fri, 30 Mar 2018 00:17:23 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
X-Powered-By: PHP/5.6.33
Link:
Set-Cookie: quick_chat_alias=Anon_240883; path=/
Upgrade: h2,h2c
Vary: Accept-Encoding,User-Agent
В первой строке — интересующая нас информация. Версия протокола (не интересно), код ответа (вот он) и информационное сообщение (не особо надо).
Вычленяем код:
HTTPSTATUS=`cat $HEADERDUMP|head -1|awk '{print $2}'`
1. Берем первую строчку (head -1)
2. Сохраняем в переменную 2-е значение после пробела (разделитель пробел): awk '{print $2}'
Дальше можно анализировать:
if [ "$HTTPSTATUS" == "200" ];then
echo "OK"
else
echo "HTTP error $HTTPSTATUS"
fi

Также, впрочем, можно поступить и для анализа запросов POST, вот тестовый скрипт php для отладки подобных возможностей и скрипт bash

Пример с запросом GET
Пример с запросом GET на Гитхаб
Пример с запросом POST
Пример с запросом POST на Гитхаб
Точнее про то, что копирайт и копирасты это говно, а копирование — не воровство.
Имеются:
— главный герой, получивший от нас (ваших товарищей из параллельного измерения) Хаопринтер, с возможностью 3D-печати и кучей дополнительных опций. Впрочем, он неплохо работает и с дефолтными настройками.
— злобный и жадный копираст, у которого бомбануло
— судья-взяточник, который, естественно, на стороне копираста
В общем, смотрите 🙂