[{"content":"","date":"29 June 2025","externalUrl":null,"permalink":"/tags/chrome-extension/","section":"Tags","summary":"","title":"Chrome Extension","type":"tags"},{"content":"","date":"29 June 2025","externalUrl":null,"permalink":"/","section":"Codeconuts","summary":"","title":"Codeconuts","type":"page"},{"content":"","date":"29 June 2025","externalUrl":null,"permalink":"/tags/coding/","section":"Tags","summary":"","title":"Coding","type":"tags"},{"content":"","date":"29 June 2025","externalUrl":null,"permalink":"/tags/project/","section":"Tags","summary":"","title":"Project","type":"tags"},{"content":"","date":"29 June 2025","externalUrl":null,"permalink":"/projects/","section":"Projects","summary":"","title":"Projects","type":"projects"},{"content":"","date":"29 June 2025","externalUrl":"https://chromewebstore.google.com/detail/rednote-downloader/imnbmifdfhdkhkkfmpckhcjgnlgjfeep","permalink":"/projects/1751214487281-rednote-downloader/","section":"Projects","summary":"","title":"RedNote Downloader","type":"projects"},{"content":"","date":"29 June 2025","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":" Filename Restrictions and Sanitizing Regexs # If the filename contains some invalid Unicode characters, or matches a reserved keyword in NTFS, then chrome.downloads.download will throw Error: Invalid filename.\nAt any position (start/middle/end), the following Unicode characters are invalid:\nControl characters \\p{Cc} (\\u{0} is allowed at the middle) : ? \u0026quot; * \u0026lt; \u0026gt; | ~ (NTFS reserved characters) \\ and / (NTFS \u0026amp; Chrome treat them as path separators instead of a character in filename, so Invalid filename error will NOT occur) Format characters \\p{Cf} Non-characters \\p{Cn} Zero-width joiner \\u{200D} is commonly used to composite emojis and form a new emoji. However, this character is invalid as well, as its category is \u0026ldquo;Format characters\u0026rdquo;.\nThe regex is /[:?\u0026quot;*\u0026lt;\u0026gt;|~/\\\\\\u{1}-\\u{1f}\\u{7f}\\u{80}-\\u{9f}\\p{Cf}\\p{Cn}]/gu\nAt the start/end of filename, the following Unicode characters are invalid:\nNUL character \\u{0} Line separator \\p{Zl} Paragraph separator \\p{Zp} Space separators \\p{Zs} A dot . (rule by NTFS) The regex is /^[.\\u{0}\\p{Zl}\\p{Zp}\\p{Zs}]|[.\\u{0}\\p{Zl}\\p{Zp}\\p{Zs}]$/gu\nReserved keywords in NTFS: Filenames of CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9 (case-insensitive), with or without file extension, are all invalid.\nThe regex is /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?=\\.|$)/gui\nOther categories of Unicode characters, including private-use \\p{Co} and surrogate pairs \\p{Cs}, are allowed at any position.\nFinal regex # The final regex (for JavaScript) replacing all invalid characters with _:\nvar pattern = /[:?\u0026#34;*\u0026lt;\u0026gt;|~/\\\\\\u{1}-\\u{1f}\\u{7f}\\u{80}-\\u{9f}\\p{Cf}\\p{Cn}]|^[.\\u{0}\\p{Zl}\\p{Zp}\\p{Zs}]|[.\\u{0}\\p{Zl}\\p{Zp}\\p{Zs}]$|^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?=\\.|$)/gui; filename.replaceAll(pattern, \u0026#39;_\u0026#39;); Note: These characters/filenames are also invalid in NTFS, but sometimes NTFS just deletes the character instead of displaying an error.\nDetermining the Invalid Unicode characters # I wrote a script to try each Unicode character, and ran it on Google Chrome 126.0.6478.116 on Windows 11 23H2 22631.3737.\nWhat the script does:\nCall serviceWorker.postMessage('') in SW console to start/stop the for loop Create an empty blob and an Object URL to the blob in offscreen document Call chrome.downloads.download, pass the Unicode character as filename and the URL created Try to catch Error: Invalid filename. The character is invalid if error caught, otherwise valid. Store the result in chrome.storage.local Clear the download history periodically to maintain performance The invalid Unicode characters at the start/end are found. Within the set, some characters are invalid at the middle of a filename as well. So we run the script again, replacing filename: char with\nfilename: `0${char}0` background.js:\nvar creating; async function setup_offscreen_document() { const offscreenUrl = chrome.runtime.getURL(\u0026#34;offscreen.html\u0026#34;); const existingContexts = await chrome.runtime.getContexts({ contextTypes: [\u0026#39;OFFSCREEN_DOCUMENT\u0026#39;], documentUrls: [offscreenUrl] }); if (existingContexts.length \u0026gt; 0) { return; } if (creating) { await creating; } else { creating = chrome.offscreen.createDocument({ url: \u0026#34;offscreen.html\u0026#34;, reasons: [\u0026#39;BLOBS\u0026#39;], justification: \u0026#39;Create object URLs\u0026#39;, }); await creating; creating = null; } } var stop = true; var url; async function test(code) { var char = String.fromCodePoint(code); try { chrome.downloads.cancel(await chrome.downloads.download({ url: url, filename: char })); chrome.storage.local.set({ [code]: { char: char, invalid: false } }); } catch (error) { if (error.message == \u0026#34;Invalid filename\u0026#34;) chrome.storage.local.set({ [code]: { char: char, invalid: true } }); else throw error; } } async function loop() { await setup_offscreen_document(); url = await chrome.runtime.sendMessage(undefined, {}); var start = (await chrome.storage.local.get(\u0026#39;last\u0026#39;))[\u0026#39;last\u0026#39;]; if (start == undefined) start = 0; for (var i = start + 1; i \u0026lt;= 0x10ffff; i++) { await test(i); await chrome.storage.local.set({ last: i }); if ((i - start) % 10000 == 0) chrome.browsingData.removeDownloads({}); if (stop) break; } } function start_loop() { stop = false; loop(); } function stop_loop() { stop = true; } self.addEventListener(\u0026#39;message\u0026#39;, function (e) { if (stop) start_loop(); else stop_loop(); }); manifest.json:\n{ \u0026#34;manifest_version\u0026#34;: 3, \u0026#34;name\u0026#34;: \u0026#34;Unicode Test\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;1.0\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Test.\u0026#34;, \u0026#34;background\u0026#34;: { \u0026#34;service_worker\u0026#34;: \u0026#34;background.js\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;module\u0026#34; }, \u0026#34;permissions\u0026#34;: [ \u0026#34;downloads\u0026#34;, \u0026#34;storage\u0026#34;, \u0026#34;unlimitedStorage\u0026#34;, \u0026#34;offscreen\u0026#34;, \u0026#34;browsingData\u0026#34; ] } ","date":"27 June 2024","externalUrl":null,"permalink":"/posts/1719490680406-filename-sanitizer-for-chrome.downloads-api/","section":"Posts","summary":"","title":"Filename Sanitizer for chrome.downloads API","type":"posts"},{"content":"","date":"27 June 2024","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":" Prerequisites # Cross Products Are Linear Maps # For a cross product \\(\\textbf{a}\\times\\textbf{b}\\), we may consider it as an operation \\(\\textbf{a}_{\\times}\\) acting on \\(\\textbf{b}\\). Note that the linearity of cross product\n$$\\textbf{a}_\\times(\\textbf{b}+\\textbf{c})=\\textbf{a}_\\times\\textbf{b}+\\textbf{a}_\\times\\textbf{c}$$implies that \\(\\textbf{a}_\\times\\) is a linear transformation. Therefore, we can represent \\(\\textbf{a}_\\times\\) with a \\(3\\times3\\) matrix. One can work out the cross product to show that\n$$\\textbf{a}_\\times=\\begin{pmatrix}0\u0026-a_z\u0026a_y\\\\ a_z\u00260\u0026-a_x\\\\ -a_y\u0026a_x\u00260\\end{pmatrix}$$Note that \\(\\textbf{a}_\\times\\) is skew-symmetric, which makes it a generator of rotation.\nAxis-Angle Representation # By Euler\u0026rsquo;s Rotation Theorem, any rotation in \\(\\mathbb{R}^{3}\\) can be represented by\nAn axis of rotation \\(\\hat{\\textbf{k}}\\) An angular displacement / angle or rotation \\(\\theta\\) about \\(\\hat{\\textbf{k}}\\) To preserve bijection between the rotation group and axis-angle representation, we define the right-hand rule, where\nThe thumb points in the direction of \\(\\hat{\\textbf{k}}\\) All other fingers point in the direction of rotation We can then define the axis-angle vector which represents a rotation $$\\boldsymbol{\\theta}=\\theta\\hat{\\textbf{k}}\\qquad\\theta\\in(-\\pi,\\pi],\\quad\\hat{\\textbf{k}}\\in\\{\\textbf{v}\\in\\mathbb{R}_{\\geq0}^3\\mid |\\textbf{v}|=1\\}$$ Rodrigues\u0026rsquo; Rotation Formula # In \\(\\mathbb{R}^3\\), if \\(v\\) is rotated about \\(\\hat{\\mathbf{k}}\\) for an angle \\(\\theta\\), the resulting vector is $$\\mathbf{v}_{\\text {rot}} =\\mathbf{v}+\\sin (\\theta) \\hat{\\mathbf{k}} \\times \\mathbf{v}+(1-\\cos \\theta) \\hat{\\mathbf{k}} \\times(\\hat{\\mathbf{k}} \\times \\mathbf{v})$$We can collect the cross product operations and define the rotation matrix for the aforementioned rotation\n$$R=I+\\sin(\\theta)\\hat{\\mathbf{k}}_\\times+(1-\\cos\\theta)\\hat{\\mathbf{k}}_\\times^2$$With the Taylor series of \\(e^x\\), we can verify that\n$$R=\\exp(\\theta\\hat{\\mathbf{k}}_\\times)$$Hence, rotation generated by \\(\\theta\\hat{\\mathbf{k}}_\\times\\) is exactly the same as the rotation represented by the axis-angle vector \\(\\theta\\hat{\\mathbf{k}}\\).\nReal Eigenvector and Axis of Rotation # The eigenspace with eigenvalue \\(1\\) is the axis of rotation.\nRotation matrices have orthogonal eigenspaces for distinct eigenvalues, and the eigenvalue \\(1\\) is not repeated (except for \\(I\\)), so it corresponds to a 1D eigenspace. Also, the axis-angle vector \\(\\theta\\hat{\\mathbf{k}}\\) satisfies \\(\\exp(\\theta\\hat{\\mathbf{k}}_\\times)\\theta\\hat{\\mathbf{k}}=\\theta\\hat{\\mathbf{k}}\\) can be easily verified using Rodrigues\u0026rsquo; Rotation Formula. Therefore, \\(\\theta\\hat{\\mathbf{k}}\\) spans the 1D eigenspace with eigenvalue \\(1\\), making the eigenspace the axis of rotation.\n1st Theorem # Motivation # In spherical coordinates \\((r,\\theta,\\phi)\\), suppose we would like to move \\((r,0,0)\\) to \\((r,\\theta,\\phi)\\).\nWe can imagine first pulling the vector \\(r\\hat{\\textbf{x}}\\) up by angle \\(\\pi-\\phi\\), then rotating it on the \\(x\\)-\\(y\\) plane by angle \\(\\theta\\). This is equivalent to first performing a rotation \\(\\boldsymbol{\\phi}=(\\pi-\\phi)(-\\hat{\\mathbf{x}})\\), then a rotation \\(\\boldsymbol{\\theta}=\\theta\\hat{\\mathbf{z}}\\). However, we can also imagine first rotating the vector on the \\(x\\)-\\(y\\) plane by angle \\(\\theta\\), then pulling it up by angle \\(\\pi-\\phi\\). This is equivalent to first performing a rotation \\(\\boldsymbol{\\theta}=\\theta\\hat{\\mathbf{z}}\\), then a rotation \\(\\boldsymbol{\\phi}=(\\pi-\\phi)(\\sin(\\theta)\\hat{\\mathbf{y}}-\\cos(\\theta)\\hat{\\mathbf{x}})\\). Note that for the action of pulling the vector up by angle \\(\\pi-\\phi\\), its corresponding axis-angle vector \\(\\boldsymbol{\\phi}\\) has rotated too.\nStatement # Below, we prove in general, and coordinates-free, that the order of any two rotations can be swapped, if after swapping, we rotate the axis-angle vector of the 2nd rotation according to the 1st rotation as well.\nMathematically, denote the following\n\\(A=\\exp(\\textbf{a}_\\times)\\) as the rotation matrix for the axis-angle vector \\(\\textbf{a}\\) \\(B=\\exp(\\textbf{b}_\\times)\\) as the rotation matrix for the axis-angle vector \\(\\textbf{b}\\) Additionally, we denote \\(A'\\) as the rotation matrix for the angular displacement vector \\(\\textbf{a}'\\), where \\(\\textbf{a}'=B\\textbf{a}\\). Then, the theorem states that $$BA=A'B$$ Proof # Let \\(\\mathcal{B}\\) be the orthonormal basis of the column vectors of the rotation matrix \\(B\\). Denote \\([\\textbf{v}]_\\mathcal{B}=B^{-1}\\textbf{v}\\) the vector \\(\\textbf{v}\\) in the basis \\(\\mathcal{B}\\), and \\([M]_\\mathcal{B}=B^{-1}MB\\) the matrix in the basis \\(\\mathcal{B}\\).\nWe will show that for any \\(\\textbf{v}\\in\\mathbb{R}^3\\),\n$$\\exp(([\\textbf{v}]_\\mathcal{B})_\\times)=\\exp([\\textbf{v}_\\times]_\\mathcal{B})=[\\exp(\\textbf{v}_\\times)]_\\mathcal{B}$$We will first show the left-side identity. For fixed vectors \\(\\textbf{v},\\textbf{w}\\) and any arbitrary vector \\(\\textbf{u}\\), recall the volume of the parallelepiped formed by the 3 vectors, or alternatively by the scalar triple product, we have $$\\langle B\\textbf{u},B\\textbf{v}\\times B\\textbf{w}\\rangle=\\det\\begin{pmatrix}\\mid\u0026\\mid\u0026\\mid \\\\ B\\textbf{u}\u0026B\\textbf{v}\u0026B\\textbf{w} \\\\ \\mid\u0026\\mid\u0026\\mid\\end{pmatrix}$$ Also, since \\(B\\) is a rotation matrix, which has properties \\(\\det(B)=1\\) and \\(\\langle\\textbf{p},\\textbf{q}\\rangle=\\langle B\\textbf{p},B\\textbf{q}\\rangle\\), $$\\begin{align*}\\det\\begin{pmatrix}\\mid\u0026\\mid\u0026\\mid \\\\ B\\textbf{u}\u0026B\\textbf{v}\u0026B\\textbf{w} \\\\ \\mid\u0026\\mid\u0026\\mid\\end{pmatrix}\u0026= \\det(B)\\det\\begin{pmatrix}\\mid\u0026\\mid\u0026\\mid \\\\ \\textbf{u}\u0026\\textbf{v}\u0026\\textbf{w} \\\\ \\mid\u0026\\mid\u0026\\mid\\end{pmatrix}\\\\ \u0026=\\det(B)\\langle \\textbf{u},\\textbf{v}\\times \\textbf{w}\\rangle\\\\ \u0026=\\langle B\\textbf{u},B(\\textbf{v}\\times \\textbf{w})\\rangle\\end{align*}$$Hence, for any arbitrary vector \\(\\textbf{u}\\), $$\\langle B\\textbf{u},B\\textbf{v}\\times B\\textbf{w}\\rangle=\\langle B\\textbf{u},B(\\textbf{v}\\times \\textbf{w})\\rangle$$ which implies $$B\\textbf{v}\\times B\\textbf{w}= B(\\textbf{v}\\times\\textbf{w})$$ i.e. we can factor out the matrix \\(B\\). Then, for any vector \\(\\textbf{w}\\), $$\\begin{align*} B^{-1}\\textbf{v}\\times\\textbf{w}\u0026= B^{-1}(\\textbf{v}\\times B\\textbf{w})\\\\ \u0026= B^{-1}\\textbf{v}_\\times B\\textbf{w}\\\\ ([\\textbf{v}]_\\mathcal{B})_\\times\\textbf{w}\u0026= [\\textbf{v}_\\times]_\\mathcal{B}\\textbf{w} \\end{align*}$$ This implies $$([\\textbf{v}]_\\mathcal{B})_\\times= [\\textbf{v}_\\times]_\\mathcal{B}$$ which gives the left-side identity if we \\(\\exp\\) both sides. The right-side identity is straightforward. By the Taylor series of \\(e^x\\), we get $$\\begin{align*} \\exp([\\textbf{v}_\\times]_\\mathcal{B})\u0026=I+[\\textbf{v}_\\times]_\\mathcal{B}+ \\frac{1}{2}([\\textbf{v}_\\times]_\\mathcal{B})^{2}+\\cdots\\\\ \u0026=B^{-1}B+B^{-1}\\textbf{v}_\\times B+ \\frac{1}{2}(B^{-1}\\textbf{v}_\\times B)^{2}+\\cdots\\\\ \u0026=B^{-1}B+B^{-1}\\textbf{v}_\\times B+ \\frac{1}{2}B^{-1}\\textbf{v}_\\times^{2}B+\\cdots\\\\ \u0026=B^{-1}\\exp(\\textbf{v}_\\times)B\\\\ \u0026= [\\exp(\\textbf{v}_\\times)]_\\mathcal{B} \\end{align*}$$ Hence, we conclude with the identity $$\\exp(([\\textbf{v}]_\\mathcal{B})_\\times)=[\\exp(\\textbf{v}_\\times)]_\\mathcal{B}$$Substitute \\(\\textbf{v}=\\textbf{a}'\\) on both sides of the equation, we get $$\\begin{align*} \\exp((B^{-1}B\\textbf{a})_\\times)\u0026=B^{-1}\\exp({\\textbf{a}'}_\\times)B\\\\ BA\u0026=A'B \\end{align*}$$ 2nd Theorem # Statement # Two rotations commute if and only if one of the following\ntheir axes of rotations are parallel at least one of the rotations is of angle \\(0\\) their axes of rotations are perpendicular and the angles of rotations are \\(\\pi\\) Proof # Define two rotations \\(R_1=\\exp(\\theta_1(\\hat{\\textbf{k}}_1)_\\times),R_2=\\exp(\\theta_2(\\hat{\\textbf{k}}_2)_\\times)\\).\nWe first show that two commuting rotations imply one of the 3 conditions. Using the 1st theorem, we get \\(R_2R_1=R_1'R_2\\) and \\(R_1R_2=R_2'R_1\\). Assuming \\(R_2R_1=R_1R_2\\), then we get the system of equations\n$$\\begin{cases}R_1R_2=R_1'R_2\\\\R_2R_1=R_2'R_1\\end{cases}\\Longrightarrow\\begin{cases}R_1=R_1'\\\\R_2=R_2'\\end{cases}$$We focus on the 1st equation. It can be written as\n$$\\exp(\\theta_1(\\hat{\\textbf{k}}_1)_\\times)=\\exp(\\theta_1'(\\hat{\\textbf{k}}_1')_\\times)$$where \\(R_2(\\theta_1\\hat{\\textbf{k}}_1)=\\theta_1'\\hat{\\textbf{k}}_1'\\). Note that \\(\\theta_1=0\\) satisfies the equation with no restrictions on \\(R_2\\). We assume below that \\(\\theta_1\\neq0\\). Then,\n$$\\exp(\\theta_1(\\hat{\\textbf{k}}_1)_\\times)\\hat{\\textbf{k}}_1'=\\exp(\\theta_1'(\\hat{\\textbf{k}}_1')_\\times)\\hat{\\textbf{k}}_1'=\\hat{\\textbf{k}}_1'$$So \\(\\hat{\\textbf{k}}_1'\\) is in the eigenspace spanned by \\(\\hat{\\textbf{k}}_1\\). Both are unit vectors, and we restricted the unit vectors to the first quadrant, so \\(\\hat{\\textbf{k}}_1'=\\hat{\\textbf{k}}_1\\). Then,\n$$R_2(\\theta_1\\hat{\\textbf{k}}_1)=\\theta_1'\\hat{\\textbf{k}}_1$$This implies \\(\\hat{\\textbf{k}}_1\\) is an eigenvector of \\(R_2\\). Since \\(\\theta_1,\\theta_1'\\in\\mathbb{R}\\), \\(\\theta_1'/\\theta_1=1\\) which is the only real eigenvalue of \\(R_2\\) for general \\(\\theta_2\\). This implies \\(\\hat{\\textbf{k}}_1=\\hat{\\textbf{k}}_2\\). An exception is when \\(\\theta_2=\\pi\\), it is also possible that \\(\\theta_1'/\\theta_1=-1\\), which is another real eigenvalue. Since this eigenvalue is different from that of the axis-angle vector, and rotation matrices have orthogonal eigenspaces, \\(\\hat{\\textbf{k}}_1\\cdot\\hat{\\textbf{k}}_2=0\\).\nWe can use the same argument on the 2nd equation to obtain \\(\\hat{\\textbf{k}}_1=\\hat{\\textbf{k}}_2\\) for general \\(\\theta_1\\), and an exception at \\(\\theta_1=\\pi\\) where \\(\\hat{\\textbf{k}}_1\\cdot\\hat{\\textbf{k}}_2=0\\).\nNow we show that any of the 3 conditions is sufficient for the two rotations to commute.\nIf \\(\\hat{\\textbf{k}}_1=\\hat{\\textbf{k}}_2\\), we can apply the Baker–Campbell–Hausdorff formula to get $$\\begin{align*}\\exp(\\theta_1(\\hat{\\textbf{k}}_1)_\\times)\\exp(\\theta_2(\\hat{\\textbf{k}}_1)_\\times)\u0026=\\exp((\\theta_1+\\theta_2)(\\hat{\\textbf{k}}_1)_\\times)\\\\\u0026=\\exp((\\theta_2+\\theta_1)(\\hat{\\textbf{k}}_1)_\\times)\\\\\u0026=\\exp(\\theta_2(\\hat{\\textbf{k}}_1)_\\times)\\exp(\\theta_1(\\hat{\\textbf{k}}_1)_\\times)\\end{align*}$$ A rotation of angle \\(0\\) implies \\(R=I\\), which commutes with any matrix.\nPlugging \\(\\theta=\\pi\\) into Rodrigues\u0026rsquo; Rotation Formula, we get $$R=I+2\\hat{\\mathbf{k}}_\\times^2$$ We compute the commutator $$[R_1,R_2]=[I+2(\\hat{\\mathbf{k}}_1)_\\times^2,I+2(\\hat{\\mathbf{k}}_2)_\\times^2]=[2(\\hat{\\mathbf{k}}_1)_\\times^2,2(\\hat{\\mathbf{k}}_2)_\\times^2]$$ For any vector \\(\\textbf{v}\\), given that \\(\\hat{\\mathbf{k}}_1\\cdot\\hat{\\mathbf{k}}_2=0\\), and by applying the Vector Triple Product, we get (note that cross products are not associative) $$\\begin{align*} \u0026\\hat{\\mathbf{k}}_1\\times\\hat{\\mathbf{k}}_1\\times\\hat{\\mathbf{k}}_2\\times\\hat{\\mathbf{k}}_2\\times\\textbf{v}\\\\\u0026=\\hat{\\mathbf{k}}_1\\times\\hat{\\mathbf{k}}_1\\times((\\hat{\\mathbf{k}}_2\\cdot\\textbf{v})\\hat{\\mathbf{k}}_2-\\textbf{v})\\\\ \u0026=(\\hat{\\mathbf{k}}_2\\cdot\\textbf{v})((\\hat{\\mathbf{k}}_1\\cdot\\hat{\\mathbf{k}}_2)\\hat{\\mathbf{k}}_1-\\hat{\\mathbf{k}}_2)-(\\hat{\\mathbf{k}}_1\\cdot\\textbf{v})\\hat{\\mathbf{k}}_1-\\textbf{v}\\\\ \u0026=-(\\hat{\\mathbf{k}}_2\\cdot\\textbf{v})\\hat{\\mathbf{k}}_2-(\\hat{\\mathbf{k}}_1\\cdot\\textbf{v})\\hat{\\mathbf{k}}_1-\\textbf{v} \\end{align*}$$ Swapping indicies \\(1\\) and \\(2\\) give the same result. Hence, \\([(\\hat{\\mathbf{k}}_1)_\\times^2,(\\hat{\\mathbf{k}}_2)_\\times^2]=0\\) which implies \\([R_1,R_2]=0\\).\n","date":"24 March 2024","externalUrl":null,"permalink":"/posts/1711291860559-commutativity-of-rotations/","section":"Posts","summary":"","title":"Commutativity of Rotations","type":"posts"},{"content":"","date":"24 March 2024","externalUrl":null,"permalink":"/tags/math/","section":"Tags","summary":"","title":"Math","type":"tags"},{"content":"","date":"25 May 2019","externalUrl":"https://github.com/codeconuts/WinPosReset","permalink":"/projects/1558798920649-winposreset/","section":"Projects","summary":"","title":"Reset Positions and Sizes of Windows","type":"projects"},{"content":"","date":"25 May 2019","externalUrl":null,"permalink":"/tags/util/","section":"Tags","summary":"","title":"Util","type":"tags"},{"content":" Introduction # Assume a security manager is present that doesn\u0026rsquo;t grant the setSecurityManager permission. However, if it grants the following 3 permissions\naccessDeclaredMembers suppressAccessChecks getProtectionDomain then the security manager can be disabled with the method we discuss in this article. Optionally, the permission accessClassInPackage.jdk.internal.reflect will make things more convenient.\nProblem # setSecurityManager Is Guarded # The security manager guards certain methods from being invoked by callers without the appropriate permissions. Below is the source code of the security field, getSecurityManager() and setSecurityManager(SecurityManager) methods in java.lang.System.\nprivate static volatile SecurityManager security; /** * Sets the System security. * * \u0026lt;p\u0026gt; If there is a security manager already installed, this method first * calls the security manager\u0026#39;s \u0026lt;code\u0026gt;checkPermission\u0026lt;/code\u0026gt; method * with a \u0026lt;code\u0026gt;RuntimePermission(\u0026#34;setSecurityManager\u0026#34;)\u0026lt;/code\u0026gt; * permission to ensure it\u0026#39;s ok to replace the existing * security manager. * This may result in throwing a \u0026lt;code\u0026gt;SecurityException\u0026lt;/code\u0026gt;. * * \u0026lt;p\u0026gt; Otherwise, the argument is established as the current * security manager. If the argument is \u0026lt;code\u0026gt;null\u0026lt;/code\u0026gt; and no * security manager has been established, then no action is taken and * the method simply returns. * * @param s the security manager. * @exception SecurityException if the security manager has already * been set and its \u0026lt;code\u0026gt;checkPermission\u0026lt;/code\u0026gt; method * doesn\u0026#39;t allow it to be replaced. * @see #getSecurityManager * @see SecurityManager#checkPermission * @see java.lang.RuntimePermission */ public static void setSecurityManager(final SecurityManager s) { if (security == null) { // ensure image reader is initialized Object.class.getResource(\u0026#34;java/lang/ANY\u0026#34;); } if (s != null) { try { s.checkPackageAccess(\u0026#34;java.lang\u0026#34;); } catch (Exception e) { // no-op } } setSecurityManager0(s); } private static synchronized void setSecurityManager0(final SecurityManager s) { SecurityManager sm = getSecurityManager(); if (sm != null) { // ask the currently installed security manager if we // can replace it. sm.checkPermission(new RuntimePermission (\u0026#34;setSecurityManager\u0026#34;)); } if ((s != null) \u0026amp;\u0026amp; (s.getClass().getClassLoader() != null)) { // New security manager class is not on bootstrap classpath. // Cause policy to get initialized before we install the new // security manager, in order to prevent infinite loops when // trying to initialize the policy (which usually involves // accessing some security and/or system properties, which in turn // calls the installed security manager\u0026#39;s checkPermission method // which will loop infinitely if there is a non-system class // (in this case: the new security manager class) on the stack). AccessController.doPrivileged(new PrivilegedAction\u0026lt;\u0026gt;() { public Object run() { s.getClass().getProtectionDomain().implies (SecurityConstants.ALL_PERMISSION); return null; } }); } security = s; } /** * Gets the system security interface. * * @return if a security manager has already been established for the * current application, then that security manager is returned; * otherwise, \u0026lt;code\u0026gt;null\u0026lt;/code\u0026gt; is returned. * @see #setSecurityManager */ public static SecurityManager getSecurityManager() { return security; } As you can see, calling System.setSecurityManager(null) will not work if a security manager is present and does not grant us the setSecurityManager permission.\nsecurity Is Filtered from Reflection # Reflection is commonly used for accessing private class elements, such as private fields. However, the fields returned from the reflection methods go through a filter, removing those that are sensitive or unsafe. The security field in java.lang.System which holds a java.lang.SecurityManager is an example. Without the Field object, we cannot even do AccessibleObject.setAccessible(boolean).\nInspecting The Filter Class # Lets take a look of the source codes and track to the filter class. First, lets take a look at the method getDeclaredFields in java.lang.Class class.\n/** * Returns an array of {@code Field} objects reflecting all the fields * declared by the class or interface represented by this * {@code Class} object. This includes public, protected, default * (package) access, and private fields, but excludes inherited fields. * * \u0026lt;p\u0026gt; If this {@code Class} object represents a class or interface with no * declared fields, then this method returns an array of length 0. * * \u0026lt;p\u0026gt; If this {@code Class} object represents an array type, a primitive * type, or void, then this method returns an array of length 0. * * \u0026lt;p\u0026gt; The elements in the returned array are not sorted and are not in any * particular order. * * @return the array of {@code Field} objects representing all the * declared fields of this class * @throws SecurityException * If a security manager, \u0026lt;i\u0026gt;s\u0026lt;/i\u0026gt;, is present and any of the * following conditions is met: * * \u0026lt;ul\u0026gt; * * \u0026lt;li\u0026gt; the caller\u0026#39;s class loader is not the same as the * class loader of this class and invocation of * {@link SecurityManager#checkPermission * s.checkPermission} method with * {@code RuntimePermission(\u0026#34;accessDeclaredMembers\u0026#34;)} * denies access to the declared fields within this class * * \u0026lt;li\u0026gt; the caller\u0026#39;s class loader is not the same as or an * ancestor of the class loader for the current class and * invocation of {@link SecurityManager#checkPackageAccess * s.checkPackageAccess()} denies access to the package * of this class * * \u0026lt;/ul\u0026gt; * * @since 1.1 * @jls 8.2 Class Members * @jls 8.3 Field Declarations */ @CallerSensitive public Field[] getDeclaredFields() throws SecurityException { SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkMemberAccess(sm, Member.DECLARED, Reflection.getCallerClass(), true); } return copyFields(privateGetDeclaredFields(false)); } The fields are obtained from privateGetDeclaredFields(false).\n// // // java.lang.reflect.Field handling // // // Returns an array of \u0026#34;root\u0026#34; fields. These Field objects must NOT // be propagated to the outside world, but must instead be copied // via ReflectionFactory.copyField. private Field[] privateGetDeclaredFields(boolean publicOnly) { Field[] res; ReflectionData\u0026lt;T\u0026gt; rd = reflectionData(); if (rd != null) { res = publicOnly ? rd.declaredPublicFields : rd.declaredFields; if (res != null) return res; } // No cached value available; request value from VM res = Reflection.filterFields(this, getDeclaredFields0(publicOnly)); if (rd != null) { if (publicOnly) { rd.declaredPublicFields = res; } else { rd.declaredFields = res; } } return res; } As you can see, there is an invocation to the method Reflection.filterFields. That is the important line, where the fields get filtered. If you check the import statements, the Reflection class is actually jdk.internal.reflect.Reflection. Lets take a look at the filter method.\npublic static Field[] filterFields(Class\u0026lt;?\u0026gt; containingClass, Field[] fields) { if (fieldFilterMap == null) { // Bootstrapping return fields; } return (Field[])filter(fields, fieldFilterMap.get(containingClass)); } This method invokes a filter method, which takes an array of Member, and the names of the fields that should be filtered.\nprivate static Member[] filter(Member[] members, String[] filteredNames) { if ((filteredNames == null) || (members.length == 0)) { return members; } int numNewMembers = 0; for (Member member : members) { boolean shouldSkip = false; for (String filteredName : filteredNames) { if (member.getName() == filteredName) { shouldSkip = true; break; } } if (!shouldSkip) { ++numNewMembers; } } Member[] newMembers = (Member[])Array.newInstance(members[0].getClass(), numNewMembers); int destIdx = 0; for (Member member : members) { boolean shouldSkip = false; for (String filteredName : filteredNames) { if (member.getName() == filteredName) { shouldSkip = true; break; } } if (!shouldSkip) { newMembers[destIdx++] = member; } } return newMembers; } Inspecting the Filter Maps # Finally, lets take at the declaration of the fieldFilterMap.\n/** Used to filter out fields and methods from certain classes from public view, where they are sensitive or they may contain VM-internal objects. These Maps are updated very rarely. Rather than synchronize on each access, we use copy-on-write */ private static volatile Map\u0026lt;Class\u0026lt;?\u0026gt;,String[]\u0026gt; fieldFilterMap; private static volatile Map\u0026lt;Class\u0026lt;?\u0026gt;,String[]\u0026gt; methodFilterMap; static { Map\u0026lt;Class\u0026lt;?\u0026gt;,String[]\u0026gt; map = new HashMap\u0026lt;Class\u0026lt;?\u0026gt;,String[]\u0026gt;(); map.put(Reflection.class, new String[] {\u0026#34;fieldFilterMap\u0026#34;, \u0026#34;methodFilterMap\u0026#34;}); map.put(System.class, new String[] {\u0026#34;security\u0026#34;}); map.put(Class.class, new String[] {\u0026#34;classLoader\u0026#34;}); fieldFilterMap = map; methodFilterMap = new HashMap\u0026lt;\u0026gt;(); } As you can see, the Javadoc also clearly states the functionality. By default, fields in java.lang.System named security, fields in java.lang.Class named classLoader, and fields in jdk.internal.reflect.Reflection named fieldFilterMap or methodFilterMap, are filtered. For methods, none are filtered by default.\nSolution: Obtain security Natively # Get the security Field # If you look within the privateGetDeclaredFields(boolean) method, it actually calls the underlying native method getDeclaredFields0(boolean), which returns a raw array of Fields. So, lets call the native method on java.lang.Class\u0026lt;System\u0026gt;, and select the security field.\nMethod getDeclaredFields0M = Class.class.getDeclaredMethod(\u0026#34;getDeclaredFields0\u0026#34;, boolean.class); getDeclaredFields0M.setAccessible(true); Field[] fields = (Field[]) getDeclaredFields0M.invoke(System.class, false); Field securityField = null; for (Field field : fields) if (field.getName().equals(\u0026#34;security\u0026#34;)) securityField = field; Set Its Value via Reflection # Since we now have obtained the target field, what remains is to simply set the value via reflection.\nsecurityField.setAccessible(true); securityField.set(null, null); Thats it! The security field has been set to null. In other words, the Java Security Manager is disabled. You can check with System.getSecurityManager() != null, which will return false.\nOptional: Motifying the Filter Map # We can move one step further and remove security from jdk.internal.reflect.Reflection.fieldFilterMap. However, the fieldFilterMap also marks to filter itself, so we need to use solution 1 on fieldFilerMap. This solution also requires an additional permission of accessClassInPackage.jdk.internal.reflect, otherwise we are not allowed to access packages which are not exported from another module with reflection.\nObtain fieldFilterMap Field # Since we cannot reference jdk.internal.reflect.Reflection, we need to use Class.forName(String).\nMethod getDeclaredFields0M = Class.class.getDeclaredMethod(\u0026#34;getDeclaredFields0\u0026#34;, boolean.class); getDeclaredFields0M.setAccessible(true); Field[] fields = (Field[]) getDeclaredFields0M.invoke(Class.forName(\u0026#34;jdk.internal.reflect.Reflection\u0026#34;), false); Field mapField = null; for (Field field : fields) if (field.getName().equals(\u0026#34;fieldFilterMap\u0026#34;)) mapField = field; Get the Map via Reflection # We don\u0026rsquo;t want to set the field to null, otherwise the filter will malfunction. Instead, get the Map and remove the entries.\nInspect setAccessible(boolean) # However, setAccessible(boolean) will also explicitly check whether the Reflection class is exported to us, regardless of accessClassInPackage permission.\n/** * @throws InaccessibleObjectException {@inheritDoc} * @throws SecurityException {@inheritDoc} */ @Override @CallerSensitive public void setAccessible(boolean flag) { AccessibleObject.checkPermission(); if (flag) checkCanSetAccessible(Reflection.getCallerClass()); setAccessible0(flag); } The first suspicious check is AccessibleObject.checkPermission(), which routes to here.\n/** * The Permission object that is used to check whether a client * has sufficient privilege to defeat Java language access * control checks. */ private static final java.security.Permission ACCESS_PERMISSION = new ReflectPermission(\u0026#34;suppressAccessChecks\u0026#34;); static void checkPermission() { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(ACCESS_PERMISSION); } This is not the cause. The second suspicious check is checkCanSetAccessible(Reflection.getCallerClass()), which routes to here.\n@Override void checkCanSetAccessible(Class\u0026lt;?\u0026gt; caller) { checkCanSetAccessible(caller, clazz); } Which routes to AccessibleObject.checkCanSetAccessible(Class\u0026lt;?\u0026gt;, Class\u0026lt;?\u0026gt;).\nvoid checkCanSetAccessible(Class\u0026lt;?\u0026gt; caller, Class\u0026lt;?\u0026gt; declaringClass) { checkCanSetAccessible(caller, declaringClass, true); } private boolean checkCanSetAccessible(Class\u0026lt;?\u0026gt; caller, Class\u0026lt;?\u0026gt; declaringClass, boolean throwExceptionIfDenied) { Module callerModule = caller.getModule(); Module declaringModule = declaringClass.getModule(); if (callerModule == declaringModule) return true; if (callerModule == Object.class.getModule()) return true; if (!declaringModule.isNamed()) return true; String pn = declaringClass.getPackageName(); int modifiers; if (this instanceof Executable) { modifiers = ((Executable) this).getModifiers(); } else { modifiers = ((Field) this).getModifiers(); } // class is public and package is exported to caller boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers()); if (isClassPublic \u0026amp;\u0026amp; declaringModule.isExported(pn, callerModule)) { // member is public if (Modifier.isPublic(modifiers)) { logIfExportedForIllegalAccess(caller, declaringClass); return true; } // member is protected-static if (Modifier.isProtected(modifiers) \u0026amp;\u0026amp; Modifier.isStatic(modifiers) \u0026amp;\u0026amp; isSubclassOf(caller, declaringClass)) { logIfExportedForIllegalAccess(caller, declaringClass); return true; } } // package is open to caller if (declaringModule.isOpen(pn, callerModule)) { logIfOpenedForIllegalAccess(caller, declaringClass); return true; } if (throwExceptionIfDenied) { // not accessible String msg = \u0026#34;Unable to make \u0026#34;; if (this instanceof Field) msg += \u0026#34;field \u0026#34;; msg += this + \u0026#34; accessible: \u0026#34; + declaringModule + \u0026#34; does not \\\u0026#34;\u0026#34;; if (isClassPublic \u0026amp;\u0026amp; Modifier.isPublic(modifiers)) msg += \u0026#34;exports\u0026#34;; else msg += \u0026#34;opens\u0026#34;; msg += \u0026#34; \u0026#34; + pn + \u0026#34;\\\u0026#34; to \u0026#34; + callerModule; InaccessibleObjectException e = new InaccessibleObjectException(msg); if (printStackTraceWhenAccessFails()) { e.printStackTrace(System.err); } throw e; } return false; } As you can see, InaccessibleObjectException is thrown when jdk.internal.reflect is not exported.\nCall setAccessible0(boolean) # To solve this issue, we can simply invoke the setAccessible0(boolean) via reflection, which directly enables the suppressor for Java language access control.\nMethod setAccessible0M = AccessibleObject.class.getDeclaredMethod(\u0026#34;setAccessible0\u0026#34;, boolean.class); setAccessible0M.setAccessible(true); setAccessible0M.invoke(mapField, true); Obtain the Map and Remove Entries # ((Map\u0026lt;?, ?\u0026gt;) mapField.get(null)).clear(); Generics are erased at runtime, so we can simply cast to Map\u0026lt;?, ?\u0026gt; and clear it or remove entries. If you want to modify the map by putting entries, you will need to cast to Map\u0026lt;Class\u0026lt;?\u0026gt;, String[]\u0026gt;, where the key is the class the fields are declared in, and the value an array storing the names of the fields to be filtered. Although there are utility methods in Reflection for you to mess with the filter configuration, the entire reflection process must be done again, which is annoying. Here is the source code of the utility methods.\n// fieldNames must contain only interned Strings public static synchronized void registerFieldsToFilter(Class\u0026lt;?\u0026gt; containingClass, String ... fieldNames) { fieldFilterMap = registerFilter(fieldFilterMap, containingClass, fieldNames); } // methodNames must contain only interned Strings public static synchronized void registerMethodsToFilter(Class\u0026lt;?\u0026gt; containingClass, String ... methodNames) { methodFilterMap = registerFilter(methodFilterMap, containingClass, methodNames); } private static Map\u0026lt;Class\u0026lt;?\u0026gt;,String[]\u0026gt; registerFilter(Map\u0026lt;Class\u0026lt;?\u0026gt;,String[]\u0026gt; map, Class\u0026lt;?\u0026gt; containingClass, String ... names) { if (map.get(containingClass) != null) { throw new IllegalArgumentException (\u0026#34;Filter already registered: \u0026#34; + containingClass); } map = new HashMap\u0026lt;Class\u0026lt;?\u0026gt;,String[]\u0026gt;(map); map.put(containingClass, names); return map; } Modify Cache If Any # If the above method did not work, it is most likely because the class you are getting Members on has cached the Members. If you look back on the privateGetDeclaredFields(boolean), it actually obtains fields from java.lang.Class.ReflectionData.declaredFields field. Otherwise, it will obtain fields from getDeclaredFields0(), filter it, and cache it to ReflectionData.declaredFields. What this means is that the array of Field returned, which does not contain the filtered Fields, may actually be the old version before the filter got modified. Therefore, try to set the cache to the newer version.\nMethod reflectionDataM = Class.class.getDeclaredMethod(\u0026#34;reflectionData\u0026#34;); reflectionDataM.setAccessible(true); Object reflectionData = reflectionDataM.invoke(System.class); Field cacheField = reflectiondata.getClass().getDeclaredField(\u0026#34;declaredFields\u0026#34;); cacheField.setAccessible(true); cacheField.set(reflectionData, fields); ","date":"13 April 2019","externalUrl":null,"permalink":"/posts/1555156507529-disable-security-manager/","section":"Posts","summary":"","title":"Disable Security Manager","type":"posts"},{"content":"","date":"13 April 2019","externalUrl":null,"permalink":"/tags/java/","section":"Tags","summary":"","title":"Java","type":"tags"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]