├── .editorconfig ├── .github └── workflows │ ├── dotnet-package.yml │ └── dotnet.yml ├── .gitignore ├── Base62.Tests ├── Base62.Tests.csproj ├── Base62Tests.cs └── validation_data.txt ├── Base62.sln ├── Base62 ├── Base62.csproj └── Base62Converter.cs ├── LICENSE.md └── README.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig dotnet 2 | root = true 3 | 4 | # Default settings: 5 | [*] 6 | insert_final_newline = false 7 | indent_style = space 8 | indent_size = 2 9 | 10 | [*.cs] 11 | indent_style = space 12 | indent_size = 4 13 | end_of_line = crlf 14 | 15 | # organize usings 16 | dotnet_sort_system_directives_first = true 17 | dotnet_separate_import_directive_groups = false -------------------------------------------------------------------------------- /.github/workflows/dotnet-package.yml: -------------------------------------------------------------------------------- 1 | name: Upload dotnet package 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | packages: write 12 | contents: read 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Setup .NET 17 | uses: actions/setup-dotnet@v3 18 | with: 19 | dotnet-version: 6.0.x 20 | - run: dotnet build --configuration Release . 21 | - name: Create the package 22 | run: dotnet pack --configuration Release . 23 | - name: Publish the package to nuget.org 24 | run: dotnet nuget push Base62/bin/Release/*.nupkg -k $NUGET_AUTH_TOKEN -s https://api.nuget.org/v3/index.json 25 | env: 26 | NUGET_AUTH_TOKEN: ${{secrets.NUGET_TOKEN}} 27 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Setup .NET 16 | uses: actions/setup-dotnet@v3 17 | with: 18 | dotnet-version: 6.0.x 19 | - name: Restore dependencies 20 | run: dotnet restore 21 | - name: Build 22 | run: dotnet build --no-restore 23 | - name: Test 24 | run: dotnet test --no-build --verbosity normal 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /Base62.Tests/Base62.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1;net5.0;net6.0 5 | 6 | false 7 | 8 | latest 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | all 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | Always 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Base62.Tests/Base62Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Xunit; 5 | 6 | namespace Base62.Tests 7 | { 8 | public class Base62Tests 9 | { 10 | [Fact] 11 | public void Encoded_CanBe_Decoded() 12 | { 13 | var input = "120"; 14 | var converter = new Base62Converter(); 15 | var encoded = converter.Encode(input); 16 | 17 | var decoded = converter.Decode(encoded); 18 | 19 | Assert.Equal(input, decoded); 20 | } 21 | 22 | [Fact] 23 | public void Encoded_Inverted_CanBe_Decoded() 24 | { 25 | var input = "Whatup"; 26 | var converter = new Base62Converter(Base62Converter.CharacterSet.INVERTED); 27 | var encoded = converter.Encode(input); 28 | 29 | var decoded = converter.Decode(encoded); 30 | 31 | Assert.Equal(input, decoded); 32 | } 33 | 34 | [Fact] 35 | public void NonAscii_CanBe_Decoded() 36 | { 37 | var input = "love爱"; 38 | var converter = new Base62Converter(Base62Converter.CharacterSet.DEFAULT); 39 | var encoded = converter.Encode(input); 40 | 41 | var decoded = converter.Decode(encoded); 42 | 43 | Assert.Equal(input, decoded); 44 | } 45 | 46 | [Theory] 47 | [MemberData(nameof(GetData))] 48 | public void ASCII_AND_UTF8_Can_RoundTrip(string input, string expected) 49 | { 50 | var converter = new Base62Converter(Base62Converter.CharacterSet.DEFAULT); 51 | var encoded = converter.Encode(input); 52 | var decoded = converter.Decode(encoded); 53 | 54 | Assert.Equal(expected, encoded); 55 | Assert.Equal(input, decoded); 56 | } 57 | 58 | [Fact] 59 | public void FirstZeroBytesAreConvertedCorrectly() 60 | { 61 | var sourceBytes = new byte[] { 0, 0, 1, 2, 0, 0 }; 62 | var converter = new Base62Converter(Base62Converter.CharacterSet.DEFAULT); 63 | var encoded = converter.Encode(sourceBytes); 64 | var decoded = converter.Decode(encoded); 65 | 66 | Assert.Equal(sourceBytes, decoded); 67 | } 68 | 69 | public static IEnumerable GetData() 70 | { 71 | var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "validation_data.txt"); 72 | using (var fileReader = new StreamReader(filePath)) 73 | { 74 | string row = null; 75 | while ((row = fileReader.ReadLine()) != null) 76 | { 77 | yield return row.Split('\t'); 78 | } 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Base62.Tests/validation_data.txt: -------------------------------------------------------------------------------- 1 | s|AP:/:-c0dsTy{RU>HE GTd7iXdpkmIpcDafvGI8VGM8kqL 2 | VUAZS!C<'jXHcug]ymhp CJjihlYgtAnMv2swqOZx5tjV3Zo 3 | )b_cGzqwaEJHfJ_|%=^w 5u6Kzh6DxD8nO4aEbLnVB3cOWhD 4 | jKG/~kj,;^r8pqa7VyIp FAJtS15WWPEqiLcDooJgqSG27OK 5 | e>6EMmoXi{M,QP?P61vF ERdVoM3wC2byDsNSk5BTnpSnS9G 6 | CNLK')/Gh4"/gR"=StzV 9bPk55C0QRvunDYCpuK9JILcVa2 7 | z&>aoAbyD;c}z~tF Oh" HQaBf0rPbrWmuhOBv7a4fimdYw6 8 | ?_s$\\'I{oMzm~)o F0CWO509LlHwmV7ZIyG3RsyV8CV 10 | BQrUn@Hbnu, -6=u[sTK 9Sg0ifJr4dUyycYz7dmsa2kOMGp 11 | G,vr7j;E@V+CAv Qdog^ A9d9zrbVNS2yCSZOQeOfx6I8fM6 12 | g9yTE42[6|X[ppn'*^9# EjAJdoX3USFpI7SR5FXZ5otKAmB 13 | ɹ⮝}󯭗񥋕Ҙ[.ϻ܂wӤ뺧,ƭۓާ򔤡ˌ񒮀 5IFmxrbwitrm50rcXUG24b9VQsgoJlYLbpLGsP0ibj9etwZSQUaj5uVstaktc 14 | ̮(Aɱ򧪯<ƘM&왚ܜƢ𓑽¿݆ѱd嗇ۆw 4AM4UlVB8EU8dWfWSsXnOj3jjAoRTEoo0YYQrbLHxiidTLfsztYaV 15 | 괲ꯆЏ�⦌=𚵤ΗF뮎ɂ�糟ϕ;뉀h쳔Ϝ򑝞 SsUwuuhaToPe53MvuswcjfUBB3Wn4sToqUUtcuuRBwAmIHO8LpAVwJnOHojZWoVHaw 16 | 鬅ʶƢݮ-ȍ6ǣʷ⅄⚧P𶕓𔎺ӱ񍛮㪢򲱋񩺣ȡ 7uBq5XYL3Jcw8Jwh9yJ8r7blCWADUK22cteubBnSVVnhBWl09vylXfRrL1R8dm8D5LKwz 17 | ū±宠湵Ҵ򬦏ڴ캃#䁅􏹍+ۘ򹏹󊼃Q񴘥򑌨ɔ۟ 1q1anhrpKhoaQEoS4WOlEQG1efmVwjYAJTIdABq1xwpLO4VegbiBtPmdzFr6aReClNroRnKR 18 | Rֵ짭͟𺮽ӄڪ팰赲\󽋟9X𚴑H슨q򺚼鬯ฮ 2T38pYtGbe3F6b6Ot8qAGvU0mIBPu4ueDZUoDOKKw5nqL2a1DATE80TE9KRnh6eWk 19 | ݣ򷡎򴺇럚䅻ˣŹ끤󣢘򡬃Zᅥũ右𷷱ЖӜ 9fOEssQw2073nWZxTMVAHbQpCKJVZgxJN1frhkUG5H7gyT1yybPCGXrLse9pFZcqsXHVfxye3zsWy 20 | Ӄ²〟뢢ϡ=Θ􊢚􋵛򆼊⎻򐜲̣煶r|򃥀椑򐽮伉 XYA07xsrzZCu0qE3HoPp24gJnaozuB6qlEmonZxYA6nrVUWn4AbjaoAbVNFpgTz4XMDRTSc8LZ 21 | ^τ񾧊�ϯ聙eZ򼝣\岮ḍϡ󢳪񒿥͐厣򙧯 sgqiaUFtlMr134KfrDEyXmv5oq2AL6I0ZHM6NS5tooVCUNWac5F5G4ZeR9wrivkk2MABMcR 22 | ̷𸤗󦵮놳μ󁇨󟋏ţ񧭥�󐀏*떓􍾅ػʵѢdT; 1u5e4XhT39ZKNZTxjdedGM6thTcb5Qy9kkvs8T8Z1wYvWUn3jshVXLnSvLJmuKpivIHedPRb 23 | ᱗ȤճŬn𑺶𡟻ߏ赤󟞲򑁰〷'Ë⣼䕹Lчǝ 7dRvagxqRnKzEO8xWKS2Lew2pylpBUe1xsRa7jLfkW18KPWMScwevHIRiQ1lQsUWCgoBx 24 | 𑿿ᦛǽ𷭾ƅ܊䗞󡯤🤵Œ8c񣤚灯ȇ󥄉Ὰ۷霵϶ c9tvA8at2zSKLUg00dFuceZzslHBiNvkcTjdrf2xknuTmifPsksNsU7USF8nQuddw7wHvVVq7S 25 | love爱 2Fn0Je6zHl 26 | 120 DWjo 27 | 987 FjWx 28 | 456 EM5O 29 | 0215984 108Cmq4qLk 30 | 🍙 4PCn4j -------------------------------------------------------------------------------- /Base62.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32210.238 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Base62", "Base62\Base62.csproj", "{AE1B5455-F004-4948-AE22-AC59C03C53BC}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Base62.Tests", "Base62.Tests\Base62.Tests.csproj", "{11235960-3E83-46D3-A66B-F493894471C5}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5CF4F621-110B-4ADF-A464-A75042EFCF6B}" 11 | ProjectSection(SolutionItems) = preProject 12 | .gitignore = .gitignore 13 | .editorconfig = .editorconfig 14 | LICENSE.md = LICENSE.md 15 | README.md = README.md 16 | EndProjectSection 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {AE1B5455-F004-4948-AE22-AC59C03C53BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {AE1B5455-F004-4948-AE22-AC59C03C53BC}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {AE1B5455-F004-4948-AE22-AC59C03C53BC}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {AE1B5455-F004-4948-AE22-AC59C03C53BC}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {11235960-3E83-46D3-A66B-F493894471C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {11235960-3E83-46D3-A66B-F493894471C5}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {11235960-3E83-46D3-A66B-F493894471C5}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {11235960-3E83-46D3-A66B-F493894471C5}.Release|Any CPU.Build.0 = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(SolutionProperties) = preSolution 34 | HideSolutionNode = FALSE 35 | EndGlobalSection 36 | GlobalSection(ExtensibilityGlobals) = postSolution 37 | SolutionGuid = {8510CFB9-442B-4F8A-BE3E-D87E5F9B0BA5} 38 | EndGlobalSection 39 | EndGlobal 40 | -------------------------------------------------------------------------------- /Base62/Base62.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard1.3;netstandard2.0;netstandard2.1;net5.0;net6.0 5 | Base62 encoder and decoder for .NET for URL shortening and/or obfuscation. 6 | true 7 | 8 | 2022 9 | 10 | https://github.com/ghost1face/base62 11 | https://github.com/ghost1face/base62 12 | base62 encoder decoder url-shortener conversion 13 | ghost1face 14 | 15 | * Addresses bug with leading zeros 16 | 17 | 1.3.0.0 18 | 1.3.0.0 19 | LICENSE.md 20 | 1.3.0 21 | git 22 | true 23 | true 24 | true 25 | 26 | 27 | 28 | 29 | True 30 | 31 | 32 | 33 | 34 | 35 | true 36 | true 37 | 38 | 39 | 40 | 41 | all 42 | runtime; build; native; contentfiles; analyzers; buildtransitive 43 | 44 | 45 | runtime; build; native; contentfiles; analyzers; buildtransitive 46 | all 47 | 48 | 49 | all 50 | runtime; build; native; contentfiles; analyzers 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Base62/Base62Converter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Base62 6 | { 7 | /// 8 | /// Encodes and decodes text to and from base62 encoding. 9 | /// 10 | public class Base62Converter 11 | { 12 | private const string DEFAULT_CHARACTER_SET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 13 | private const string INVERTED_CHARACTER_SET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 14 | private readonly string characterSet; 15 | 16 | /// 17 | /// Initializes a new instance of . 18 | /// 19 | public Base62Converter() 20 | { 21 | characterSet = DEFAULT_CHARACTER_SET; 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of with the provided . 26 | /// 27 | /// 28 | public Base62Converter(CharacterSet charset) 29 | { 30 | if (charset == CharacterSet.DEFAULT) 31 | characterSet = DEFAULT_CHARACTER_SET; 32 | else 33 | characterSet = INVERTED_CHARACTER_SET; 34 | } 35 | 36 | /// 37 | /// Encodes the input text to Base62 format. 38 | /// 39 | /// The input value. 40 | /// Encoded base62 value. 41 | public string Encode(string value) 42 | { 43 | var arr = Encoding.UTF8.GetBytes(value); 44 | var converted = Encode(arr); 45 | var builder = new StringBuilder(); 46 | foreach (var c in converted) 47 | { 48 | builder.Append(characterSet[c]); 49 | } 50 | return builder.ToString(); 51 | } 52 | 53 | /// 54 | /// Decodes the input text from Base62 format. 55 | /// 56 | /// The input value. 57 | /// The decoded value. 58 | public string Decode(string value) 59 | { 60 | var arr = new byte[value.Length]; 61 | for (var i = 0; i < arr.Length; i++) 62 | { 63 | arr[i] = (byte)characterSet.IndexOf(value[i]); 64 | } 65 | 66 | var converted = Decode(arr); 67 | return Encoding.UTF8.GetString(converted, 0, converted.Length); 68 | } 69 | 70 | /// 71 | /// Encodes the input bytes to Base62 format. 72 | /// 73 | /// The input value. 74 | /// Encoded base62 value. 75 | public byte[] Encode(byte[] value) 76 | { 77 | return BaseConvert(value, 256, 62); 78 | } 79 | 80 | /// 81 | /// Decodes the input bytes from Base62 format. 82 | /// 83 | /// The inpnut value. 84 | /// The decoded value. 85 | public byte[] Decode(byte[] value) 86 | { 87 | return BaseConvert(value, 62, 256); 88 | } 89 | 90 | /// 91 | /// Converts source byte array from the source base to the destination base. 92 | /// 93 | /// Byte array to convert. 94 | /// Source base to convert from. 95 | /// Target base to convert to. 96 | /// Converted byte array. 97 | private static byte[] BaseConvert(byte[] source, int sourceBase, int targetBase) 98 | { 99 | if (targetBase < 2 || targetBase > 256) 100 | throw new ArgumentOutOfRangeException(nameof(targetBase), targetBase, "Value must be between 2 & 256 (inclusive)"); 101 | 102 | if (sourceBase < 2 || sourceBase > 256) 103 | throw new ArgumentOutOfRangeException(nameof(sourceBase), sourceBase, "Value must be between 2 & 256 (inclusive)"); 104 | 105 | // Set initial capacity estimate if the size is small. 106 | var startCapacity = source.Length < 1028 107 | ? (int)(source.Length * 1.5) 108 | : source.Length; 109 | 110 | var result = new List(startCapacity); 111 | var quotient = new List((int)(source.Length * 0.5)); 112 | int count; 113 | int initialStartOffset = 0; 114 | 115 | // This is a bug fix for the following issue: 116 | // https://github.com/ghost1face/base62/issues/4 117 | while (source[initialStartOffset] == 0) 118 | { 119 | result.Add(0); 120 | initialStartOffset++; 121 | } 122 | 123 | int startOffset = initialStartOffset; 124 | 125 | while ((count = source.Length) > 0) 126 | { 127 | quotient.Clear(); 128 | int remainder = 0; 129 | for (var i = initialStartOffset; i != count; i++) 130 | { 131 | int accumulator = source[i] + remainder * sourceBase; 132 | byte digit = (byte)((accumulator - (accumulator % targetBase)) / targetBase); 133 | remainder = accumulator % targetBase; 134 | if (quotient.Count > 0 || digit != 0) 135 | { 136 | quotient.Add(digit); 137 | } 138 | } 139 | 140 | result.Insert(startOffset, remainder); 141 | source = quotient.ToArray(); 142 | initialStartOffset = 0; 143 | } 144 | 145 | var output = new byte[result.Count]; 146 | 147 | for (int i = 0; i < result.Count; i++) 148 | output[i] = (byte)result[i]; 149 | 150 | return output; 151 | } 152 | 153 | /// 154 | /// Character set to use for encoding/decoding. 155 | /// 156 | public enum CharacterSet 157 | { 158 | /// 159 | /// Alpha numeric character set, using capital letters first before lowercase. 160 | /// 161 | DEFAULT, 162 | 163 | /// 164 | /// Alpha numeric character set, using lower case letters first before uppercase. 165 | /// 166 | INVERTED 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Daniel Destouche 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Base62 2 | 3 | Base62 encoder and decoder based on [Base62 for PHP](https://github.com/tuupola/base62) for .NET. This library is useful for converting data into shortened strings good for URL shortening and/or obfuscating auto-incrementing resource ids from being exposed through RESTful APIs. 4 | 5 | [![.NET](https://github.com/ghost1face/base62/actions/workflows/dotnet.yml/badge.svg?branch=master)](https://github.com/ghost1face/base62/actions/workflows/dotnet.yml) [![Nuget](https://img.shields.io/nuget/v/Base62.svg)](https://www.nuget.org/packages/Base62) 6 | 7 | 8 | ## Compatibility 9 | 10 | Written to be netstandard compliant, this library should be able to be used cross-platform. See [netstandard](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) details for more information. 11 | 12 | ## Usage 13 | 14 | ``` 15 | var base62Converter = new Base62Converter(); 16 | 17 | var encoded = base62Converter.Encode("120"); 18 | 19 | Console.WriteLine(encoded); 20 | 21 | var decoded = base62Converter.Decode(encoded); 22 | 23 | Console.WriteLine(decoded); 24 | 25 | // output is: 26 | // "DWjo" 27 | // "120" 28 | 29 | ``` 30 | 31 | ## Character sets 32 | 33 | By default Base62 uses `[0-9A-Za-z]` character set but can be alternated to use `[0-9a-zA-Z]` through the constructor. 34 | 35 | ``` 36 | new Base62Converter(Base62Converter.CharacterSet.INVERTED); 37 | 38 | ... 39 | 40 | ``` 41 | 42 | ## License 43 | 44 | [The MIT LIcense (MIT)](./LICENSE.md). 45 | --------------------------------------------------------------------------------