Работа с 1CD средствами Python

Библиотека работы с 1CD средствами Python.
Opensource / Cross platform / Python.

Структура данных файлов 1CD уже давно ни для кого не является секретом. Благодарим за это awa. Без его статьи этой библиотеки не было бы.

В остальном история разработки крайне простая: возникло желание изучить Python. А какой лучший способ изучения языка программирования? Конечно же, написать на нем небольшой проект. В голову пришла идея написать извлекатель паролей (точнее их хэшей) из файловой базы 1С. Поначалу думал сделать простую обертку над tool_1cd, но потом решил, что это как-то не спортивно. Из готовых аналогов нашел только проприетарные библиотеки / приложения и реализацию средствами 1С (медленно, только под Windows, но все равно круто). В итоге решил написать библиотеку для Python. А потом уже и экстрактор на ней сделать.

Что из этого получилось, можно увидеть во вложении.

Как работатать:

1. Ставим модуль через pip:

pip install onec_dtools

2.  Импортируем библиотеку в приложение и смотрим, что она может.

import onec_dtools

with open('1Cv8.1CD', 'rb') as f:
db = onec_dtools.DatabaseReader(f)
print("База данных 1С (вер. {}/{})".format(db.version, db.locale))
print("Всего таблиц: {}".format(len(db.description)))
if row.is_empty:
continue
for row in db.tables['V8USERS']:
print(row.as_list(True))

В db у нас будет класс для взаимодействия файловой базой. Инициализация класса, пожалуй, самая длительная операция, т.к. она считывает всю структуру БД. После инициализации нам сразу доступна информации о версии формата файла (тестировалось все только с базами формата 8.2.14 — этого должно быть достаточно для работы со всеми актуальными версиями платформы), языка БД и описание таблиц.

Чтение таблиц БД возвращает генератор. Таким образом данные считываются и загружаются в память построчно, а не всей таблицей сразу. Этот же механизм используется при работе с полями неограниченной длины: при считывании строки они возвращают не целиковое значение, а его длину и генератор для чтения.

Вот, пожалуй, и все, что умеет библиотека на данный момент. В перспективе добавлю возможность работы с файлами конфигураций.

27.12.2024

С момент публикации этой статьи модуль был «причесан» и теперь вполне может считаться production ready.

Из основных изменений:

  • Добавлена возможность работы с контейнерами по аналогии с v8unpack. Об этом даже есть отдельная статья.
  • Добавлены тесты, которые не смотря на свою простоту обеспечивают 98% покрытие кода проекта.
  • Появилась документация.

Если среди вас есть те, кто желает присоединиться к разработке, то добро пожаловать на Github. Что хотелось бы реализовать:

  • Обратную совместимость с Python 2.7.
  • Возможность работы с файлами DT. К сожалению я не нашел достаточно хорошего описания формата и не смог до конца разобраться в нем сам, поэтому буду рад помощи.

13.02.2024

Большое обновление.

  • Рефакторинг механизмов чтения формата 1CD
  • Новый API работы с файловой базой
  • Значительно улучшена документация
  • Исправлена ошибка преобразования значений типа Numeric
  • Исправлена ошибка чтения значений полей, допускающих NULL
  • Ускорено чтение информации о страницах размещения объектов БД
  • Ускорен разбор описаний таблиц БД
  • Ускорено преобразование полей типа DateTime
  • Преобразование значение в полях таблиц теперь происходит в момент обращения к ним, а не в момент чтения строк

05.09.2024

  • Добавлена поддержка 8.3.8

24.02.2024

  • Добавлена возможность распаковки EFD файлов (файлы поставки конфигурации)

