Hi,
I've seen this question: Is there a way to get a Confluence page's tiny link programmatically? and I have one in a similar vein.
What is the algorithm used to create the tiny links?
I'm assuming it's some kind of base 64/62 encoder of some sort? If anyone knows where in the source to look for the code, a path would be greatly appreciated.
Steve
Check out the source of com.atlassian.confluence.pages.TinyUrl...
Base64.encodeBase64(longToByteArray(id));
id is the pageId.
If anyone would like to emulate this functionality in PHP for whatever reason:
function tinylinkid($id) { $bytes = pack("L", $id); // convert the long to bytes $bytes = rtrim($bytes); // trim any null characters $bytestring = ""; $len = strlen($bytes); for($i = 0; $i < $len; $i++) { $bytestring = $bytestring . $bytes[$i]; // build the byte string } $base64string = base64_encode($bytestring); // base64 encode the byte string $base64string = rtrim($base64string, "="); // trim any base64 padding characters return $base64string; }
Thanks for David for the pointer.
Could you describe a method of decoding?
I used the following functions in PHP based on the Steve's code:
function id2tinylink($id) { $bytes = pack("L", (int)$id); // convert the long to bytes $base64string=base64_encode($bytes); $base64string_escaped=strtr($base64string,'+/','_-'); return rtrim($base64string_escaped, "A="); } function tinylink2id($tinylink){ $base64string_encoded = str_pad($tinylink, 8, "A", STR_PAD_RIGHT); $base64string = strtr($base64string_encoded, '_-', '+/'); $bytes = base64_decode($base64string); return unpack("L",$bytes)[1]; }
Here a solution in JS. Please check if the browsers are compatible with btoa and atob (https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/btoa).
// The long to byte array conversion is taken from this source:// https://stackoverflow.com/questions/8482309/converting-javascript-integer-to-byte-array-and-backfunction longToByteArray(long) { var byteArray = [0, 0, 0, 0]; for ( var index = 0; index < byteArray.length; index ++ ) { var byte = long & 0xff; byteArray [ index ] = byte; long = (long - byte) / 256 ; } return byteArray;}function pageIdToShortUrl(pageId) { return btoa(longToByteArray(pageId) .map(String.fromCharCode) .map(function (s) { return s[0] }) .join("")) .replace(/=/g,"");}function byteArrayToLong(byteArray) { var value = 0; for ( var i = byteArray.length - 1; i >= 0; i--) { value = (value * 256) + byteArray[i]; } return value;};function shortUrlToPageId(shortUrl) { return byteArrayToLong(atob(shortUrl) .split("") .map(function (b) { return b.charCodeAt()}));}
I recently ported this to python:
def page_id_to_tiny(page_id): return base64.b64encode((page_id).to_bytes(4, byteorder='little')).decode().replace('/','-').replace('+','_').rstrip('A=')def tiny_to_page_id(tiny): return int.from_bytes(base64.b64decode(tiny.ljust(8,'A').replace('_','+').replace('-','/').encode()),byteorder='little')
Updated as per feedback below from @Sebastian Schmachtel and @Fabian Sturm that noticed that Atlassian do not actually implement url safe encoding from rfc4648 correctly.
This would have been a perfect answer but I think Atlassian screwed up and + is replaced by _ and / by - instead of the other way round as it is defined for the url safe encoding rfc4648.
I used this functions and started to see weird effects. So I returned here et voila a hint just in time :-)
My (working) code looks now like this
def page_id_to_tiny(page_ids: List[int]) -> List[str]:"""converts a list of pageids (int) to a list of last part of shortlink (/x/$PART)"""shortlinks: List[str] = []for page_id in page_ids:encoded = base64.b64encode(page_id.to_bytes(4, byteorder='little')).decode()encoded_fixed = encoded.replace("/", "-").replace("+", "_").rstrip('A=')shortlinks.append(encoded_fixed)return shortlinksdef tiny_to_page_id(tiny_links: List[str]) -> List[int]:"""converts a list of last part of shortlink (/x/$PART) to a list of pageids (int)"""page_ids: List[int] = []for tiny_link in tiny_links:tiny_link = tiny_link.replace("-", "/").replace("_", "+")page_ids.append(int.from_bytes(base64.b64decode(tiny_link.ljust(8, 'A').encode()), byteorder='little'))return page_ids
Here is a rough bash implementation. I don't bother chopping a trailing A because this official implementation also didn't
confluence-tiny-code() { local id="${1?}" local tiny printf -v tiny "\\\x%02x" $(( id & 0xff )) $(( (id >> 8 ) & 0xff )) $(( (id >> 16 ) & 0xff )) $(( (id >> 24 ) & 0xff )) tiny=$(printf "$tiny" | base64) tiny="${tiny//\+/_}" tiny="${tiny//\//-}" echo "${tiny%%=*}"}
I used your examples to write a PostgreSQL equivalent. Probably not as efficient, but could be useful.
-- Encode to short URLSELECT TRIM(TRAILING 'A=' FROM translate(encode(('\x' || regexp_replace(lpad(to_hex(content.contentid), 16, '0'),'(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)','\8\7\6\5\4\3\2\1'))::bytea, 'base64'), '-_', '/+')) "short_url"FROM (SELECT 200999755 "contentid") "content"
An example from the Confluence table directly:
SELECT contentid, TRIM(TRAILING 'A=' FROM translate(encode(('\x' || regexp_replace(lpad(to_hex(content.contentid), 16, '0'),'(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)','\8\7\6\5\4\3\2\1'))::bytea, 'base64'), '-_', '/+')) "short_url",titleFROM contentLIMIT 10
To decode a short URL to it's content ID:
-- Decode to content IDSELECT ('x' || regexp_replace(rpad(encode(decode(translate(short.url || repeat('A', 8 - length(short.url)), '_-', '+/'), 'base64'), 'hex'), 16, '0'),'(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)','\8\7\6\5\4\3\2\1'))::bit(64)::bigint "content_id"FROM (SELECT 'SwP7Cw' "url") "short"
For those interested in my Excel solution.Function implemented with VBA. So, you need to enable macros for this to work.
Create an Excel file and save as .xlsm.Press Alt-11 to open VBA editor. Add the VBA code below. Save. Press Alt-Q to close VBA editor.
Now you can use the function to make the calculation from x-hash in cell A2.=TinylinkToPageId(A2)Sorry for the dutch comments. Created with CoPilot assistance.
VBA script
Function TinylinkToPageId(tinyHash As String) As DoubleDim base64Str As StringDim decoded() As ByteDim i As IntegerDim pageId As Double
' Stap 1: Confluence-specifieke tekens vervangenbase64Str = Replace(tinyHash, "-", "/")base64Str = Replace(base64Str, "_", "+")
' Stap 2: Base64 padding toevoegenDo While Len(base64Str) Mod 4 <> 0base64Str = base64Str & "A"Loop
' Stap 3: Base64 decoderendecoded = Base64Decode(base64Str)
' Stap 4: Zorg dat we 8 bytes hebbenReDim Preserve decoded(7)
' Stap 5: Little-endian interpretatieFor i = 0 To 7pageId = pageId + decoded(i) * 2 ^ (8 * i)Next i
TinylinkToPageId = pageIdEnd Function
Function Base64Decode(base64Str As String) As Byte()Dim xmlObj As ObjectSet xmlObj = CreateObject("MSXML2.DOMDocument.6.0")Dim node As ObjectSet node = xmlObj.createElement("b64")node.DataType = "bin.base64"node.Text = base64StrBase64Decode = node.nodeTypedValueEnd Function
It looks like you're new here. Sign in or register to get started.