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.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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 shortlinks
def 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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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-back
function 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()}));
}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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]; }
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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 Double
Dim base64Str As String
Dim decoded() As Byte
Dim i As Integer
Dim pageId As Double
' Stap 1: Confluence-specifieke tekens vervangen
base64Str = Replace(tinyHash, "-", "/")
base64Str = Replace(base64Str, "_", "+")
' Stap 2: Base64 padding toevoegen
Do While Len(base64Str) Mod 4 <> 0
base64Str = base64Str & "A"
Loop
' Stap 3: Base64 decoderen
decoded = Base64Decode(base64Str)
' Stap 4: Zorg dat we 8 bytes hebben
ReDim Preserve decoded(7)
' Stap 5: Little-endian interpretatie
For i = 0 To 7
pageId = pageId + decoded(i) * 2 ^ (8 * i)
Next i
TinylinkToPageId = pageId
End Function
Function Base64Decode(base64Str As String) As Byte()
Dim xmlObj As Object
Set xmlObj = CreateObject("MSXML2.DOMDocument.6.0")
Dim node As Object
Set node = xmlObj.createElement("b64")
node.DataType = "bin.base64"
node.Text = base64Str
Base64Decode = node.nodeTypedValue
End Function
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I used your examples to write a PostgreSQL equivalent. Probably not as efficient, but could be useful.
-- Encode to short URL
SELECT 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",
title
FROM content
LIMIT 10
To decode a short URL to it's content ID:
-- Decode to content ID
SELECT ('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"
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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%%=*}"
}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
Steve
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.