32 Comments

  1. pumbaE

    Спасибо через pip найдем, я бы советовал убрать из начала названия «1C» и заменить на что-нибудь другое, ато могут придраться.

    Reply
  2. MishaHD

    Автор молодец, только ради спортивного интереса можно покопаться в структуре файловой базы. Но надо бы убрать из названия 1с, а то могут быть проблемы. Да и вообще наверняка программа нарушает лицензионное соглашение.

    Reply
  3. Infactum

    (2) pumbaE, (3) MishaHD,

    От греха подальше переименую 1С в onec пожалуй.

    Нарушений лицензии я тут никаких не вижу, тем более аналогичных решений достаточно и существуют они давно.

    Reply
  4. cool.vlad4

    (3) MishaHD,

    Да и вообще наверняка программа нарушает лицензионное соглашение.

    совсем не наверняка и не очевидно, что нарушает.

    (2) pumbaE, на гитхабе ищется в две секунды https://github.com/Infactum/1C-dtools

    Reply
  5. Жолтокнижниг

    Сам хотел такое написать на питоне, с удовольствем посмотрю ваше творение.

    Reply
  6. doctorov_s

    Ну так ссылку то ты дашь на свое творение??

    Reply
  7. Infactum

    (11) doctorov_s, стоит быть внимательнее немного.

    Или вам мало того, что пакет опубликован в PiPy, и есть исходники во вложении к статье / ссылка на гитхаб в комментариях?

    Reply
  8. quick

    Я тут недавно для 7-ки задел подобное

    https://github.com/WorldException/v7py

    Reply
  9. Asis

    (0)

    В python 2 не работает:

    >>> import onec_dtools
    Traceback (most recent call last):
    File «<stdin>», line 1, in <module>
    File «C:Python27libsite-packagesonec_dtools\__init__.py», line 1, in <module>
    from onec_dtools.db import Database
    File «C:Python27libsite-packagesonec_dtoolsdb.py», line 5, in <module>
    from onec_dtools.db_row import Row
    File «C:Python27libsite-packagesonec_dtoolsdb_row.py», line 3, in <module>
    from _datetime import datetime
    ImportError: No module named _datetime
    

    Показать

    Reply
  10. Infactum

    (17) Asis, на данный момент заявлена поддержка python 3.4 и 3.5.

    Возможно заработает и на более ранних версиях 3.х — не тестировал.

    Я в курсе, что Python 2.x по прежнему очень популярен, но в моим планах обеспечить его поддержку пока нет. Если желаете помочь в этом — добро пожаловать на гитхаб.

    P.S. Указанная ошибка должна устраниться просто переименованием _datetime в datetime. Но я не думаю, что это будет единственная причина несовместимости с python 3

    Reply
  11. enwony

    Это же github! Дорабатывайте, делайте свою ветку или делайте pull request автору. У меня на python 2.7.8 немного не завелась — поправил, работает (на более ранних версиях может не работать).

    Кстати, автору большое спасибо — отличное начинание и хороший код.

    Reply
  12. Cujoko

    А зачем совместимость с Python 2.7? Люди обычно думают, как бы сделать совместимость с 3.х. Вообще проблемы поставить несколько версий интерпретатора под разные нужды быть не должно.

    Reply
  13. Infactum

    (20) Cujoko, на самом деле полно людей, которые принципиально до сих пор предпочитают python 2.x.

    Я сам ничего бэкпортировать не буду, но если найдется желающий — почему бы и нет? Это ж опенсорс.

    Меня больше работа с DT интересует.

    Reply
  14. Infactum

    Обновлено. Теперь в части чтения 1CD работает намного быстрее и читать строки можно не только итеративно, но и по индексу. Так же исправлено много разных ошибок.

    Reply
  15. Infactum

    Добавил поддержку 8.3.8, благодаря описанию из этой статьи. Вроде бы сейчас onec_dtools единственная open source библиотека, которая работает с этим форматом.

    Reply
  16. kote

    .. так бы и ставил лайки после каждого обновления 🙂 спасибо, что не забрасываете это неблагодарное дело!

    Reply
  17. sss999

    по строению dt файла есть инфа?

    Reply
  18. Infactum

    (25) Нету.

    Если бы было готовое описание формата, то я бы уже добавил его в библиотеку.

    А так надо за хекс редактор садиться. В планах есть, но пока не до этого.

    Reply
  19. sss999

    (26)а как тип распаковывает dt в таблицы? http://expert.chistov.pro/public/183180/

    Reply
  20. Infactum

    (27) Описания формата DT нет в открытом доступе насколько мне известно. Это вовсе не означает, что никто в нем не разобрался.

    Про сабж по ссылке мне известно, но это коммерческий продукт. Очевидно, что его автор помогать мне не будет.

    Собственно по распаковке EFD я тоже к нему обращался, т.к. у него был плагин для Total Commander, и он готов был предоставить исходники, но сказал, что они были утеряны. Поэтому пришлось самому разбираться. Видимо DT ждет та же судьба.

    Reply
  21. Brawler

    В бою не пробовал, но дело вы делаете правидное однозначно!

    Reply
  22. kote

    (0) Отправил Вам pull request на githab в библиотеку — добавил многопоточности при распаковке.

    На сильно допиленной ERP2 результат довольно обнадеживающий — порядка 3х минут против 12ти в исходном варианте.

    Но на маленьких базах результат, наоборот, ухудшится наверное..

    (но там и дельта по времени будет совсем незначительна с другой стороны)

    Reply
  23. kote

    (31)

    Версия с многопоточностью доступна тут:

    https://github.com/pruidzeko/onec_dtools

    Reply
  24. Infactum

    (31) Дошли наконец руки нормально посмотреть..

    Как я понимаю основной задачей у вас было добиться ускорения распаковки. Вижу, что успех есть, но к коду довольно много претензий.

    Во-первых вы используете многопоточность, которая в силу GIL в Python толком не работает. Исключение, на сколько я помню, операции ввода/вывода. Но т.к. в скрипте идет работа с файловой системой, то мы все равно ограничены отсутствием параллельность операций работы с диском (если конечно у вас не NVMe SSD). Единственное, что тут реально можно выполнить параллельно — это разархивация файлов верхнего уровня. Поэтому накидал свой пример тут.

    Как работает:

    — Поднимаем с данные всех внутренних файлов контейнера. Если конфигурация большая, то нужен будет python x64

    — Отдаем эту информацию пулу субпроцессов на обработку. Количество процессов берется по числу ядер системы, поэтому тут мы легко получим 100% CPU

    — Если распакованный файл не является контейнером, то отдаем данные пишущему процессу. Иначе разбираем его на субфайлы (они уже не будут упакованы).

    В целом это должно давать хороший прирост производительности. Хотя Python и скорость работы это вещи слабо совместимые.

    P.S. Как-то очень долго у вас ERP распаковывается. Типовая конфигурация (~650МБ) у меня разбирается за ~35 секунд даже в стоковом режиме. Проблема в основном в диске, на самом деле.

    Reply
  25. kote

    О, спасибо!

    (33)

    Типовая конфигурация (~650МБ) у меня разбирается за ~35 секунд даже в стоковом режиме.

    Удивили.. у меня на работе, SSD Samsung 970EVO 250Gb на шине PCIe, c NVMe

    i5-6400 3.1 ГГц, 8 Гб ОЗУ

    Python 3.7 / x64 использовался..

    ERP, конечно, была сильно «искаверканная» — но такая разница колоссальная с Вашими данными.. попробую типовую спарсить..

    Еще Касперский стоит, но рабочие папки для парсинга в исключениях. Удалить нет никакой возможности из-за политик безопасности.

    ===

    А поделитесь, пожалуйста, Вашей конфигурацией компьютера. Если не сложно, поподробнее.. и используется ли антивирус и какой.

    Reply
  26. Infactum

    (34) Тестировалось все на относительно старом компе.

    i7-3770 @ 3.8 GHz
    16 Gb @ 1866 Mhz
    SSD Samsung 860 @ SATA3
    

    Какой-то Касперский с дефолтными настройками стоит.

    Вот пример распаковки измененной ERP ~1.2ГБ

    1 285 402 831 config.cf
    1 файлов  1 285 402 831 байт
    
    (onec_dtools) E:demo_unpack>powershell Measure-Command { onec_dtoolsexamplesv8unpack_mt.py -U E:demo_unpackconfig.cf E:demo_unpack
    esult }
    
    
    Days              : 0
    Hours             : 0
    Minutes           : 0
    Seconds           : 51
    Milliseconds      : 445
    Ticks             : 514456366
    TotalDays         : 0,000595435608796296
    TotalHours        : 0,0142904546111111
    TotalMinutes      : 0,857427276666667
    TotalSeconds      : 51,4456366
    TotalMilliseconds : 51445,6366
    

    Показать

    Reply
  27. Amunrah

    А можно-ли с помощью этой библиотеки тип и версию конфигурации посмотреть? ) Как сформировать запрос чтобы на выходе получить нечто вроде — Бухгалтерия предприятия — 3.0.64.54?..

    И еще, не по теме вопроса ) в примере кода из описания у вас написано:

    if row.is_empty:
    continue
    for row in db.tables[‘V8USERS’]:
    print(row.as_list(True))
    

    Не должно-ли быть как-то так?:

    for row in db.tables[‘V8USERS’]:
    if row.is_empty:
    continue
    print(row.as_list(True))
    
    Reply
  28. Infactum

    (36) Извлечь версию конфигурации из контейнера конфигурации можно прочитав внутренний файл root. В нем будет идентификатор файла с нужной вам информацией.

    Если надо прямо из конфигурации базы данных информацию получить, то наверное надо ее предварительно прочитать. Полагаю, она где-то в таблице v8config. В общем нужно смотреть и пробовать — это не сложно.

    Другое дело что библиотека просто дает базовый функционал работы с контейнерами и прочими файлами платформы. Остальное — это примеры использования. Такого примера, что вас интересует, у меня нет.

    А насчет примера в описании: да, в статье ошибка. На github пример содержит корректный код.

    Reply
  29. Amunrah

    (37) Библиотека замечательная и похоже действительно уникальная ) буду вникать по возможности, но пока я не знаком с ней на столько тонко, прошу разъяснить некоторые моменты хотя-бы в общих чертах… Процесс чтения таблицы CONFIG оказался весьма продолжительным, вероятно предпочтительней считывать техническую информацию из root-файла (это один из способов, если я вас правильно понял) и вот вопрос как это сделать или в каком направлении копать?.. )

    Запрос — db.tables[‘CONFIG’][40957].as_list(True)

    Выдает следующее — [‘root’, datetime.datetime(2018, 7, 11, 12, 0, 38), datetime.datetime(2018, 7, 11, 12, 0, 38), 0, 46, b'{xbf{x7fxb5x91Nxaax81x99x99YJx92x91xaex89ix8ax99xaex89ex92x89nxa2x91x81x81xaex81x99axb2YRxa2yx8axa9x99xa5N-x00′, 0]

    Запрос — db.tables[‘CONFIG’][40958].as_list(True)

    Выдает примерно тоже самое — [‘version’, datetime.datetime(2018, 7, 11, 12, 0, 38),..

    Вероятно это дата создания. дата модификации, еще параметры, и какая-то бинарная строка данных…

    Reply
  30. Amunrah

    (38) А хотя нет, все в порядке ) нашел. действительно не сложно. Спасибо — библиотека отличная.

    Reply
  31. Infactum

    (38) Бинарные данные просто упакованы zlib. Разбирается так:

    data = row[‘BINARYDATA’].value
    value = zlib.decompressobj(-15).decompress(data).decode(‘utf-8-sig’)
    

    Дальше думаю без проблем разберетесь.

    Reply
  32. Amunrah

    (40) Да разобрался. большое спасибо и огромный респект за такой основательный труд. )

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *